This commit was generated by cvs2svn to compensate for changes in r6255,
authorivan <ivan>
Sun, 2 Mar 2008 04:11:51 +0000 (04:11 +0000)
committerivan <ivan>
Sun, 2 Mar 2008 04:11:51 +0000 (04:11 +0000)
which included commits to RCS files with non-trunk default branches.

1696 files changed:
AGPL [new file with mode: 0644]
CREDITS [new file with mode: 0644]
FS/Changes [new file with mode: 0644]
FS/FS.pm [new file with mode: 0644]
FS/FS/AccessRight.pm [new file with mode: 0644]
FS/FS/CGI.pm [new file with mode: 0644]
FS/FS/ClientAPI.pm [new file with mode: 0644]
FS/FS/ClientAPI/Agent.pm [new file with mode: 0644]
FS/FS/ClientAPI/MyAccount.pm [new file with mode: 0644]
FS/FS/ClientAPI/Signup.pm [new file with mode: 0644]
FS/FS/ClientAPI/passwd.pm [new file with mode: 0644]
FS/FS/ClientAPI_SessionCache.pm [new file with mode: 0644]
FS/FS/Conf.pm [new file with mode: 0644]
FS/FS/ConfDefaults.pm [new file with mode: 0644]
FS/FS/ConfItem.pm [new file with mode: 0644]
FS/FS/Conf_compat17.pm [new file with mode: 0644]
FS/FS/Cron/backup.pm [new file with mode: 0644]
FS/FS/Cron/bill.pm [new file with mode: 0644]
FS/FS/Cron/expire_user_pref.pm [new file with mode: 0644]
FS/FS/Cron/notify.pm [new file with mode: 0644]
FS/FS/Cron/vacuum.pm [new file with mode: 0644]
FS/FS/CurrentUser.pm [new file with mode: 0644]
FS/FS/Daemon.pm [new file with mode: 0644]
FS/FS/InitHandler.pm [new file with mode: 0644]
FS/FS/Misc.pm [new file with mode: 0644]
FS/FS/Misc/prune.pm [new file with mode: 0644]
FS/FS/Msgcat.pm [new file with mode: 0644]
FS/FS/Pony.pm [new file with mode: 0644]
FS/FS/Record.pm [new file with mode: 0644]
FS/FS/Report.pm [new file with mode: 0644]
FS/FS/Report/Table.pm [new file with mode: 0644]
FS/FS/Report/Table/Monthly.pm [new file with mode: 0644]
FS/FS/Schema.pm [new file with mode: 0644]
FS/FS/SearchCache.pm [new file with mode: 0644]
FS/FS/Setup.pm [new file with mode: 0644]
FS/FS/TicketSystem.pm [new file with mode: 0644]
FS/FS/TicketSystem/RT_External.pm [new file with mode: 0644]
FS/FS/TicketSystem/RT_Internal.pm [new file with mode: 0644]
FS/FS/TicketSystem/RT_Libs.pm [new file with mode: 0644]
FS/FS/UI/Web.pm [new file with mode: 0644]
FS/FS/UI/bytecount.pm [new file with mode: 0644]
FS/FS/UID.pm [new file with mode: 0644]
FS/FS/Upgrade.pm [new file with mode: 0644]
FS/FS/XMLRPC.pm [new file with mode: 0644]
FS/FS/access_group.pm [new file with mode: 0644]
FS/FS/access_groupagent.pm [new file with mode: 0644]
FS/FS/access_right.pm [new file with mode: 0644]
FS/FS/access_user.pm [new file with mode: 0644]
FS/FS/access_user_pref.pm [new file with mode: 0644]
FS/FS/access_usergroup.pm [new file with mode: 0644]
FS/FS/acct_rt_transaction.pm [new file with mode: 0644]
FS/FS/acct_snarf.pm [new file with mode: 0644]
FS/FS/addr_block.pm [new file with mode: 0755]
FS/FS/agent.pm [new file with mode: 0644]
FS/FS/agent_payment_gateway.pm [new file with mode: 0644]
FS/FS/agent_type.pm [new file with mode: 0644]
FS/FS/banned_pay.pm [new file with mode: 0644]
FS/FS/cdr.pm [new file with mode: 0644]
FS/FS/cdr_calltype.pm [new file with mode: 0644]
FS/FS/cdr_carrier.pm [new file with mode: 0644]
FS/FS/cdr_type.pm [new file with mode: 0644]
FS/FS/cdr_upstream_rate.pm [new file with mode: 0644]
FS/FS/clientapi_session.pm [new file with mode: 0644]
FS/FS/clientapi_session_field.pm [new file with mode: 0644]
FS/FS/conf.pm [new file with mode: 0644]
FS/FS/cust_bill.pm [new file with mode: 0644]
FS/FS/cust_bill_ApplicationCommon.pm [new file with mode: 0644]
FS/FS/cust_bill_event.pm [new file with mode: 0644]
FS/FS/cust_bill_pay.pm [new file with mode: 0644]
FS/FS/cust_bill_pay_batch.pm [new file with mode: 0644]
FS/FS/cust_bill_pay_pkg.pm [new file with mode: 0644]
FS/FS/cust_bill_pkg.pm [new file with mode: 0644]
FS/FS/cust_bill_pkg_detail.pm [new file with mode: 0644]
FS/FS/cust_credit.pm [new file with mode: 0644]
FS/FS/cust_credit_bill.pm [new file with mode: 0644]
FS/FS/cust_credit_bill_pkg.pm [new file with mode: 0644]
FS/FS/cust_credit_refund.pm [new file with mode: 0644]
FS/FS/cust_event.pm [new file with mode: 0644]
FS/FS/cust_main.pm [new file with mode: 0644]
FS/FS/cust_main_Mixin.pm [new file with mode: 0644]
FS/FS/cust_main_county.pm [new file with mode: 0644]
FS/FS/cust_main_invoice.pm [new file with mode: 0644]
FS/FS/cust_main_note.pm [new file with mode: 0644]
FS/FS/cust_pay.pm [new file with mode: 0644]
FS/FS/cust_pay_batch.pm [new file with mode: 0644]
FS/FS/cust_pay_pending.pm [new file with mode: 0644]
FS/FS/cust_pay_refund.pm [new file with mode: 0644]
FS/FS/cust_pay_void.pm [new file with mode: 0644]
FS/FS/cust_pkg.pm [new file with mode: 0644]
FS/FS/cust_pkg_option.pm [new file with mode: 0644]
FS/FS/cust_pkg_reason.pm [new file with mode: 0644]
FS/FS/cust_refund.pm [new file with mode: 0644]
FS/FS/cust_svc.pm [new file with mode: 0644]
FS/FS/cust_tax_exempt.pm [new file with mode: 0644]
FS/FS/cust_tax_exempt_pkg.pm [new file with mode: 0644]
FS/FS/domain_record.pm [new file with mode: 0644]
FS/FS/export_svc.pm [new file with mode: 0644]
FS/FS/h_Common.pm [new file with mode: 0644]
FS/FS/h_cust_bill.pm [new file with mode: 0644]
FS/FS/h_cust_credit.pm [new file with mode: 0644]
FS/FS/h_cust_pay.pm [new file with mode: 0644]
FS/FS/h_cust_svc.pm [new file with mode: 0644]
FS/FS/h_cust_tax_exempt.pm [new file with mode: 0644]
FS/FS/h_domain_record.pm [new file with mode: 0644]
FS/FS/h_svc_acct.pm [new file with mode: 0644]
FS/FS/h_svc_broadband.pm [new file with mode: 0644]
FS/FS/h_svc_domain.pm [new file with mode: 0644]
FS/FS/h_svc_external.pm [new file with mode: 0644]
FS/FS/h_svc_forward.pm [new file with mode: 0644]
FS/FS/h_svc_phone.pm [new file with mode: 0644]
FS/FS/h_svc_www.pm [new file with mode: 0644]
FS/FS/inventory_class.pm [new file with mode: 0644]
FS/FS/inventory_item.pm [new file with mode: 0644]
FS/FS/m2m_Common.pm [new file with mode: 0644]
FS/FS/m2name_Common.pm [new file with mode: 0644]
FS/FS/msgcat.pm [new file with mode: 0644]
FS/FS/nas.pm [new file with mode: 0644]
FS/FS/option_Common.pm [new file with mode: 0644]
FS/FS/part_bill_event.pm [new file with mode: 0644]
FS/FS/part_event.pm [new file with mode: 0644]
FS/FS/part_event/Action.pm [new file with mode: 0644]
FS/FS/part_event/Action/addpost.pm [new file with mode: 0644]
FS/FS/part_event/Action/apply.pm [new file with mode: 0644]
FS/FS/part_event/Action/bill.pm [new file with mode: 0644]
FS/FS/part_event/Action/cancel.pm [new file with mode: 0644]
FS/FS/part_event/Action/collect.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_batch.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_comp.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_fee_percent.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_realtime_card.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_realtime_check.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_realtime_lec.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_send.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_send_agent.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_send_alternate.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_send_if_newest.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_spool_csv.pm [new file with mode: 0644]
FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm [new file with mode: 0644]
FS/FS/part_event/Action/fee.pm [new file with mode: 0644]
FS/FS/part_event/Action/suspend.pm [new file with mode: 0644]
FS/FS/part_event/Action/suspend_if_pkgpart.pm [new file with mode: 0644]
FS/FS/part_event/Action/suspend_unless_pkgpart.pm [new file with mode: 0644]
FS/FS/part_event/Condition.pm [new file with mode: 0644]
FS/FS/part_event/Condition/agent.pm [new file with mode: 0644]
FS/FS/part_event/Condition/agent_type.pm [new file with mode: 0644]
FS/FS/part_event/Condition/balance.pm [new file with mode: 0644]
FS/FS/part_event/Condition/balance_age.pm [new file with mode: 0644]
FS/FS/part_event/Condition/balance_under.pm [new file with mode: 0644]
FS/FS/part_event/Condition/cust_bill_age.pm [new file with mode: 0644]
FS/FS/part_event/Condition/cust_bill_has_service.pm [new file with mode: 0644]
FS/FS/part_event/Condition/cust_bill_owed.pm [new file with mode: 0644]
FS/FS/part_event/Condition/cust_bill_owed_under.pm [new file with mode: 0644]
FS/FS/part_event/Condition/cust_pay_batch_declined.pm [new file with mode: 0644]
FS/FS/part_event/Condition/cust_status.pm [new file with mode: 0644]
FS/FS/part_event/Condition/every.pm [new file with mode: 0644]
FS/FS/part_event/Condition/once.pm [new file with mode: 0644]
FS/FS/part_event/Condition/payby.pm [new file with mode: 0644]
FS/FS/part_event/Condition/pkg_class.pm [new file with mode: 0644]
FS/FS/part_event/Condition/pkg_status.pm [new file with mode: 0644]
FS/FS/part_event_condition.pm [new file with mode: 0644]
FS/FS/part_event_condition_option.pm [new file with mode: 0644]
FS/FS/part_event_condition_option_option.pm [new file with mode: 0644]
FS/FS/part_event_option.pm [new file with mode: 0644]
FS/FS/part_export.pm [new file with mode: 0644]
FS/FS/part_export/acct_plesk.pm [new file with mode: 0644]
FS/FS/part_export/acct_sql.pm [new file with mode: 0644]
FS/FS/part_export/apache.pm [new file with mode: 0644]
FS/FS/part_export/artera_turbo.pm [new file with mode: 0644]
FS/FS/part_export/bind.pm [new file with mode: 0644]
FS/FS/part_export/bind_slave.pm [new file with mode: 0644]
FS/FS/part_export/bsdshell.pm [new file with mode: 0644]
FS/FS/part_export/communigate_pro.pm [new file with mode: 0644]
FS/FS/part_export/communigate_pro_singledomain.pm [new file with mode: 0644]
FS/FS/part_export/cp.pm [new file with mode: 0644]
FS/FS/part_export/cpanel.pm [new file with mode: 0644]
FS/FS/part_export/cyrus.pm [new file with mode: 0644]
FS/FS/part_export/domain_shellcommands.pm [new file with mode: 0644]
FS/FS/part_export/domain_sql.pm [new file with mode: 0644]
FS/FS/part_export/everyone_net.pm [new file with mode: 0644]
FS/FS/part_export/forward_shellcommands.pm [new file with mode: 0644]
FS/FS/part_export/http.pm [new file with mode: 0644]
FS/FS/part_export/infostreet.pm [new file with mode: 0644]
FS/FS/part_export/ldap.pm [new file with mode: 0644]
FS/FS/part_export/nas_wrapper.pm [new file with mode: 0644]
FS/FS/part_export/null.pm [new file with mode: 0644]
FS/FS/part_export/passwdfile.pm [new file with mode: 0644]
FS/FS/part_export/postfix.pm [new file with mode: 0644]
FS/FS/part_export/prizm.pm [new file with mode: 0644]
FS/FS/part_export/radiator.pm [new file with mode: 0644]
FS/FS/part_export/router.pm [new file with mode: 0644]
FS/FS/part_export/shellcommands.pm [new file with mode: 0644]
FS/FS/part_export/shellcommands_withdomain.pm [new file with mode: 0644]
FS/FS/part_export/snmp.pm [new file with mode: 0644]
FS/FS/part_export/sqlmail.pm [new file with mode: 0644]
FS/FS/part_export/sqlradius.pm [new file with mode: 0644]
FS/FS/part_export/sqlradius_withdomain.pm [new file with mode: 0644]
FS/FS/part_export/sysvshell.pm [new file with mode: 0644]
FS/FS/part_export/textradius.pm [new file with mode: 0644]
FS/FS/part_export/trango.pm [new file with mode: 0644]
FS/FS/part_export/vpopmail.pm [new file with mode: 0644]
FS/FS/part_export/www_plesk.pm [new file with mode: 0644]
FS/FS/part_export/www_shellcommands.pm [new file with mode: 0644]
FS/FS/part_export_option.pm [new file with mode: 0644]
FS/FS/part_pkg.pm [new file with mode: 0644]
FS/FS/part_pkg/base_delayed.pm [new file with mode: 0644]
FS/FS/part_pkg/base_rate.pm [new file with mode: 0644]
FS/FS/part_pkg/bulk.pm [new file with mode: 0644]
FS/FS/part_pkg/flat.pm [new file with mode: 0644]
FS/FS/part_pkg/flat_comission.pm [new file with mode: 0644]
FS/FS/part_pkg/flat_comission_cust.pm [new file with mode: 0644]
FS/FS/part_pkg/flat_comission_pkg.pm [new file with mode: 0644]
FS/FS/part_pkg/flat_delayed.pm [new file with mode: 0644]
FS/FS/part_pkg/flat_introrate.pm [new file with mode: 0644]
FS/FS/part_pkg/incomplete/billoneday.pm [new file with mode: 0644]
FS/FS/part_pkg/prepaid.pm [new file with mode: 0644]
FS/FS/part_pkg/prorate.pm [new file with mode: 0644]
FS/FS/part_pkg/prorate_delayed.pm [new file with mode: 0644]
FS/FS/part_pkg/sesmon_hour.pm [new file with mode: 0644]
FS/FS/part_pkg/sesmon_minute.pm [new file with mode: 0644]
FS/FS/part_pkg/sql_external.pm [new file with mode: 0644]
FS/FS/part_pkg/sql_generic.pm [new file with mode: 0644]
FS/FS/part_pkg/sqlradacct_hour.pm [new file with mode: 0644]
FS/FS/part_pkg/subscription.pm [new file with mode: 0644]
FS/FS/part_pkg/voip_cdr.pm [new file with mode: 0644]
FS/FS/part_pkg/voip_sqlradacct.pm [new file with mode: 0644]
FS/FS/part_pkg_option.pm [new file with mode: 0644]
FS/FS/part_pkg_taxclass.pm [new file with mode: 0644]
FS/FS/part_pop_local.pm [new file with mode: 0644]
FS/FS/part_referral.pm [new file with mode: 0644]
FS/FS/part_svc.pm [new file with mode: 0644]
FS/FS/part_svc_column.pm [new file with mode: 0644]
FS/FS/part_svc_router.pm [new file with mode: 0755]
FS/FS/part_virtual_field.pm [new file with mode: 0755]
FS/FS/pay_batch.pm [new file with mode: 0644]
FS/FS/payby.pm [new file with mode: 0644]
FS/FS/payinfo_Mixin.pm [new file with mode: 0644]
FS/FS/payment_gateway.pm [new file with mode: 0644]
FS/FS/payment_gateway_option.pm [new file with mode: 0644]
FS/FS/pkg_class.pm [new file with mode: 0644]
FS/FS/pkg_referral.pm [new file with mode: 0644]
FS/FS/pkg_svc.pm [new file with mode: 0644]
FS/FS/port.pm [new file with mode: 0644]
FS/FS/prepay_credit.pm [new file with mode: 0644]
FS/FS/queue.pm [new file with mode: 0644]
FS/FS/queue_arg.pm [new file with mode: 0644]
FS/FS/queue_depend.pm [new file with mode: 0644]
FS/FS/raddb.pm [new file with mode: 0644]
FS/FS/radius_usergroup.pm [new file with mode: 0644]
FS/FS/rate.pm [new file with mode: 0644]
FS/FS/rate_detail.pm [new file with mode: 0644]
FS/FS/rate_prefix.pm [new file with mode: 0644]
FS/FS/rate_region.pm [new file with mode: 0644]
FS/FS/reason.pm [new file with mode: 0644]
FS/FS/reason_type.pm [new file with mode: 0644]
FS/FS/reg_code.pm [new file with mode: 0644]
FS/FS/reg_code_pkg.pm [new file with mode: 0644]
FS/FS/registrar.pm [new file with mode: 0644]
FS/FS/router.pm [new file with mode: 0755]
FS/FS/session.pm [new file with mode: 0644]
FS/FS/svc_Common.pm [new file with mode: 0644]
FS/FS/svc_External_Common.pm [new file with mode: 0644]
FS/FS/svc_Parent_Mixin.pm [new file with mode: 0644]
FS/FS/svc_acct.pm [new file with mode: 0644]
FS/FS/svc_acct_pop.pm [new file with mode: 0644]
FS/FS/svc_broadband.pm [new file with mode: 0755]
FS/FS/svc_domain.pm [new file with mode: 0644]
FS/FS/svc_external.pm [new file with mode: 0644]
FS/FS/svc_forward.pm [new file with mode: 0644]
FS/FS/svc_phone.pm [new file with mode: 0644]
FS/FS/svc_www.pm [new file with mode: 0644]
FS/FS/type_pkgs.pm [new file with mode: 0644]
FS/MANIFEST [new file with mode: 0644]
FS/MANIFEST.SKIP [new file with mode: 0644]
FS/Makefile.PL [new file with mode: 0644]
FS/bin/freeside-addgroup [new file with mode: 0755]
FS/bin/freeside-addoutsource [new file with mode: 0644]
FS/bin/freeside-addoutsourceuser [new file with mode: 0644]
FS/bin/freeside-adduser [new file with mode: 0644]
FS/bin/freeside-apply-credits [new file with mode: 0755]
FS/bin/freeside-count-active-customers [new file with mode: 0755]
FS/bin/freeside-daily [new file with mode: 0755]
FS/bin/freeside-dbdef-create [new file with mode: 0755]
FS/bin/freeside-delete-addr_blocks [new file with mode: 0755]
FS/bin/freeside-deloutsource [new file with mode: 0644]
FS/bin/freeside-deloutsourceuser [new file with mode: 0644]
FS/bin/freeside-deluser [new file with mode: 0644]
FS/bin/freeside-disable-reasons [new file with mode: 0755]
FS/bin/freeside-email [new file with mode: 0755]
FS/bin/freeside-expiration-alerter [new file with mode: 0755]
FS/bin/freeside-fetch [new file with mode: 0755]
FS/bin/freeside-history-requeue [new file with mode: 0755]
FS/bin/freeside-init-config [new file with mode: 0755]
FS/bin/freeside-monthly [new file with mode: 0755]
FS/bin/freeside-prepaidd [new file with mode: 0644]
FS/bin/freeside-prune-applications [new file with mode: 0755]
FS/bin/freeside-queued [new file with mode: 0644]
FS/bin/freeside-radgroup [new file with mode: 0644]
FS/bin/freeside-reexport [new file with mode: 0644]
FS/bin/freeside-reset-fixed [new file with mode: 0755]
FS/bin/freeside-selfservice-server [new file with mode: 0644]
FS/bin/freeside-setinvoice [new file with mode: 0644]
FS/bin/freeside-setup [new file with mode: 0755]
FS/bin/freeside-sqlradius-dedup-group [new file with mode: 0755]
FS/bin/freeside-sqlradius-radacctd [new file with mode: 0644]
FS/bin/freeside-sqlradius-reset [new file with mode: 0755]
FS/bin/freeside-sqlradius-seconds [new file with mode: 0644]
FS/bin/freeside-sqlradius-set-lastlog [new file with mode: 0755]
FS/bin/freeside-upgrade [new file with mode: 0755]
FS/t/AccessRight.t [new file with mode: 0644]
FS/t/CGI.t [new file with mode: 0644]
FS/t/ClientAPI.t [new file with mode: 0644]
FS/t/ClientAPI_SessionCache.t [new file with mode: 0644]
FS/t/Conf.t [new file with mode: 0644]
FS/t/ConfDefaults.t [new file with mode: 0644]
FS/t/ConfItem.t [new file with mode: 0644]
FS/t/Cron-backup.t [new file with mode: 0644]
FS/t/Cron-bill.t [new file with mode: 0644]
FS/t/Cron-vacuum.t [new file with mode: 0644]
FS/t/Daemon.t [new file with mode: 0644]
FS/t/InitHandler.t [new file with mode: 0644]
FS/t/Misc.t [new file with mode: 0644]
FS/t/Msgcat.t [new file with mode: 0644]
FS/t/Record.t [new file with mode: 0644]
FS/t/Report-Table-Monthly.t [new file with mode: 0644]
FS/t/Report-Table.t [new file with mode: 0644]
FS/t/Report.t [new file with mode: 0644]
FS/t/SearchCache.t [new file with mode: 0644]
FS/t/UID.t [new file with mode: 0644]
FS/t/access_group.t [new file with mode: 0644]
FS/t/access_groupagent.t [new file with mode: 0644]
FS/t/access_right.t [new file with mode: 0644]
FS/t/access_user.t [new file with mode: 0644]
FS/t/access_user_pref.t [new file with mode: 0644]
FS/t/access_usergroup.t [new file with mode: 0644]
FS/t/acct_rt_transaction.t [new file with mode: 0644]
FS/t/acct_snarf.t [new file with mode: 0644]
FS/t/agent.t [new file with mode: 0644]
FS/t/agent_payment_gateway.t [new file with mode: 0644]
FS/t/agent_type.t [new file with mode: 0644]
FS/t/banned_pay.t [new file with mode: 0644]
FS/t/cdr.t [new file with mode: 0644]
FS/t/cdr_calltype.t [new file with mode: 0644]
FS/t/cdr_carrier.t [new file with mode: 0644]
FS/t/cdr_type.t [new file with mode: 0644]
FS/t/cdr_upstream_rate.t [new file with mode: 0644]
FS/t/clientapi_session.t [new file with mode: 0644]
FS/t/clientapi_session_field.t [new file with mode: 0644]
FS/t/conf.t [new file with mode: 0644]
FS/t/cust_bill.t [new file with mode: 0644]
FS/t/cust_bill_ApplicationCommon.t [new file with mode: 0644]
FS/t/cust_bill_event.t [new file with mode: 0644]
FS/t/cust_bill_pay.t [new file with mode: 0644]
FS/t/cust_bill_pay_batch.t [new file with mode: 0644]
FS/t/cust_bill_pay_pkg.t [new file with mode: 0644]
FS/t/cust_bill_pkg.t [new file with mode: 0644]
FS/t/cust_bill_pkg_detail.t [new file with mode: 0644]
FS/t/cust_credit.t [new file with mode: 0644]
FS/t/cust_credit_bill.t [new file with mode: 0644]
FS/t/cust_credit_bill_pkg.t [new file with mode: 0644]
FS/t/cust_credit_refund.t [new file with mode: 0644]
FS/t/cust_event.t [new file with mode: 0644]
FS/t/cust_main.t [new file with mode: 0644]
FS/t/cust_main_Mixin.t [new file with mode: 0644]
FS/t/cust_main_county.t [new file with mode: 0644]
FS/t/cust_main_invoice.t [new file with mode: 0644]
FS/t/cust_main_note.t [new file with mode: 0644]
FS/t/cust_pay.t [new file with mode: 0644]
FS/t/cust_pay_batch.t [new file with mode: 0644]
FS/t/cust_pay_pending.t [new file with mode: 0644]
FS/t/cust_pay_refund.t [new file with mode: 0644]
FS/t/cust_pay_void.t [new file with mode: 0644]
FS/t/cust_pkg.t [new file with mode: 0644]
FS/t/cust_pkg_option.t [new file with mode: 0644]
FS/t/cust_pkg_reason.t [new file with mode: 0644]
FS/t/cust_refund.t [new file with mode: 0644]
FS/t/cust_svc.t [new file with mode: 0644]
FS/t/cust_tax_exempt.t [new file with mode: 0644]
FS/t/cust_tax_exempt_pkg.t [new file with mode: 0644]
FS/t/domain_record.t [new file with mode: 0644]
FS/t/export_svc.t [new file with mode: 0644]
FS/t/h_Common.t [new file with mode: 0644]
FS/t/h_cust_bill.t [new file with mode: 0644]
FS/t/h_cust_credit.t [new file with mode: 0644]
FS/t/h_cust_pay.t [new file with mode: 0644]
FS/t/h_cust_svc.t [new file with mode: 0644]
FS/t/h_cust_tax_exempt.t [new file with mode: 0644]
FS/t/h_domain_record.t [new file with mode: 0644]
FS/t/h_svc_acct.t [new file with mode: 0644]
FS/t/h_svc_broadband.t [new file with mode: 0644]
FS/t/h_svc_domain.t [new file with mode: 0644]
FS/t/h_svc_external.t [new file with mode: 0644]
FS/t/h_svc_forward.t [new file with mode: 0644]
FS/t/h_svc_www.t [new file with mode: 0644]
FS/t/inventory_class.t [new file with mode: 0644]
FS/t/inventory_item.t [new file with mode: 0644]
FS/t/msgcat.t [new file with mode: 0644]
FS/t/nas.t [new file with mode: 0644]
FS/t/option_Common.t [new file with mode: 0644]
FS/t/part_bill_event.t [new file with mode: 0644]
FS/t/part_event-Action.t [new file with mode: 0644]
FS/t/part_event-Condition.t [new file with mode: 0644]
FS/t/part_event.t [new file with mode: 0644]
FS/t/part_event_condition.t [new file with mode: 0644]
FS/t/part_event_condition_option.t [new file with mode: 0644]
FS/t/part_event_condition_option_option.t [new file with mode: 0644]
FS/t/part_event_option.t [new file with mode: 0644]
FS/t/part_export-acct_sql.t [new file with mode: 0644]
FS/t/part_export-apache.t [new file with mode: 0644]
FS/t/part_export-bind.t [new file with mode: 0644]
FS/t/part_export-bind_slave.t [new file with mode: 0644]
FS/t/part_export-bsdshell.t [new file with mode: 0644]
FS/t/part_export-communigate_pro.t [new file with mode: 0644]
FS/t/part_export-communigate_pro_singledomain.t [new file with mode: 0644]
FS/t/part_export-cp.t [new file with mode: 0644]
FS/t/part_export-cyrus.t [new file with mode: 0644]
FS/t/part_export-domain_shellcommands.t [new file with mode: 0644]
FS/t/part_export-forward_shellcommands.t [new file with mode: 0644]
FS/t/part_export-http.t [new file with mode: 0644]
FS/t/part_export-infostreet.t [new file with mode: 0644]
FS/t/part_export-ldap.t [new file with mode: 0644]
FS/t/part_export-null.t [new file with mode: 0644]
FS/t/part_export-passwdfile.t [new file with mode: 0644]
FS/t/part_export-postfix.t [new file with mode: 0644]
FS/t/part_export-radiator.t [new file with mode: 0644]
FS/t/part_export-router.t [new file with mode: 0644]
FS/t/part_export-shellcommands.t [new file with mode: 0644]
FS/t/part_export-shellcommands_withdomain.t [new file with mode: 0644]
FS/t/part_export-sqlmail.t [new file with mode: 0644]
FS/t/part_export-sqlradius.t [new file with mode: 0644]
FS/t/part_export-sqlradius_withdomain.t [new file with mode: 0644]
FS/t/part_export-sysvshell.t [new file with mode: 0644]
FS/t/part_export-textradius.t [new file with mode: 0644]
FS/t/part_export-vpopmail.t [new file with mode: 0644]
FS/t/part_export-www_shellcommands.t [new file with mode: 0644]
FS/t/part_export.t [new file with mode: 0644]
FS/t/part_export_option.t [new file with mode: 0644]
FS/t/part_pkg-flat.t [new file with mode: 0644]
FS/t/part_pkg-flat_comission.t [new file with mode: 0644]
FS/t/part_pkg-flat_comission_cust.t [new file with mode: 0644]
FS/t/part_pkg-flat_comission_pkg.t [new file with mode: 0644]
FS/t/part_pkg-flat_delayed.t [new file with mode: 0644]
FS/t/part_pkg-prorate.t [new file with mode: 0644]
FS/t/part_pkg-sesmon_hour.t [new file with mode: 0644]
FS/t/part_pkg-sesmon_minute.t [new file with mode: 0644]
FS/t/part_pkg-sql_external.t [new file with mode: 0644]
FS/t/part_pkg-sql_generic.t [new file with mode: 0644]
FS/t/part_pkg-sqlradacct_hour.t [new file with mode: 0644]
FS/t/part_pkg-subscription.t [new file with mode: 0644]
FS/t/part_pkg-voip_cdr.t [new file with mode: 0644]
FS/t/part_pkg-voip_sqlradacct.t [new file with mode: 0644]
FS/t/part_pkg.t [new file with mode: 0644]
FS/t/part_pkg_option.t [new file with mode: 0644]
FS/t/part_pkg_taxclass.t [new file with mode: 0644]
FS/t/part_pop_local.t [new file with mode: 0644]
FS/t/part_referral.t [new file with mode: 0644]
FS/t/part_svc.t [new file with mode: 0644]
FS/t/part_svc_column.t [new file with mode: 0644]
FS/t/pay_batch.t [new file with mode: 0644]
FS/t/payby.t [new file with mode: 0644]
FS/t/payinfo_Mixin.t [new file with mode: 0644]
FS/t/payment_gateway.t [new file with mode: 0644]
FS/t/payment_gateway_option.t [new file with mode: 0644]
FS/t/pkg_class.t [new file with mode: 0644]
FS/t/pkg_referral.t [new file with mode: 0644]
FS/t/pkg_svc.t [new file with mode: 0644]
FS/t/port.t [new file with mode: 0644]
FS/t/prepay_credit.t [new file with mode: 0644]
FS/t/queue.t [new file with mode: 0644]
FS/t/queue_arg.t [new file with mode: 0644]
FS/t/queue_depend.t [new file with mode: 0644]
FS/t/raddb.t [new file with mode: 0644]
FS/t/radius_usergroup.t [new file with mode: 0644]
FS/t/rate.t [new file with mode: 0644]
FS/t/rate_detail.t [new file with mode: 0644]
FS/t/rate_prefix.t [new file with mode: 0644]
FS/t/rate_region.t [new file with mode: 0644]
FS/t/reason.t [new file with mode: 0644]
FS/t/reason_type.t [new file with mode: 0644]
FS/t/reg_code.t [new file with mode: 0644]
FS/t/reg_code_pkg.t [new file with mode: 0644]
FS/t/registrar.t [new file with mode: 0644]
FS/t/session.t [new file with mode: 0644]
FS/t/svc_Common.t [new file with mode: 0644]
FS/t/svc_External_Common.t [new file with mode: 0644]
FS/t/svc_Parent_Mixin.t [new file with mode: 0644]
FS/t/svc_acct.t [new file with mode: 0644]
FS/t/svc_acct_pop.t [new file with mode: 0644]
FS/t/svc_broadband.t [new file with mode: 0644]
FS/t/svc_domain.t [new file with mode: 0644]
FS/t/svc_external.t [new file with mode: 0644]
FS/t/svc_forward.t [new file with mode: 0644]
FS/t/svc_phone.t [new file with mode: 0644]
FS/t/svc_www.t [new file with mode: 0644]
FS/t/type_pkgs.t [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
SCHEMA_CHANGE [new file with mode: 0644]
TODO [new file with mode: 0644]
bin/add-history-records.pl [new file with mode: 0755]
bin/all-postal-no-email [new file with mode: 0755]
bin/apache.export [new file with mode: 0755]
bin/artera.import [new file with mode: 0644]
bin/backup-dvd [new file with mode: 0644]
bin/bill-as-nextmonth [new file with mode: 0755]
bin/bill-as-nextmonth-BILL [new file with mode: 0755]
bin/bill-as-nextyear [new file with mode: 0755]
bin/bill-as-nextyear-BILL [new file with mode: 0755]
bin/bill-for-nextmonth [new file with mode: 0755]
bin/bill-for-nextyear [new file with mode: 0755]
bin/bill-nextmonth [new file with mode: 0755]
bin/bill-nextyear [new file with mode: 0755]
bin/billco-upload [new file with mode: 0644]
bin/bind.export [new file with mode: 0755]
bin/bind.import [new file with mode: 0755]
bin/breakdown-bill-applications [new file with mode: 0644]
bin/bsdshell.export [new file with mode: 0755]
bin/cdr_calltype.import [new file with mode: 0755]
bin/cdr_upstream_rate.import [new file with mode: 0755]
bin/create-fetchmailrc [new file with mode: 0644]
bin/customer-faker [new file with mode: 0755]
bin/expand-country [new file with mode: 0755]
bin/explain-ar-total.sql [new file with mode: 0644]
bin/find-overapplied [new file with mode: 0644]
bin/fix-sequences [new file with mode: 0755]
bin/freeside-init [new file with mode: 0755]
bin/freeside-migrate-events [new file with mode: 0644]
bin/freeside-session-kill [new file with mode: 0755]
bin/freeside-upgrade-unicode [new file with mode: 0755]
bin/freeside.import [new file with mode: 0644]
bin/fs-migrate-cust_tax_exempt [new file with mode: 0755]
bin/fs-migrate-part_svc [new file with mode: 0755]
bin/fs-migrate-payref [new file with mode: 0755]
bin/fs-migrate-svc_acct_sm [new file with mode: 0755]
bin/fs-radius-add-check [new file with mode: 0755]
bin/fs-radius-add-reply [new file with mode: 0755]
bin/generate-prepay [new file with mode: 0755]
bin/generate-raddb [new file with mode: 0755]
bin/generate-table-module [new file with mode: 0755]
bin/generate-tests [new file with mode: 0755]
bin/import-county-tax-rates [new file with mode: 0755]
bin/ispman.ldap.import [new file with mode: 0755]
bin/mapsecrets2access_user [new file with mode: 0755]
bin/masonize [new file with mode: 0755]
bin/passwd.import [new file with mode: 0755]
bin/payment-faker [new file with mode: 0755]
bin/pg-readonly [new file with mode: 0644]
bin/pg-version [new file with mode: 0755]
bin/pod2x [new file with mode: 0755]
bin/postfix.export [new file with mode: 0755]
bin/postfix_courierimap.import [new file with mode: 0755]
bin/print-schema [new file with mode: 0755]
bin/rate-us.import [new file with mode: 0755]
bin/rate.import [new file with mode: 0755]
bin/reset-cust_credit-otaker [new file with mode: 0755]
bin/rollback [new file with mode: 0755]
bin/rotate-cdrs [new file with mode: 0755]
bin/rt-drop-tables [new file with mode: 0755]
bin/rt-update-links [new file with mode: 0644]
bin/sendmail.import [new file with mode: 0644]
bin/sequences.reset [new file with mode: 0644]
bin/shadow.reimport [new file with mode: 0755]
bin/slony-setup [new file with mode: 0755]
bin/sqlradius-norealm.reimport [new file with mode: 0755]
bin/sqlradius.import [new file with mode: 0644]
bin/sqlradius.reimport [new file with mode: 0755]
bin/strip-eps [new file with mode: 0755]
bin/svc_acct.import [new file with mode: 0755]
bin/svc_acct_pop.import [new file with mode: 0755]
bin/svc_broadband.renumber [new file with mode: 0755]
bin/svc_domain.erase [new file with mode: 0755]
bin/sysvshell.export [new file with mode: 0755]
conf/agent_defaultpkg [new file with mode: 0644]
conf/alerter_template [new file with mode: 0644]
conf/blank_logo.eps [new file with mode: 0644]
conf/company_address [new file with mode: 0644]
conf/company_name [new file with mode: 0644]
conf/cust_pkg-change_svcpart [new file with mode: 0644]
conf/declinetemplate [new file with mode: 0644]
conf/home [new file with mode: 0644]
conf/impending_recur_template [new file with mode: 0644]
conf/invoice_from [new file with mode: 0644]
conf/invoice_html [new file with mode: 0644]
conf/invoice_html_statement [new file with mode: 0644]
conf/invoice_latex [new file with mode: 0644]
conf/invoice_latex.diff [new file with mode: 0644]
conf/invoice_latex_statement [new file with mode: 0644]
conf/invoice_latexfooter [new file with mode: 0644]
conf/invoice_latexnotes [new file with mode: 0644]
conf/invoice_latexnotes_statement [new file with mode: 0644]
conf/invoice_latexsmallfooter [new file with mode: 0644]
conf/invoice_template [new file with mode: 0644]
conf/invoice_template_statement [new file with mode: 0644]
conf/locale [new file with mode: 0644]
conf/logo.eps [new file with mode: 0644]
conf/logo.png [new file with mode: 0644]
conf/lpr [new file with mode: 0644]
conf/maxsearchrecordsperpage [new file with mode: 0644]
conf/payment_receipt_email [new file with mode: 0644]
conf/report_template [new file with mode: 0644]
conf/shells [new file with mode: 0644]
conf/show-msgcat-codes [new file with mode: 0644]
conf/smtpmachine [new file with mode: 0644]
conf/soadefaultttl [new file with mode: 0644]
conf/soaexpire [new file with mode: 0644]
conf/soarefresh [new file with mode: 0644]
conf/soaretry [new file with mode: 0644]
conf/ticket_system [new file with mode: 0644]
conf/welcome_letter [new file with mode: 0644]
debian/README.Debian [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/conffiles.ex [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/cron.d.ex [new file with mode: 0644]
debian/dirs [new file with mode: 0644]
debian/docs [new file with mode: 0644]
debian/ex.doc-base.package [new file with mode: 0644]
debian/freeside-doc.docs [new file with mode: 0644]
debian/freeside-doc.files [new file with mode: 0644]
debian/init.d.ex [new file with mode: 0644]
debian/manpage.1.ex [new file with mode: 0644]
debian/manpage.sgml.ex [new file with mode: 0644]
debian/menu.ex [new file with mode: 0644]
debian/postinst.ex [new file with mode: 0644]
debian/postrm.ex [new file with mode: 0644]
debian/preinst.ex [new file with mode: 0644]
debian/prerm.ex [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debian/watch.ex [new file with mode: 0644]
eg/TEMPLATE_cust_main.import [new file with mode: 0755]
eg/export_template.pm [new file with mode: 0644]
eg/part_event-Action-template.pm [new file with mode: 0644]
eg/part_event-Condition-template.pm [new file with mode: 0644]
eg/table_template-svc.pm [new file with mode: 0644]
eg/table_template.pm [new file with mode: 0644]
eg/xmlrpc-example.pl [new file with mode: 0755]
etc/abbr_state.txt [new file with mode: 0644]
etc/countries.txt [new file with mode: 0644]
etc/domain-template.txt [new file with mode: 0644]
etc/megapop.pl [new file with mode: 0755]
etc/sql-reserved-words.txt [new file with mode: 0644]
fs_passwd/fs_passwd [new file with mode: 0755]
fs_selfadmin/FS-MailAdminServer/MailAdminClient.pm [new file with mode: 0755]
fs_selfadmin/FS-MailAdminServer/cgi/mailadmin.cgi [new file with mode: 0755]
fs_selfadmin/FS-MailAdminServer/fs_mailadmind [new file with mode: 0755]
fs_selfadmin/README [new file with mode: 0644]
fs_selfadmin/fs_mailadmin_server [new file with mode: 0755]
fs_selfservice/DEPLOY [new file with mode: 0755]
fs_selfservice/FS-SelfService/Changes [new file with mode: 0644]
fs_selfservice/FS-SelfService/MANIFEST [new file with mode: 0644]
fs_selfservice/FS-SelfService/Makefile.PL [new file with mode: 0644]
fs_selfservice/FS-SelfService/SelfService.pm [new file with mode: 0644]
fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/ach_payment_results.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent.cgi [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_customer_menu.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_delete_svc.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_login.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_logout.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_main.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_menu.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_order_pkg.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_provision.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/bill.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/card.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/change_bill.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/change_password.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/change_pay.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/change_pkg.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/change_ship.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/check.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/contact.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/customer_change_pkg.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/cvv2.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/cvv2.png [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/cvv2_amex.png [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/decline.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/delete_svc.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/list_customers.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/login.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/logout.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/make_ach_payment.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/make_payment.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/map.gif [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/myaccount.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/myaccount_menu.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/order_pkg.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/passwd.cgi [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/passwd.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/payment_results.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_change_bill.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_change_password.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_change_pay.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_change_pkg.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_change_ship.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_order_pkg.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/process_order_recharge.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_svc_acct.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/process_svc_external.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/promocode.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/provision.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/provision_list.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/provision_svc_acct.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/recharge_prepay.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/recharge_results.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/regcode.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/selfservice.cgi [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/signup-agentselect.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/signup-alternate.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/signup-billaddress.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/signup-freeoption.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/signup-snarf.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/signup.cgi [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/signup.html [new file with mode: 0755]
fs_selfservice/FS-SelfService/cgi/stateselect.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/success-delayed.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/success.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/svc_acct.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/view_customer.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/view_invoice.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/view_support_details.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/view_usage.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/view_usage_details.html [new file with mode: 0644]
fs_selfservice/FS-SelfService/cgi/xmlrpc.cgi [new file with mode: 0644]
fs_selfservice/FS-SelfService/freeside-selfservice-clientd [new file with mode: 0644]
fs_selfservice/FS-SelfService/freeside-selfservice-xmlrpc-server [new file with mode: 0644]
fs_selfservice/FS-SelfService/ieak.template [new file with mode: 0755]
fs_selfservice/FS-SelfService/test.pl [new file with mode: 0644]
fs_selfservice/fs_passwd_test [new file with mode: 0755]
fs_selfservice/php/freeside.class.php [new file with mode: 0644]
fs_selfservice/php/freeside.login_example.php [new file with mode: 0644]
fs_selfservice/php/freeside_signup_example.php [new file with mode: 0644]
fs_sesmon/FS-SessionClient/Changes [new file with mode: 0644]
fs_sesmon/FS-SessionClient/MANIFEST [new file with mode: 0644]
fs_sesmon/FS-SessionClient/MANIFEST.SKIP [new file with mode: 0644]
fs_sesmon/FS-SessionClient/Makefile.PL [new file with mode: 0644]
fs_sesmon/FS-SessionClient/SessionClient.pm [new file with mode: 0644]
fs_sesmon/FS-SessionClient/bin/freeside-login [new file with mode: 0644]
fs_sesmon/FS-SessionClient/bin/freeside-logout [new file with mode: 0644]
fs_sesmon/FS-SessionClient/cgi/login.cgi [new file with mode: 0644]
fs_sesmon/FS-SessionClient/cgi/logout.cgi [new file with mode: 0644]
fs_sesmon/FS-SessionClient/fs_sessiond [new file with mode: 0644]
fs_sesmon/FS-SessionClient/test.pl [new file with mode: 0644]
fs_sesmon/fs_session_server [new file with mode: 0644]
htetc/freeside-base1.99.conf [new file with mode: 0644]
htetc/freeside-base1.conf [new file with mode: 0644]
htetc/freeside-base2.conf [new file with mode: 0644]
htetc/freeside-rt.conf [new file with mode: 0644]
htetc/handler.pl [new file with mode: 0644]
httemplate/.htaccess [new file with mode: 0755]
httemplate/autohandler [new file with mode: 0644]
httemplate/browse/access_group.html [new file with mode: 0644]
httemplate/browse/access_user.html [new file with mode: 0644]
httemplate/browse/addr_block.cgi [new file with mode: 0644]
httemplate/browse/agent.cgi [new file with mode: 0755]
httemplate/browse/agent_type.cgi [new file with mode: 0755]
httemplate/browse/cust_main_county.cgi [new file with mode: 0755]
httemplate/browse/elements/browse.html [new file with mode: 0644]
httemplate/browse/inventory_class.html [new file with mode: 0644]
httemplate/browse/invoice_template.html [new file with mode: 0644]
httemplate/browse/msgcat.cgi [new file with mode: 0755]
httemplate/browse/nas.cgi [new file with mode: 0755]
httemplate/browse/part_bill_event.cgi [new file with mode: 0755]
httemplate/browse/part_event.html [new file with mode: 0644]
httemplate/browse/part_export.cgi [new file with mode: 0755]
httemplate/browse/part_pkg.cgi [new file with mode: 0755]
httemplate/browse/part_referral.html [new file with mode: 0755]
httemplate/browse/part_svc.cgi [new file with mode: 0755]
httemplate/browse/part_virtual_field.cgi [new file with mode: 0644]
httemplate/browse/payment_gateway.html [new file with mode: 0644]
httemplate/browse/pkg_class.html [new file with mode: 0644]
httemplate/browse/rate.cgi [new file with mode: 0644]
httemplate/browse/rate_detail.html [new file with mode: 0644]
httemplate/browse/rate_region.html [new file with mode: 0644]
httemplate/browse/reason.html [new file with mode: 0644]
httemplate/browse/reason_type.html [new file with mode: 0644]
httemplate/browse/router.cgi [new file with mode: 0644]
httemplate/browse/svc_acct_pop.cgi [new file with mode: 0755]
httemplate/config/config-delete.cgi [new file with mode: 0644]
httemplate/config/config-download.cgi [new file with mode: 0644]
httemplate/config/config-process.cgi [new file with mode: 0644]
httemplate/config/config-view.cgi [new file with mode: 0644]
httemplate/config/config.cgi [new file with mode: 0644]
httemplate/docs/ach.html [new file with mode: 0644]
httemplate/docs/admin.html [new file with mode: 0755]
httemplate/docs/cvv2.html [new file with mode: 0644]
httemplate/docs/ieak.html [new file with mode: 0644]
httemplate/docs/index.html [new file with mode: 0644]
httemplate/docs/legacy.html [new file with mode: 0755]
httemplate/docs/man/FS/part_export/.cvs_is_on_crack [new file with mode: 0644]
httemplate/docs/overview-new.dia [new file with mode: 0644]
httemplate/docs/overview-new.png [new file with mode: 0644]
httemplate/docs/overview.dia [new file with mode: 0644]
httemplate/docs/overview.png [new file with mode: 0644]
httemplate/docs/passwd.html [new file with mode: 0755]
httemplate/docs/schema.dia [new file with mode: 0644]
httemplate/docs/schema.html [new file with mode: 0644]
httemplate/docs/schema.png [new file with mode: 0644]
httemplate/docs/session.html [new file with mode: 0644]
httemplate/docs/signup.html [new file with mode: 0644]
httemplate/docs/ssh.html [new file with mode: 0755]
httemplate/edit/REAL_cust_pkg.cgi [new file with mode: 0755]
httemplate/edit/access_group.html [new file with mode: 0644]
httemplate/edit/access_user.html [new file with mode: 0644]
httemplate/edit/agent.cgi [new file with mode: 0755]
httemplate/edit/agent_payment_gateway.html [new file with mode: 0644]
httemplate/edit/agent_type.cgi [new file with mode: 0755]
httemplate/edit/bulk-cust_svc.html [new file with mode: 0644]
httemplate/edit/cust_bill_pay.cgi [new file with mode: 0755]
httemplate/edit/cust_credit.cgi [new file with mode: 0755]
httemplate/edit/cust_credit_bill.cgi [new file with mode: 0755]
httemplate/edit/cust_main.cgi [new file with mode: 0755]
httemplate/edit/cust_main/billing.html [new file with mode: 0644]
httemplate/edit/cust_main/contact.html [new file with mode: 0644]
httemplate/edit/cust_main/select-country.html [new file with mode: 0644]
httemplate/edit/cust_main/select-county.html [new file with mode: 0644]
httemplate/edit/cust_main/select-domain.html [new file with mode: 0644]
httemplate/edit/cust_main/select-state.html [new file with mode: 0644]
httemplate/edit/cust_main_county-expand.cgi [new file with mode: 0755]
httemplate/edit/cust_main_county.html [new file with mode: 0644]
httemplate/edit/cust_main_note.cgi [new file with mode: 0755]
httemplate/edit/cust_pay.cgi [new file with mode: 0755]
httemplate/edit/cust_pkg.cgi [new file with mode: 0755]
httemplate/edit/cust_refund.cgi [new file with mode: 0755]
httemplate/edit/elements/edit.html [new file with mode: 0644]
httemplate/edit/elements/svc_Common.html [new file with mode: 0644]
httemplate/edit/inventory_class.html [new file with mode: 0644]
httemplate/edit/invoice_logo.html [new file with mode: 0644]
httemplate/edit/invoice_template.html [new file with mode: 0644]
httemplate/edit/msgcat.cgi [new file with mode: 0755]
httemplate/edit/part_bill_event.cgi [new file with mode: 0755]
httemplate/edit/part_event.html [new file with mode: 0644]
httemplate/edit/part_export.cgi [new file with mode: 0644]
httemplate/edit/part_pkg.cgi [new file with mode: 0755]
httemplate/edit/part_pkg_taxclass.html [new file with mode: 0644]
httemplate/edit/part_referral.html [new file with mode: 0755]
httemplate/edit/part_svc.cgi [new file with mode: 0755]
httemplate/edit/part_virtual_field.cgi [new file with mode: 0644]
httemplate/edit/payment_gateway.html [new file with mode: 0644]
httemplate/edit/pkg_class.html [new file with mode: 0644]
httemplate/edit/prepay_credit.cgi [new file with mode: 0644]
httemplate/edit/process/REAL_cust_pkg.cgi [new file with mode: 0755]
httemplate/edit/process/access_group.html [new file with mode: 0644]
httemplate/edit/process/access_user.html [new file with mode: 0644]
httemplate/edit/process/addr_block/add.cgi [new file with mode: 0755]
httemplate/edit/process/addr_block/allocate.cgi [new file with mode: 0755]
httemplate/edit/process/addr_block/deallocate.cgi [new file with mode: 0755]
httemplate/edit/process/addr_block/split.cgi [new file with mode: 0755]
httemplate/edit/process/agent.cgi [new file with mode: 0755]
httemplate/edit/process/agent_payment_gateway.html [new file with mode: 0644]
httemplate/edit/process/agent_type.cgi [new file with mode: 0755]
httemplate/edit/process/bulk-cust_svc.cgi [new file with mode: 0644]
httemplate/edit/process/cust_bill_pay.cgi [new file with mode: 0755]
httemplate/edit/process/cust_credit.cgi [new file with mode: 0755]
httemplate/edit/process/cust_credit_bill.cgi [new file with mode: 0755]
httemplate/edit/process/cust_main.cgi [new file with mode: 0755]
httemplate/edit/process/cust_main_county-collapse.cgi [new file with mode: 0755]
httemplate/edit/process/cust_main_county-expand.cgi [new file with mode: 0755]
httemplate/edit/process/cust_main_county.html [new file with mode: 0644]
httemplate/edit/process/cust_main_note.cgi [new file with mode: 0755]
httemplate/edit/process/cust_pay.cgi [new file with mode: 0755]
httemplate/edit/process/cust_pkg.cgi [new file with mode: 0755]
httemplate/edit/process/cust_refund.cgi [new file with mode: 0755]
httemplate/edit/process/cust_svc.cgi [new file with mode: 0644]
httemplate/edit/process/domain_record.cgi [new file with mode: 0755]
httemplate/edit/process/elements/process.html [new file with mode: 0644]
httemplate/edit/process/elements/svc_Common.html [new file with mode: 0644]
httemplate/edit/process/generic.cgi [new file with mode: 0644]
httemplate/edit/process/inventory_class.html [new file with mode: 0644]
httemplate/edit/process/invoice_logo.html [new file with mode: 0644]
httemplate/edit/process/invoice_template.html [new file with mode: 0644]
httemplate/edit/process/msgcat.cgi [new file with mode: 0644]
httemplate/edit/process/part_bill_event.cgi [new file with mode: 0755]
httemplate/edit/process/part_event.html [new file with mode: 0644]
httemplate/edit/process/part_export.cgi [new file with mode: 0644]
httemplate/edit/process/part_pkg.cgi [new file with mode: 0755]
httemplate/edit/process/part_pkg_taxclass.html [new file with mode: 0644]
httemplate/edit/process/part_referral.html [new file with mode: 0755]
httemplate/edit/process/part_svc.cgi [new file with mode: 0755]
httemplate/edit/process/payment_gateway.html [new file with mode: 0644]
httemplate/edit/process/pkg_class.html [new file with mode: 0644]
httemplate/edit/process/prepay_credit.cgi [new file with mode: 0644]
httemplate/edit/process/quick-charge.cgi [new file with mode: 0644]
httemplate/edit/process/quick-cust_pkg.cgi [new file with mode: 0644]
httemplate/edit/process/rate.cgi [new file with mode: 0755]
httemplate/edit/process/rate_detail.html [new file with mode: 0644]
httemplate/edit/process/rate_region.cgi [new file with mode: 0755]
httemplate/edit/process/reason.html [new file with mode: 0644]
httemplate/edit/process/reason_type.html [new file with mode: 0644]
httemplate/edit/process/reg_code.cgi [new file with mode: 0644]
httemplate/edit/process/router.cgi [new file with mode: 0644]
httemplate/edit/process/svc_Common.html [new file with mode: 0644]
httemplate/edit/process/svc_acct.cgi [new file with mode: 0755]
httemplate/edit/process/svc_acct_pop.cgi [new file with mode: 0755]
httemplate/edit/process/svc_broadband.cgi [new file with mode: 0644]
httemplate/edit/process/svc_domain.cgi [new file with mode: 0755]
httemplate/edit/process/svc_external.cgi [new file with mode: 0755]
httemplate/edit/process/svc_forward.cgi [new file with mode: 0755]
httemplate/edit/process/svc_phone.html [new file with mode: 0644]
httemplate/edit/process/svc_www.cgi [new file with mode: 0644]
httemplate/edit/quick-charge.html [new file with mode: 0644]
httemplate/edit/rate.cgi [new file with mode: 0644]
httemplate/edit/rate_detail.html [new file with mode: 0644]
httemplate/edit/rate_region.cgi [new file with mode: 0644]
httemplate/edit/reason.html [new file with mode: 0644]
httemplate/edit/reason_type.html [new file with mode: 0644]
httemplate/edit/reg_code.cgi [new file with mode: 0644]
httemplate/edit/router.cgi [new file with mode: 0755]
httemplate/edit/svc_Common.html [new file with mode: 0644]
httemplate/edit/svc_acct.cgi [new file with mode: 0755]
httemplate/edit/svc_acct_pop.cgi [new file with mode: 0755]
httemplate/edit/svc_broadband.cgi [new file with mode: 0644]
httemplate/edit/svc_domain.cgi [new file with mode: 0755]
httemplate/edit/svc_external.cgi [new file with mode: 0644]
httemplate/edit/svc_forward.cgi [new file with mode: 0755]
httemplate/edit/svc_phone.cgi [new file with mode: 0644]
httemplate/edit/svc_www.cgi [new file with mode: 0644]
httemplate/elements/calendar-en.js [new file with mode: 0644]
httemplate/elements/calendar-setup.js [new file with mode: 0644]
httemplate/elements/calendar-win2k-2.css [new file with mode: 0644]
httemplate/elements/calendar.js [new file with mode: 0644]
httemplate/elements/calendar_stripped.js [new file with mode: 0644]
httemplate/elements/checkboxes-table-name.html [new file with mode: 0644]
httemplate/elements/checkboxes-table.html [new file with mode: 0644]
httemplate/elements/cssexpr.js [new file with mode: 0644]
httemplate/elements/dashboard-toplist.html [new file with mode: 0644]
httemplate/elements/error.html [new file with mode: 0644]
httemplate/elements/errorpage.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/behaviors/disablehandles.htc [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/behaviors/showtableborders.htc [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/fck_editorarea.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/fck_internal.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/fck_showtableborders_gecko.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/images/fck_anchor.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/images/fck_flashlogo.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/images/fck_hiddenfield.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/css/images/fck_pagebreak.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/common/fck_dialog_common.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/common/fcknumericfield.htc [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/common/images/locked.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/common/images/reset.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/common/images/unlocked.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/common/moz-bindings.xml [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_about.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fckeditor.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fredck.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_anchor.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_button.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_checkbox.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_colorselector.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_docprops.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_docprops/fck_document_preview.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_find.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_flash.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_flash/fck_flash_preview.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_form.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_hiddenfield.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_image.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_image/fck_image_preview.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_link.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_link/fck_link.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_listprop.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_paste.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_radiobutton.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_replace.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_select.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_select/fck_select.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_smiley.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_source.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_specialchar.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/blank.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controlWindow.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/controls.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellChecker.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellchecker.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_spellerpages/spellerpages/wordWindow.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_table.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_tablecell.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_template.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_template/images/template1.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_template/images/template2.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_template/images/template3.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_textarea.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/dialog/fck_textfield.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/fckdebug.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/fckdialog.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/fckeditor.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/fckeditor.original.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/browser.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/basexml.pl [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/commands.pl [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/connector.cgi [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/io.pl [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/upload_fck.pl [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/connectors/perl/util.pl [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/frmactualfolder.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/frmcreatefolder.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/frmfolders.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourceslist.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/frmresourcetype.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/frmupload.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/ButtonArrow.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder32.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened32.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderUp.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ai.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/avi.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/bmp.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/cs.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/default.icon.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/dll.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/doc.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/exe.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/fla.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/gif.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/htm.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/html.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/jpg.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/js.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mdb.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mp3.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/pdf.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/png.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ppt.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/rdp.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swf.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swt.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/txt.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/vsd.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xls.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xml.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/zip.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ai.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/avi.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/bmp.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/cs.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/default.icon.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/dll.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/doc.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/exe.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/fla.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/gif.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/htm.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/html.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/jpg.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/js.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mdb.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mp3.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/pdf.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/png.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ppt.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/rdp.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swf.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swt.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/txt.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/vsd.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xls.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xml.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/zip.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/images/spacer.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/js/common.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/browser/default/js/fckxml.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/filemanager/upload/test.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/anchor.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/arrow_ltr.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/arrow_rtl.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/angel_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/angry_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/broken_heart.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/cake.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/confused_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/cry_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/devil_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/embaressed_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/envelope.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/heart.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/kiss.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/lightbulb.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/omg_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/regular_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/sad_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/shades_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/teeth_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_down.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_up.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/tounge_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/smiley/msn/wink_smile.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/images/spacer.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/js/fckeditorcode_gecko.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/js/fckeditorcode_ie.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/_getfontformat.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/_translationstatus.txt [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/af.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/ar.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/bg.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/bn.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/bs.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/ca.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/cs.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/da.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/de.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/el.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/en-au.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/en-ca.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/en-uk.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/en.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/eo.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/es.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/et.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/eu.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/fa.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/fi.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/fo.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/fr.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/gl.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/he.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/hi.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/hr.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/hu.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/it.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/ja.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/km.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/ko.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/lt.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/lv.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/mn.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/ms.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/nb.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/nl.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/no.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/pl.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/pt-br.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/pt.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/ro.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/ru.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/sk.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/sl.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/sr-latn.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/sr.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/sv.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/th.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/tr.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/uk.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/vi.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/zh-cn.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/lang/zh.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/autogrow/fckplugin.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/placeholder/fck_placeholder.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/placeholder/fckplugin.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/placeholder/lang/de.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/placeholder/lang/en.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/placeholder/lang/fr.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/placeholder/lang/it.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/placeholder/lang/pl.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/simplecommands/fckplugin.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/plugins/tablecommands/fckplugin.js [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/_fckviewstrips.html [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/fck_dialog.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/fck_editor.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/fck_strip.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/images/toolbar.arrowright.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/images/toolbar.buttonarrow.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/images/toolbar.collapse.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/images/toolbar.end.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/images/toolbar.expand.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/images/toolbar.separator.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/default/images/toolbar.start.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/fck_dialog.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/fck_editor.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/fck_strip.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.arrowright.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.bg.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.buttonarrow.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.collapse.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.end.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.expand.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.separator.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.start.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/fck_dialog.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/fck_editor.css [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/fck_strip.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.arrowright.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonarrow.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonbg.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.collapse.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.end.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.expand.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.separator.gif [new file with mode: 0644]
httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.start.gif [new file with mode: 0644]
httemplate/elements/fckeditor/fckconfig.js [new file with mode: 0644]
httemplate/elements/fckeditor/fckeditor.js [new file with mode: 0644]
httemplate/elements/fckeditor/fckpackager.xml [new file with mode: 0644]
httemplate/elements/fckeditor/fckstyles.xml [new file with mode: 0644]
httemplate/elements/fckeditor/fcktemplates.xml [new file with mode: 0644]
httemplate/elements/footer.html [new file with mode: 0644]
httemplate/elements/freeside.css [new file with mode: 0644]
httemplate/elements/header-popup.html [new file with mode: 0644]
httemplate/elements/header.html [new file with mode: 0644]
httemplate/elements/hidden.html [new file with mode: 0644]
httemplate/elements/iframecontentmws.js [new file with mode: 0644]
httemplate/elements/jsrsClient.js [new file with mode: 0644]
httemplate/elements/jsrsServer.html [new file with mode: 0644]
httemplate/elements/menu.html [new file with mode: 0644]
httemplate/elements/menubar.html [new file with mode: 0644]
httemplate/elements/overlibmws.js [new file with mode: 0644]
httemplate/elements/overlibmws_crossframe.js [new file with mode: 0644]
httemplate/elements/overlibmws_draggable.js [new file with mode: 0644]
httemplate/elements/overlibmws_iframe.js [new file with mode: 0644]
httemplate/elements/pager.html [new file with mode: 0644]
httemplate/elements/phonenumber.html [new file with mode: 0644]
httemplate/elements/progress-init.html [new file with mode: 0644]
httemplate/elements/progress-popup.html [new file with mode: 0644]
httemplate/elements/qlib/box.js [new file with mode: 0644]
httemplate/elements/qlib/boxctrl.js [new file with mode: 0644]
httemplate/elements/qlib/boxres.js [new file with mode: 0644]
httemplate/elements/qlib/button.js [new file with mode: 0644]
httemplate/elements/qlib/buttonres.js [new file with mode: 0644]
httemplate/elements/qlib/control.js [new file with mode: 0644]
httemplate/elements/qlib/counter.js [new file with mode: 0644]
httemplate/elements/qlib/imagelist.js [new file with mode: 0644]
httemplate/elements/qlib/label.js [new file with mode: 0644]
httemplate/elements/qlib/messagebox.js [new file with mode: 0644]
httemplate/elements/qlib/progress.js [new file with mode: 0644]
httemplate/elements/qlib/sound.js [new file with mode: 0644]
httemplate/elements/qlib/sprite.js [new file with mode: 0644]
httemplate/elements/qlib/window.js [new file with mode: 0644]
httemplate/elements/qlib/wndctrl.js [new file with mode: 0644]
httemplate/elements/search-cust_main.html [new file with mode: 0644]
httemplate/elements/select-access_group.html [new file with mode: 0644]
httemplate/elements/select-agent.html [new file with mode: 0644]
httemplate/elements/select-agent_type.html [new file with mode: 0644]
httemplate/elements/select-cust-fields.html [new file with mode: 0644]
httemplate/elements/select-cust-part_pkg.html [new file with mode: 0644]
httemplate/elements/select-cust_main-status.html [new file with mode: 0644]
httemplate/elements/select-cust_pkg-status.html [new file with mode: 0644]
httemplate/elements/select-month_year.html [new file with mode: 0644]
httemplate/elements/select-otaker.html [new file with mode: 0644]
httemplate/elements/select-part_referral.html [new file with mode: 0644]
httemplate/elements/select-payby.html [new file with mode: 0644]
httemplate/elements/select-pkg_class.html [new file with mode: 0644]
httemplate/elements/select-table.html [new file with mode: 0644]
httemplate/elements/select-taxclass.html [new file with mode: 0644]
httemplate/elements/selectlayers.html [new file with mode: 0644]
httemplate/elements/small_custview.html [new file with mode: 0644]
httemplate/elements/table-grid.html [new file with mode: 0644]
httemplate/elements/table.html [new file with mode: 0644]
httemplate/elements/tablebreak-tr-title.html [new file with mode: 0644]
httemplate/elements/tr-checkbox-multiple.html [new file with mode: 0644]
httemplate/elements/tr-checkbox.html [new file with mode: 0644]
httemplate/elements/tr-fixed-country.html [new file with mode: 0644]
httemplate/elements/tr-fixed-state.html [new file with mode: 0644]
httemplate/elements/tr-fixed.html [new file with mode: 0644]
httemplate/elements/tr-freq.html [new file with mode: 0644]
httemplate/elements/tr-input-beginning_ending.html [new file with mode: 0644]
httemplate/elements/tr-input-date-field.html [new file with mode: 0644]
httemplate/elements/tr-input-lessthan_greaterthan.html [new file with mode: 0644]
httemplate/elements/tr-input-money.html [new file with mode: 0644]
httemplate/elements/tr-input-percentage.html [new file with mode: 0644]
httemplate/elements/tr-input-text.html [new file with mode: 0644]
httemplate/elements/tr-password.html [new file with mode: 0644]
httemplate/elements/tr-select-access_group.html [new file with mode: 0644]
httemplate/elements/tr-select-agent.html [new file with mode: 0644]
httemplate/elements/tr-select-agent_type.html [new file with mode: 0644]
httemplate/elements/tr-select-cust-fields.html [new file with mode: 0644]
httemplate/elements/tr-select-cust_main-status.html [new file with mode: 0644]
httemplate/elements/tr-select-cust_pkg-status.html [new file with mode: 0644]
httemplate/elements/tr-select-from_to.html [new file with mode: 0644]
httemplate/elements/tr-select-invoice_template.html [new file with mode: 0644]
httemplate/elements/tr-select-otaker.html [new file with mode: 0644]
httemplate/elements/tr-select-part_pkg.html [new file with mode: 0644]
httemplate/elements/tr-select-part_referral.html [new file with mode: 0644]
httemplate/elements/tr-select-part_svc.html [new file with mode: 0644]
httemplate/elements/tr-select-payby.html [new file with mode: 0644]
httemplate/elements/tr-select-pkg_class.html [new file with mode: 0644]
httemplate/elements/tr-select-reason.html [new file with mode: 0755]
httemplate/elements/tr-select-taxclass.html [new file with mode: 0644]
httemplate/elements/tr-select.html [new file with mode: 0644]
httemplate/elements/tr-selectlayers.html [new file with mode: 0644]
httemplate/elements/tr-selectmultiple-part_pkg.html [new file with mode: 0644]
httemplate/elements/tr-td-label.html [new file with mode: 0644]
httemplate/elements/tr-title.html [new file with mode: 0644]
httemplate/elements/xmenu.css [new file with mode: 0644]
httemplate/elements/xmenu.js [new file with mode: 0644]
httemplate/elements/xmenu.top.css [new file with mode: 0644]
httemplate/elements/xmenu.top.js [new file with mode: 0644]
httemplate/elements/xmlhttp.html [new file with mode: 0644]
httemplate/graph/cust_bill_pkg.cgi [new file with mode: 0644]
httemplate/graph/cust_pkg.cgi [new file with mode: 0644]
httemplate/graph/elements/monthly.html [new file with mode: 0644]
httemplate/graph/money_time.cgi [new file with mode: 0644]
httemplate/graph/report_cust_bill_pkg.html [new file with mode: 0644]
httemplate/graph/report_cust_pkg.html [new file with mode: 0644]
httemplate/graph/report_money_time.html [new file with mode: 0644]
httemplate/images/32clear.gif [new file with mode: 0644]
httemplate/images/ach.png [new file with mode: 0644]
httemplate/images/arrow.down.png [new file with mode: 0644]
httemplate/images/arrow.right.black.png [new file with mode: 0644]
httemplate/images/arrow.right.png [new file with mode: 0644]
httemplate/images/background-cheat.png [new file with mode: 0644]
httemplate/images/black-gradient.png [new file with mode: 0644]
httemplate/images/black-gray-corner.png [new file with mode: 0644]
httemplate/images/black-gray-gradient.png [new file with mode: 0644]
httemplate/images/black-gray-side.png [new file with mode: 0644]
httemplate/images/black-gray-top.png [new file with mode: 0644]
httemplate/images/calendar-disabled.png [new file with mode: 0644]
httemplate/images/calendar.png [new file with mode: 0644]
httemplate/images/cvv2.png [new file with mode: 0644]
httemplate/images/cvv2_amex.png [new file with mode: 0644]
httemplate/images/menu-left-example.png [new file with mode: 0644]
httemplate/images/menu-top-example.png [new file with mode: 0644]
httemplate/images/progressbar-empty.png [new file with mode: 0644]
httemplate/images/progressbar-full.png [new file with mode: 0644]
httemplate/images/red_telephone_mimooh_01.png [new file with mode: 0644]
httemplate/images/small-logo.png [new file with mode: 0644]
httemplate/index.html [new file with mode: 0644]
httemplate/misc/batch-cust_pay.html [new file with mode: 0644]
httemplate/misc/bill.cgi [new file with mode: 0755]
httemplate/misc/bulk_change_pkg.cgi [new file with mode: 0755]
httemplate/misc/cancel-unaudited.cgi [new file with mode: 0755]
httemplate/misc/cancel_cust.html [new file with mode: 0644]
httemplate/misc/cancel_pkg.html [new file with mode: 0755]
httemplate/misc/catchall.cgi [new file with mode: 0755]
httemplate/misc/cdr-import.html [new file with mode: 0644]
httemplate/misc/change_pkg.cgi [new file with mode: 0755]
httemplate/misc/counties.cgi [new file with mode: 0644]
httemplate/misc/cust_main-cancel.cgi [new file with mode: 0755]
httemplate/misc/cust_main-import.cgi [new file with mode: 0644]
httemplate/misc/cust_main-import_charges.cgi [new file with mode: 0644]
httemplate/misc/cust_main_note-import.cgi [new file with mode: 0644]
httemplate/misc/cust_main_note-import.html [new file with mode: 0644]
httemplate/misc/cust_pay-import.cgi [new file with mode: 0644]
httemplate/misc/delete-agent_payment_gateway.cgi [new file with mode: 0644]
httemplate/misc/delete-cust_credit.cgi [new file with mode: 0755]
httemplate/misc/delete-cust_pay.cgi [new file with mode: 0755]
httemplate/misc/delete-cust_refund.cgi [new file with mode: 0755]
httemplate/misc/delete-customer.cgi [new file with mode: 0755]
httemplate/misc/delete-domain_record.cgi [new file with mode: 0755]
httemplate/misc/delete-part_export.cgi [new file with mode: 0755]
httemplate/misc/disable-payment_gateway.cgi [new file with mode: 0644]
httemplate/misc/download-batch.cgi [new file with mode: 0644]
httemplate/misc/dump.cgi [new file with mode: 0644]
httemplate/misc/elements/customer-table.html [new file with mode: 0644]
httemplate/misc/email-invoice.cgi [new file with mode: 0755]
httemplate/misc/email_events.cgi [new file with mode: 0644]
httemplate/misc/email_invoice_events.cgi [new file with mode: 0644]
httemplate/misc/email_invoices.cgi [new file with mode: 0644]
httemplate/misc/fax-invoice.cgi [new file with mode: 0755]
httemplate/misc/fax_events.cgi [new file with mode: 0644]
httemplate/misc/fax_invoice_events.cgi [new file with mode: 0644]
httemplate/misc/fax_invoices.cgi [new file with mode: 0644]
httemplate/misc/inventory_item-import.html [new file with mode: 0644]
httemplate/misc/link.cgi [new file with mode: 0755]
httemplate/misc/meta-import.cgi [new file with mode: 0644]
httemplate/misc/order_pkg.html [new file with mode: 0644]
httemplate/misc/payment.cgi [new file with mode: 0644]
httemplate/misc/print-invoice.cgi [new file with mode: 0755]
httemplate/misc/print_events.cgi [new file with mode: 0644]
httemplate/misc/print_invoice_events.cgi [new file with mode: 0644]
httemplate/misc/print_invoices.cgi [new file with mode: 0644]
httemplate/misc/process/batch-cust_pay.cgi [new file with mode: 0644]
httemplate/misc/process/bulk_change_pkg.cgi [new file with mode: 0755]
httemplate/misc/process/cancel_pkg.html [new file with mode: 0755]
httemplate/misc/process/catchall.cgi [new file with mode: 0755]
httemplate/misc/process/cdr-import.html [new file with mode: 0644]
httemplate/misc/process/cust_main-import.cgi [new file with mode: 0644]
httemplate/misc/process/cust_main-import_charges.cgi [new file with mode: 0644]
httemplate/misc/process/cust_main_note-import.cgi [new file with mode: 0644]
httemplate/misc/process/cust_pay-import.cgi [new file with mode: 0644]
httemplate/misc/process/delete-customer.cgi [new file with mode: 0755]
httemplate/misc/process/inventory_item-import.html [new file with mode: 0644]
httemplate/misc/process/link.cgi [new file with mode: 0755]
httemplate/misc/process/meta-import.cgi [new file with mode: 0644]
httemplate/misc/process/payment.cgi [new file with mode: 0644]
httemplate/misc/process/recharge_svc.html [new file with mode: 0755]
httemplate/misc/process/timeworked.html [new file with mode: 0644]
httemplate/misc/queue.cgi [new file with mode: 0644]
httemplate/misc/recharge_svc.html [new file with mode: 0755]
httemplate/misc/states.cgi [new file with mode: 0644]
httemplate/misc/svc_acct-domains.cgi [new file with mode: 0644]
httemplate/misc/timeworked.html [new file with mode: 0755]
httemplate/misc/unapply-cust_credit.cgi [new file with mode: 0755]
httemplate/misc/unapply-cust_pay.cgi [new file with mode: 0755]
httemplate/misc/unprovision.cgi [new file with mode: 0755]
httemplate/misc/unsusp_pkg.cgi [new file with mode: 0755]
httemplate/misc/unvoid-cust_pay_void.cgi [new file with mode: 0755]
httemplate/misc/upload-batch.cgi [new file with mode: 0644]
httemplate/misc/void-cust_pay.cgi [new file with mode: 0755]
httemplate/misc/whois.cgi [new file with mode: 0644]
httemplate/misc/xmlhttp-cust_main-search.cgi [new file with mode: 0644]
httemplate/misc/xmlrpc.cgi [new file with mode: 0644]
httemplate/pref/pref-process.html [new file with mode: 0644]
httemplate/pref/pref.html [new file with mode: 0644]
httemplate/search/cdr.html [new file with mode: 0644]
httemplate/search/cust_bill.html [new file with mode: 0755]
httemplate/search/cust_bill_event.cgi [new file with mode: 0644]
httemplate/search/cust_bill_event.html [new file with mode: 0755]
httemplate/search/cust_bill_pkg.cgi [new file with mode: 0644]
httemplate/search/cust_credit.html [new file with mode: 0755]
httemplate/search/cust_event.html [new file with mode: 0644]
httemplate/search/cust_main-otaker.cgi [new file with mode: 0755]
httemplate/search/cust_main-zip.html [new file with mode: 0644]
httemplate/search/cust_main.cgi [new file with mode: 0755]
httemplate/search/cust_main.html [new file with mode: 0755]
httemplate/search/cust_pay.cgi [new file with mode: 0755]
httemplate/search/cust_pay_batch.cgi [new file with mode: 0755]
httemplate/search/cust_pkg.cgi [new file with mode: 0755]
httemplate/search/cust_svc.html [new file with mode: 0644]
httemplate/search/cust_tax_exempt_pkg.cgi [new file with mode: 0644]
httemplate/search/elements/search.html [new file with mode: 0644]
httemplate/search/inventory_item.html [new file with mode: 0644]
httemplate/search/pay_batch.cgi [new file with mode: 0755]
httemplate/search/pay_batch.html [new file with mode: 0644]
httemplate/search/prepay_credit.html [new file with mode: 0644]
httemplate/search/queue.html [new file with mode: 0644]
httemplate/search/reg_code.html [new file with mode: 0644]
httemplate/search/report_cdr.html [new file with mode: 0644]
httemplate/search/report_cust_bill.html [new file with mode: 0644]
httemplate/search/report_cust_credit.html [new file with mode: 0644]
httemplate/search/report_cust_event.html [new file with mode: 0644]
httemplate/search/report_cust_main-zip.html [new file with mode: 0644]
httemplate/search/report_cust_main.html [new file with mode: 0755]
httemplate/search/report_cust_pay.html [new file with mode: 0644]
httemplate/search/report_cust_pay_batch.html [new file with mode: 0644]
httemplate/search/report_cust_pkg.html [new file with mode: 0755]
httemplate/search/report_prepaid_income.cgi [new file with mode: 0644]
httemplate/search/report_prepaid_income.html [new file with mode: 0644]
httemplate/search/report_receivables.cgi [new file with mode: 0755]
httemplate/search/report_receivables.html [new file with mode: 0755]
httemplate/search/report_rt_transaction.html [new file with mode: 0644]
httemplate/search/report_svc_acct.html [new file with mode: 0755]
httemplate/search/report_tax.cgi [new file with mode: 0755]
httemplate/search/report_tax.html [new file with mode: 0755]
httemplate/search/rt_transaction.html [new file with mode: 0644]
httemplate/search/sql.html [new file with mode: 0644]
httemplate/search/sqlradius.cgi [new file with mode: 0644]
httemplate/search/sqlradius.html [new file with mode: 0644]
httemplate/search/svc_acct.cgi [new file with mode: 0755]
httemplate/search/svc_broadband.cgi [new file with mode: 0755]
httemplate/search/svc_domain.cgi [new file with mode: 0755]
httemplate/search/svc_external.cgi [new file with mode: 0755]
httemplate/search/svc_forward.cgi [new file with mode: 0755]
httemplate/search/svc_phone.cgi [new file with mode: 0644]
httemplate/search/svc_www.cgi [new file with mode: 0755]
httemplate/search/timeworked.html [new file with mode: 0644]
httemplate/view/cust_bill-logo.cgi [new file with mode: 0755]
httemplate/view/cust_bill-pdf.cgi [new file with mode: 0755]
httemplate/view/cust_bill-ps.cgi [new file with mode: 0755]
httemplate/view/cust_bill.cgi [new file with mode: 0755]
httemplate/view/cust_main.cgi [new file with mode: 0755]
httemplate/view/cust_main/billing.html [new file with mode: 0644]
httemplate/view/cust_main/contacts.html [new file with mode: 0644]
httemplate/view/cust_main/misc.html [new file with mode: 0644]
httemplate/view/cust_main/notes.html [new file with mode: 0755]
httemplate/view/cust_main/packages.html [new file with mode: 0755]
httemplate/view/cust_main/payment_history.html [new file with mode: 0644]
httemplate/view/cust_main/tickets.html [new file with mode: 0644]
httemplate/view/cust_pay.html [new file with mode: 0644]
httemplate/view/elements/svc_Common.html [new file with mode: 0644]
httemplate/view/logo.cgi [new file with mode: 0644]
httemplate/view/svc_Common.html [new file with mode: 0644]
httemplate/view/svc_acct.cgi [new file with mode: 0755]
httemplate/view/svc_broadband.cgi [new file with mode: 0644]
httemplate/view/svc_domain.cgi [new file with mode: 0755]
httemplate/view/svc_external.cgi [new file with mode: 0644]
httemplate/view/svc_forward.cgi [new file with mode: 0755]
httemplate/view/svc_phone.cgi [new file with mode: 0644]
httemplate/view/svc_www.cgi [new file with mode: 0644]
init.d/freeside-init [new file with mode: 0644]
rpm/INSTALL [new file with mode: 0644]
rpm/freeside.spec [new file with mode: 0644]
rpm/freeside.sysconfig [new file with mode: 0644]
rpm/rpm2Bundle [new file with mode: 0755]
rt/FREESIDE_MODIFIED [new file with mode: 0644]
rt/HOWTO/README [deleted file]
rt/HOWTO/change.txt [deleted file]
rt/HOWTO/release.txt [deleted file]
rt/HOWTO/version-control.txt [deleted file]
rt/Makefile
rt/README
rt/bin/mason_handler.fcgi
rt/bin/mason_handler.scgi
rt/bin/mason_handler.svc
rt/bin/rt [deleted file]
rt/bin/rt-commit-handler
rt/bin/rt-commit-handler.in [deleted file]
rt/bin/rt-crontool
rt/bin/rt-mailgate
rt/bin/webmux.pl [deleted file]
rt/config [deleted file]
rt/config.layout [deleted file]
rt/config.layout.in [new file with mode: 0644]
rt/config.log
rt/config.pld [deleted file]
rt/config.status
rt/etc/RT_Config.pm
rt/etc/RT_Config.pm.in
rt/etc/RT_SiteConfig.pm
rt/etc/acl.Oracle
rt/etc/acl.Pg
rt/etc/acl.mysql
rt/etc/schema.Oracle [deleted file]
rt/etc/schema.mysql
rt/etc/upgrade/2.1.71 [deleted file]
rt/html/Admin/Elements/ModifyQueue [deleted file]
rt/html/Admin/Elements/ModifyUser [deleted file]
rt/html/Admin/Global/CustomField.html [deleted file]
rt/html/Admin/Global/CustomFields.html [deleted file]
rt/html/Admin/Users/Prefs.html [deleted file]
rt/html/Callbacks/ActivityReports/Elements/Tabs/Default [new file with mode: 0644]
rt/html/Callbacks/ActivityReports/NoAuth/webrt.css/Default [new file with mode: 0644]
rt/html/Callbacks/ActivityReports/Search/Results.html/SearchActions [new file with mode: 0644]
rt/html/Callbacks/RT-WebCronTool/Elements/Tabs/Default [new file with mode: 0644]
rt/html/Callbacks/kStatistics/Elements/Tabs/Default [new file with mode: 0644]
rt/html/Developer/CronTool/autohandler [new file with mode: 0644]
rt/html/Developer/CronTool/index.html [new file with mode: 0644]
rt/html/Elements/CollectionAsTable/Row
rt/html/Elements/Footer
rt/html/Elements/FreesideInvoiceSearch [new file with mode: 0644]
rt/html/Elements/FreesideNewCust [new file with mode: 0644]
rt/html/Elements/FreesideSearch [new file with mode: 0644]
rt/html/Elements/FreesideSvcSearch [new file with mode: 0644]
rt/html/Elements/Header
rt/html/Elements/PageLayout
rt/html/Elements/QuickCreate
rt/html/Elements/ShadedBox [deleted file]
rt/html/Elements/ShadedInputRow [deleted file]
rt/html/Elements/ShadedRow [deleted file]
rt/html/Elements/SimpleSearch
rt/html/Elements/Tabs
rt/html/Elements/TicketList
rt/html/Elements/ViewUser [deleted file]
rt/html/NoAuth/css/3.5-default/freeside.css [new file with mode: 0644]
rt/html/NoAuth/css/3.5-default/main.css
rt/html/NoAuth/css/3.5-default/misc.css
rt/html/NoAuth/css/3.5-default/transactions.css
rt/html/NoAuth/images/back_home.gif [deleted file]
rt/html/NoAuth/images/css/cb.gif
rt/html/NoAuth/images/css/cbr.gif
rt/html/NoAuth/images/css/ct.gif
rt/html/NoAuth/images/css/ctr.gif
rt/html/NoAuth/images/head_requestracker.gif [deleted file]
rt/html/NoAuth/images/rt.jpg [deleted file]
rt/html/NoAuth/images/small-logo.png [new file with mode: 0644]
rt/html/NoAuth/images/space.gif [deleted file]
rt/html/NoAuth/images/spacer.gif [deleted file]
rt/html/NoAuth/images/squares_blue.gif [deleted file]
rt/html/NoAuth/printrt.css [deleted file]
rt/html/NoAuth/webrt.css [deleted file]
rt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/CallsMultiQueue/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/CallsQueueDay/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/CallsQueueDay/Results.tsv [new file with mode: 0644]
rt/html/RTx/Statistics/CallsQueueDay/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/DayOfWeek/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/DayOfWeek/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/DurationAsString [new file with mode: 0755]
rt/html/RTx/Statistics/Elements/CollectionAsTable/Header [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/CollectionAsTable/ParseFormat [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/CollectionAsTable/Row [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/ControlsAsTable/ControlBox [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/ControlsAsTable/UpdatePage [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/DateSelectRow [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/DurationAsString [new file with mode: 0755]
rt/html/RTx/Statistics/Elements/GraphBox [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/SelectMultiQueue [new file with mode: 0755]
rt/html/RTx/Statistics/Elements/StatColumnMap [new file with mode: 0644]
rt/html/RTx/Statistics/Elements/Tabs [new file with mode: 0755]
rt/html/RTx/Statistics/FAQ/index.html [new file with mode: 0644]
rt/html/RTx/Statistics/OpenStalled/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/OpenStalled/Results.tsv [new file with mode: 0644]
rt/html/RTx/Statistics/OpenStalled/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/Resolution/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/Resolution/index.html [new file with mode: 0644]
rt/html/RTx/Statistics/TimeToResolve/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/TimeToResolve/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/UserTest/Elements/Chart [new file with mode: 0755]
rt/html/RTx/Statistics/UserTest/index.html [new file with mode: 0755]
rt/html/RTx/Statistics/index.html [new file with mode: 0755]
rt/html/Reports/Activity/ActivityDetail.html [new file with mode: 0644]
rt/html/Reports/Activity/ActivitySummary.html [new file with mode: 0644]
rt/html/Reports/Activity/Elements/LimitReport [new file with mode: 0644]
rt/html/Reports/Activity/Elements/MiniPlot [new file with mode: 0644]
rt/html/Reports/Activity/Elements/PrintFooter [new file with mode: 0644]
rt/html/Reports/Activity/Elements/PrintHeader [new file with mode: 0644]
rt/html/Reports/Activity/Elements/ScreenFooter [new file with mode: 0644]
rt/html/Reports/Activity/Elements/ScreenHeader [new file with mode: 0644]
rt/html/Reports/Activity/Elements/Tabs [new file with mode: 0644]
rt/html/Reports/Activity/Elements/Wrapper [new file with mode: 0644]
rt/html/Reports/Activity/ResolutionComments.html [new file with mode: 0644]
rt/html/Reports/Activity/ResolutionStatistics.html [new file with mode: 0644]
rt/html/Reports/Activity/index.html [new file with mode: 0644]
rt/html/Search/Elements/PickRestriction [deleted file]
rt/html/Search/Elements/TicketHeader [deleted file]
rt/html/Search/Elements/TicketHeaderCell [deleted file]
rt/html/Search/Elements/TicketRow [deleted file]
rt/html/Search/Listing.html [deleted file]
rt/html/Ticket/Elements/AddCustomers [new file with mode: 0644]
rt/html/Ticket/Elements/EditCustomers [new file with mode: 0644]
rt/html/Ticket/Elements/EditLinks [deleted file]
rt/html/Ticket/Elements/ShowCustomers [new file with mode: 0644]
rt/html/Ticket/Elements/ShowLink [deleted file]
rt/html/Ticket/Elements/ShowLinks [deleted file]
rt/html/Ticket/Elements/ShowMemberOf [deleted file]
rt/html/Ticket/Elements/ShowReferences [deleted file]
rt/html/Ticket/Elements/ShowSummary
rt/html/Ticket/Elements/ShowTransactionAttachments
rt/html/Ticket/Elements/Tabs
rt/html/Ticket/ModifyCustomers.html [new file with mode: 0644]
rt/html/Widgets/TitleBoxStart
rt/lib/RT.pm
rt/lib/RT/ACE.pm
rt/lib/RT/ACL.pm
rt/lib/RT/Action/Autoreply.pm
rt/lib/RT/Action/Generic.pm
rt/lib/RT/Action/Notify.pm
rt/lib/RT/Action/NotifyAsComment.pm
rt/lib/RT/Action/ResolveMembers.pm
rt/lib/RT/Action/SendEmail.pm
rt/lib/RT/Attachment.pm
rt/lib/RT/Attachments.pm
rt/lib/RT/Condition/AnyTransaction.pm
rt/lib/RT/Condition/Generic.pm
rt/lib/RT/Condition/StatusChange.pm
rt/lib/RT/CurrentUser.pm
rt/lib/RT/Date.pm
rt/lib/RT/Extension/ActivityReports.pm [new file with mode: 0644]
rt/lib/RT/Group.pm
rt/lib/RT/GroupMember.pm
rt/lib/RT/GroupMembers.pm
rt/lib/RT/Groups.pm
rt/lib/RT/Handle.pm
rt/lib/RT/I18N/en_malkovich.po [deleted file]
rt/lib/RT/Interface/CLI.pm
rt/lib/RT/Interface/Email.pm
rt/lib/RT/Interface/Web.pm
rt/lib/RT/Interface/Web_Vendor.pm [new file with mode: 0644]
rt/lib/RT/Link.pm
rt/lib/RT/Links.pm
rt/lib/RT/Queue.pm
rt/lib/RT/Queues.pm
rt/lib/RT/Record.pm
rt/lib/RT/Scrip.pm
rt/lib/RT/ScripAction.pm
rt/lib/RT/ScripActions.pm
rt/lib/RT/ScripCondition.pm
rt/lib/RT/ScripConditions.pm
rt/lib/RT/Scrips.pm
rt/lib/RT/SearchBuilder.pm
rt/lib/RT/Template.pm
rt/lib/RT/Templates.pm
rt/lib/RT/Ticket.pm
rt/lib/RT/TicketCustomFieldValue.pm [deleted file]
rt/lib/RT/TicketCustomFieldValue_Overlay.pm [deleted file]
rt/lib/RT/TicketCustomFieldValues.pm [deleted file]
rt/lib/RT/TicketCustomFieldValues_Overlay.pm [deleted file]
rt/lib/RT/Tickets.pm
rt/lib/RT/Transaction.pm
rt/lib/RT/Transactions.pm
rt/lib/RT/URI/freeside.pm [new file with mode: 0644]
rt/lib/RT/URI/freeside/Internal.pm [new file with mode: 0644]
rt/lib/RT/URI/freeside/XMLRPC.pm [new file with mode: 0644]
rt/lib/RT/User.pm
rt/lib/RT/Users.pm
rt/lib/RTx/Statistics.pm [new file with mode: 0755]
rt/lib/RTx/WebCronTool.pm [new file with mode: 0644]
rt/lib/t/00smoke.t.in [deleted file]
rt/lib/t/01harness.t.in [deleted file]
rt/lib/t/02regression.t
rt/lib/t/02regression.t.in [deleted file]
rt/lib/t/03web.pl
rt/lib/t/03web.pl.in [deleted file]
rt/lib/t/04_send_email.pl
rt/lib/t/04_send_email.pl.in [deleted file]
rt/lib/t/05cronsupport.pl.in [deleted file]
rt/lib/t/regression/00placeholder [deleted file]
rt/sbin/rt-setup-database [deleted file]
rt/sbin/rt-setup-database.in
rt/sbin/rt-test-dependencies [deleted file]
test/cgi-test [new file with mode: 0755]
test/dup-test [new file with mode: 0755]

diff --git a/AGPL b/AGPL
new file mode 100644 (file)
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 (file)
index 0000000..15f1aa9
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,178 @@
+Thanks to Matt Simerson <matt@michweb.net> of MichWeb Inc. for documentation
+and pre-release testing.  Without his help the documentation in 1.0.0
+release would have consisted of a single screenfull of text.
+(To clear up some misunderstanding, Matt did not write the current
+documentation.)
+
+Steve Cleff <cleff@yahoo.com> did the default background image in 1.0.x and
+is also the creator of Freeside's elusive mascot, Snakeman, who we hope will
+make an appearance in an upcoming version.
+
+Jerry St. Pierre <jstpi@city.timmins.on.ca> did the "SISD" graphic used in
+1.0.x and most of 1.1.x.
+
+Mark Norris of Urban Design, Inc. <http://www.urban.com/> did the red "S"
+logo for later 1.1.x versions until 1.4.1
+
+Brian McCane? <bmccane@maxbaud.net> contributed PostgreSQL support, HTML
+style enhancements and many, many bugfixes.
+
+Cerkit <cerkit@alfheim.net> contributed rsync support and desynced hosts.
+His changes will hopefully be included in an upcoming version.
+
+CompleteHOST, Inc. (http://www.completehost.com) funded the development of the
+following features:
+  - Multiple, separate databases and configurations on one box.
+  - Per-customer pricing (custom packages)
+  - Internationalization wrt addresses (cust_main, cust_main_county)
+Thanks!
+
+Mark Williamson <mark.williamson@ebbs.com.au> and Roger Mangraviti
+<rem@atu.com.au> contributed state/provence listings for Australia.
+
+Peter Wemm <peter@netplex.com.au> sent in a bunch of bugfixes for the 1.2
+release.
+
+Greg Kuhnert <gregk@no1.com.au> sent some documentation updates.
+
+Joel Griffiths <griff@aver-computer.com> contribued many bugfixes as well as
+the print-batch script.
+
+NetLoud <http://www.netloud.com/> funded the development of the following
+features:
+  - IEAK support for the signup server
+  - Pre-payment support
+
+NetAcces.Net (not netaccess.net) funded the development of the following
+features:
+  - DNS tracking and export to BIND configuration files
+  - Web site virtual host tracking and export to Apache configuration files
+
+Kristian Hoffmann <khoff@pc-intouch.com> contributed Netscape CCK
+autoconfiguration support for the signup server, lots of great mailing
+lists posts which I shamelessly made into documentation, fixes to get rid of
+the embarassing and non-database-normal "owed" field, and many other things
+I'm forgetting.  Most recently Kristian and Mark (last name?) contributed
+the IP address tracking and svc_broadband in 1.5.
+
+Jeff Finucane <jeff@cmh.net> send in a bunch of bugfixes (for the sendmail
+export, cancel-unaudited.cgi), patches to support billing date modification,
+and probably other things too (sorry if I forgot them).  And yet even more
+bug squashing, thanks!  *and* he single-handedly implemented all the necessary
+work to get rid of svc_acct_sm and the "default domain"  thanks!!  and rewrote
+the financials!  wow, thanks jeff!  and contributed financial reports!
+
+Kenny Elliott <kenny@neoserve.com> contributed ICRADIUS radreply table support,
+allowing attributes with ICRADIUS, helped fix many bugs, and some
+other stuff I can't recall (sorry).
+
+Stephen Amadei <amadei@dandy.net> contribued portability cleanups for the
+low-level DBI stuff.
+
+Jason Spence <thalakan@frys.com> contributed admin.html and other
+documentation, autocapnames javascript, bugfixes & other neat stuff I can't
+remember.
+
+Brad Dameron <bdameron@tscnet.com> contributed code to do configurable state
+and referral defaults.
+
+Surf and Sip, Inc., <http://www.surfandsip.com> sponsored a long-requested
+feature - the session monitor and time-based prepaid cards.
+Matt Peterson <matt@peterson.org> and Mack ? <mackn@mackn.net> tested
+the new features and contributed many bugfixes.
+
+Landel Telecom <http://www.landel.com/> sponsored shipping addresses and
+customer notes, as well as an update of the CP provisioning.
+
+nikotel, Inc. <http://www.nikojet.com> sponsored the inclusion of
+customer-to-customer referrals in the web interface and signup server.
+
+Three Bubba's Innanet <http://www.inna.net> sponsored expedited check entry,
+the "similar names warning" feature, and a number of other enhancements.
+
+Dave Burgess <burgess@neonramp.com> sent in a bunch of fixes and small changes
+and will doubtless send more once he's got his tree under control.
+
+Luke Pfeifer <freeside@globalli.com> contributed the "subscription" price plan.
+
+Noment Networks, LLC <http://www.noment.com/> sponsored ICRADIUS/FreeRADIUS
+groups, message catalogs, and signup server enhancements.
+
+Donald Greer <dgreer@austintx.com> provided the SQL to work around MySQL's lack
+of subqueries, and Dale Hege <fhege@lumenexus.net> provided the patches.
+Thanks!
+
+<baloo@gimpgirl.com> sent in several documentation patches.
+
+"Stephen Bechard" <steve@destek.net> sent in patches for svc_www services and
+other fixes.
+
+Charles A Beasley <cbeasley@noment.net> contributed quota editing for the
+Infostreet export.
+
+Richard Siddall <richard.siddall@elirion.net> sent in Mason fixes, fixed lots
+of typos, mod_perl 2.0 work, RPM packaging and other things I'm probably
+forgetting.
+
+Contains "JS Calendar" <http://dynarch.com/mishoo/calendar.epl>
+by Mihai Bazon <mishoo@infoiasi.ro> licensed under the terms of the GNU LGPL.
+
+Latex invoice template based on a template from eBills
+<http://ebills.sourceforge.net/> by Mark Asplen-Taylor <mark@asplen.co.uk>,
+licensed under the terms fo the GNU GPL.
+
+Contains "Request Tracker" <http://www.bestpractical.com/rt/> and 
+"RTx::Extension::ActivityReports" from Best Practical Solutions, licensed under
+the terms of the GNU GPL.
+
+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).
+
+Contains "RTx::WebCronTool" <http://search.cpan.org/dist/RTx-WebCronTool/> from
+Audrey Tang, licensed under the same terms as Perl (GPL/Artistic).
+
+#not yet used...
+# Contains "SQL Ledger" <http://www.sql-ledger.com/> by DWS Systems Inc. and
+# contributors licensed under the terms of the GNU GPL.
+
+Peter Bowen <pbowen@aboutws.com> started the difficult modular price plans
+changes, added credit card encryption features, and other things I've
+probably overlooked.
+
+Rebecca Cardennis <http://www.shinza.org/> created the great new logo first
+released with 1.4.2beta1 and 1.5.0pre6.
+
+Troy Hammonds <troyh@netsignia.net> sent in RADIUS session history viewing,
+many bugfixes and other things I'm probably forgetting.
+
+Contains the QLIB JavaScript library <http://qlib.quazzle.com/> by 
+Quazzle.com, Serge Dolgov, licensed under the terms of the GNU GPL.
+
+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>.
+
+Ricardo SIGNES <rjbs+freeside-devel@icgroup.com> has contributed a bunch of
+patches to clean up and refactor various stuff in the module layer.  Thanks!
+
+XMLHttpRequest implementation based on the SAJAX toolkit, licensed under the
+terms of the BSD license.
+(c) copyright 2005 modernmethod, inc
+Perl backend version (c) copyright 2005 Nathan Schmidt
+
+Scott Edwards <supadupa@gmail.com> contributed magic for XMLHTTP error
+handling, and other patches.
+
+Contains XMenu <http://webfx.eae.net/dhtml/xmenu/xmenu.html>
+by Erik Arvidsson, licensed under the terms of the GNU GPL.
+
+Contains public domain artwork from openclipart.org by mimooh and other
+authors.
+
+Contains FCKeditor by Frederico Caldeira Knabben, licensed under the terms of
+the GNU GPL.
+
+Everything else is my (Ivan Kohler <ivan-freeside_credits@420.am>) fault.
+
diff --git a/FS/Changes b/FS/Changes
new file mode 100644 (file)
index 0000000..c94ef10
--- /dev/null
@@ -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 (file)
index 0000000..6fa6faa
--- /dev/null
+++ b/FS/FS.pm
@@ -0,0 +1,428 @@
+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
+
+=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::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_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::svc_forward> - Mail forwarding class
+
+L<FS::svc_www> - Web virtual host class.
+
+L<FS::svc_broadband> - DSL, wireless and other broadband class.
+
+L<FS::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::cdr> - Call Detail Record class
+
+L<FS::cdr_calltype> - CDR calltype class
+
+L<FS::cdr_carrier> - CDR carrier class
+
+L<FS::cdr_upstream_rate> - CDR upstream rate 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_class> - Package class class
+
+L<FS::part_pkg> - Package definition class
+
+L<FS::part_pkg_taxclass> - Tax class class
+
+L<FS::part_pkg_option> - Package definition option 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::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::reason_type> - Reason type class
+
+L<FS::reason> - Reason class
+
+L<FS::cust_pkg_reason> - Package reason class
+
+L<FS::cust_main> - Customer 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_main_note> - Customer note class
+
+L<FS::banned_pay> - Banned payment information class
+
+L<FS::cust_bill> - Invoice class
+
+L<FS::cust_bill_pkg> - Invoice line item class
+
+L<FS::cust_bill_pkg_detail> - Invoice line item detail class
+
+L<FS::part_bill_event> - (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_phone> - Historical phone number 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 (file)
index 0000000..13dbd7f
--- /dev/null
@@ -0,0 +1,295 @@
+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',
+  
+  ###
+  # basic customer rights
+  ###
+  'Customer rights' => [
+    'New customer',
+    'View customer',
+    #'View Customer | View tickets',
+    'Edit customer',
+    '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
+    'Add customer note', #NEW
+    'Edit customer note', #NEW
+    'Bill customer now', #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',
+    'Customize customer package',
+    'Suspend customer package',
+    'Suspend customer package later',
+    'Unsuspend customer package',
+    'Cancel customer package immediately',
+    'Cancel customer package later',
+    'Add on-the-fly cancel reason', #NEW
+    'Add on-the-fly suspend reason', #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
+  
+    { 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
+    'View customer tax exemptions', #yow
+    'View customer batched 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',
+    'Refund payment',
+  
+    { 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.
+    '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 
+    
+  
+  ],
+  
+  ###
+  # report/listing rights...
+  ###
+  'Reprting/listing rights' => [
+    'List customers',
+    'List zip codes', #NEW
+    'List invoices',
+    'List packages',
+    'List services',
+  
+    { rightname=> 'List rating data', desc=>'Usage reports', global=>1 },
+    'Billing event reports',
+    '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=>'Import', global=>1 }, #some of these are ag-virt'ed now?  give em their own ACLs
+    { rightname=>'Export', 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 },
+  
+    { rightname=>'Configuration', global=>1 }, #most of the rest of the configuraiton is not agent-virtualized
+  ],
+  
+;
+  
+=head1 CLASS METHODS
+  
+=over 4
+  
+=item rights
+
+Returns a list of right names.
+
+=cut
+  
+  sub rights {
+  #my $class = shift;
+  map { ref($_) ? $_->{'rightname'} : $_ } map @{ $rights{$_} }, keys %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 (file)
index 0000000..38a869c
--- /dev/null
@@ -0,0 +1,425 @@
+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
+                small_custview myexit http_header);
+
+=head1 NAME
+
+FS::CGI - Subroutines for the web interface
+
+=head1 SYNOPSIS
+
+  use FS::CGI qw(header menubar idiot eidiot popurl);
+
+  print header( 'Title', '' );
+  print header( 'Title', menubar('item', 'URL', ... ) );
+
+  idiot "error message"; 
+  eidiot "error message";
+
+  $url = popurl; #returns current url
+  $url = popurl(3); #three levels up
+
+=head1 DESCRIPTION
+
+Provides a few common subroutines for the web interface.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item header TITLE, MENUBAR
+
+Returns an HTML header.
+
+=cut
+
+sub header {
+  use Carp;
+  carp 'FS::CGI::header deprecated; include /elements/header.html instead';
+
+  my($title,$menubar,$etc)=@_; #$etc is for things like onLoad= etc.
+  $etc = '' unless defined $etc;
+
+  my $x =  <<END;
+    <HTML>
+      <HEAD>
+        <TITLE>
+          $title
+        </TITLE>
+        <META HTTP-Equiv="Cache-Control" Content="no-cache">
+        <META HTTP-Equiv="Pragma" Content="no-cache">
+        <META HTTP-Equiv="Expires" Content="0"> 
+      </HEAD>
+      <BODY BGCOLOR="#e8e8e8"$etc>
+          <FONT SIZE=6>
+            <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
+
+Returns current URL with LEVEL levels of path removed from the end (default 0).
+
+=cut
+
+sub popurl {
+  my($up)=@_;
+  my $cgi = &FS::UID::cgi;
+  my $url_string = $cgi->isa('Apache') ? $cgi->uri : $cgi->url;
+  $url_string =~ s/\?.*//;
+  my $url = new URI::URL ( $url_string );
+  my(@path)=$url->path_components;
+  splice @path, 0-$up;
+  $url->path_components(@path);
+  my $x = $url->as_string;
+  $x .= '/' unless $x =~ /\/$/;
+  $x;
+}
+
+=item 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">';
+  }
+
+}
+
+=item small_custview CUSTNUM || CUST_MAIN_OBJECT, COUNTRYDEFAULT, NOBALANCE_FLAG, URL
+
+Sheesh. I should just switch to Mason.
+
+=cut
+
+sub small_custview {
+  use FS::Record qw(qsearchs);
+  use FS::cust_main;
+
+  my $arg = shift;
+  my $countrydefault = shift || 'US';
+  my $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->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 ) {
+    use FS::Msgcat;
+    $html .= ( FS::Msgcat::_gettext('daytime') || 'Day' ).
+             ' '. $cust_main->daytime.
+             '<BR>'. ( FS::Msgcat::_gettext('night') || 'Night' ).
+             ' '. $cust_main->night;
+  } elsif ( $cust_main->daytime || $cust_main->night ) {
+    $html .= $cust_main->daytime || $cust_main->night;
+  }
+  if ( $cust_main->fax ) {
+    $html .= '<BR>Fax '. $cust_main->fax;
+  }
+
+  $html .= '</TD></TR></TABLE></TD>';
+
+  if ( defined $cust_main->dbdef_table->column('ship_last') ) {
+
+    my $pre = $cust_main->ship_last ? 'ship_' : '';
+
+    $html .= '<TD 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}ship_zip"). '<BR>';
+    $html .= $cust_main->get("${pre}country"). '<BR>'
+      if $cust_main->get("${pre}country")
+         && $cust_main->get("${pre}country") ne $countrydefault;
+
+    $html .= '</TD></TR><TR><TD></TD><TD BGCOLOR="#ffffff">';
+
+    if ( $cust_main->get("${pre}daytime") && $cust_main->get("${pre}night") ) {
+      use FS::Msgcat;
+      $html .= ( FS::Msgcat::_gettext('daytime') || 'Day' ).
+               ' '. $cust_main->get("${pre}daytime").
+               '<BR>'. ( FS::Msgcat::_gettext('night') || 'Night' ).
+               ' '. $cust_main->get("${pre}night");
+    } elsif ( $cust_main->get("${pre}daytime")
+              || $cust_main->get("${pre}night") ) {
+      $html .= $cust_main->get("${pre}daytime")
+               || $cust_main->get("${pre}night");
+    }
+    if ( $cust_main->get("${pre}fax") ) {
+      $html .= '<BR>Fax '. $cust_main->get("${pre}fax");
+    }
+
+    $html .= '</TD></TR></TABLE></TD>';
+  }
+
+  $html .= '</TR></TABLE>';
+
+  $html .= '<BR>Balance: <B>$'. $cust_main->balance. '</B><BR>'
+    unless $nobalance;
+
+  # last payment might be good here too?
+
+  $html;
+}
+
+=back
+
+=head1 BUGS
+
+Not OO.
+
+Not complete.
+
+small_custview sooooo doesn't belong here.  i should just switch to Mason.
+
+=head1 SEE ALSO
+
+L<CGI>, L<CGI::Base>
+
+=cut
+
+1;
+
+
diff --git a/FS/FS/ClientAPI.pm b/FS/FS/ClientAPI.pm
new file mode 100644 (file)
index 0000000..902f58b
--- /dev/null
@@ -0,0 +1,37 @@
+package FS::ClientAPI;
+
+use strict;
+use vars qw(%handler $domain $DEBUG);
+
+$DEBUG = 0;
+
+%handler = ();
+
+#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 (file)
index 0000000..daede59
--- /dev/null
@@ -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/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
new file mode 100644 (file)
index 0000000..2d39510
--- /dev/null
@@ -0,0 +1,1291 @@
+package FS::ClientAPI::MyAccount;
+
+use strict;
+use vars qw($cache);
+use subs qw(_cache);
+use Digest::MD5 qw(md5_hex);
+use Date::Format;
+use Business::CreditCard;
+use Time::Duration;
+use FS::CGI qw(small_custview); #doh
+use FS::UI::Web;
+use FS::UI::bytecount;
+use FS::Conf;
+use FS::Record qw(qsearch qsearchs);
+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_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;
+
+#false laziness with FS::cust_main
+BEGIN {
+  eval "use Time::Local;";
+  die "Time::Local minimum version 1.05 required with Perl versions before 5.6"
+    if $] < 5.006 && !defined($Time::Local::VERSION);
+  eval "use Time::Local qw(timelocal_nocheck);";
+}
+
+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
+);
+
+use subs qw(_provision);
+
+sub _cache {
+  $cache ||= new FS::ClientAPI_SessionCache( {
+               'namespace' => 'FS::ClientAPI::MyAccount',
+             } );
+}
+
+#false laziness w/FS::ClientAPI::passwd::passwd
+sub login {
+  my $p = shift;
+
+  my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } )
+    or return { error => 'Domain '. $p->{'domain'}. ' not found' };
+
+  my $svc_acct = qsearchs( 'svc_acct', { 'username'  => $p->{'username'},
+                                         'domsvc'    => $svc_domain->svcnum, }
+                         );
+  return { error => 'User not found.' } unless $svc_acct;
+
+  my $conf = new FS::Conf;
+  my $pkg_svc = $svc_acct->cust_svc->pkg_svc;
+  return { error => 'Only primary user may log in.' } 
+    if $conf->exists('selfservice_server-primary_only')
+       && ( ! $pkg_svc || $pkg_svc->primary_svc ne 'Y' );
+
+  return { error => 'Incorrect password.' }
+    unless $svc_acct->check_password($p->{'password'});
+
+  my $session = {
+    'svcnum' => $svc_acct->svcnum,
+  };
+
+  my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
+  if ( $cust_pkg ) {
+    my $cust_main = $cust_pkg->cust_main;
+    $session->{'custnum'} = $cust_main->custnum;
+  }
+
+  my $session_id;
+  do {
+    $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
+  } until ( ! defined _cache->get($session_id) ); #just in case
+
+  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 { 'error' => '' };
+  } else {
+    return { 'error' => "Can't resume session" }; #better error message
+  }
+}
+
+sub customer_info {
+  my $p = shift;
+
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  return { 'error' => $session } if $context eq 'error';
+
+  my %return;
+
+  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" };
+
+    $return{balance} = $cust_main->balance;
+
+    $return{tickets} = [ ($cust_main->tickets) ];
+
+    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, $conf->config('countrydefault') );
+
+    $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;
+
+    if ( $payinfo eq $cust_main->paymask ) {
+      $new->payinfo($cust_main->payinfo);
+    } else {
+      $new->payinfo($payinfo);
+    }
+
+    $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
+
+  }elsif ( $payby =~ /^(BILL)$/ ) {
+  } 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'
+                   } );
+
+    $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') ],
+
+      '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'),
+    };
+
+  }
+
+  ##
+  #customer-specific
+  ##
+
+  my %return = %$payment_info;
+
+  my $custnum = $session->{'custnum'};
+
+  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  $return{balance} = $cust_main->balance;
+
+  $return{payname} = $cust_main->payname
+                     || ( $cust_main->first. ' '. $cust_main->get('last') );
+
+  $return{$_} = $cust_main->get($_) for qw(address1 address2 city state zip);
+
+  $return{payby} = $cust_main->payby;
+  $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->{'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'} =~ /^([A-Z]{4})$/
+    or return { 'error' => "illegal_payby " . $p->{'payby'} };
+  my $payby = $1;
+
+  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'};
+    $payinfo =~ s/[^\dx]//g;
+    $payinfo =~ /^(\d{13,16})$/
+      or return { 'error' => gettext('invalid_card') }; # . ": ". $self->payinfo
+    $payinfo = $1;
+
+    $payinfo = $cust_main->payinfo
+      if $cust_main->paymask eq $payinfo;
+
+    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 address1 address2 city state zip payip ) ],
+    'CHEK' => [ qw( ss paytype paystate stateid stateid_state payip ) ],
+  );
+
+  my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $p->{'amount'},
+    'quiet'    => 1,
+    'payinfo'  => $payinfo,
+    'paydate'  => $p->{'year'}. '-'. $p->{'month'}. '-01',
+    'payname'  => $payname,
+    'paybatch' => $paybatch,
+    'paycvv'   => $paycvv,
+    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 payinfo );
+      $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( 'payinfo' => $payinfo );
+      $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
+    }
+    $new->set( 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01' );
+    my $error = $new->replace($cust_main);
+    return { 'error' => $error } if $error;
+    $cust_main = $new;
+  }
+
+  return { 'error' => '' };
+
+}
+
+sub process_payment_order_pkg {
+  my $p = shift;
+
+  my $hr = process_payment($p);
+  return $hr if $hr->{'error'};
+
+  order_pkg($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,
+         };
+
+}
+
+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 $templatename = $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),
+           '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 ) {
+    push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context
+  }
+  @cust_svc = grep { $_->part_svc->svcdb eq $p->{'svcdb'} } @cust_svc
+    if $p->{'svcdb'};
+
+  #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username }
+  #              @svc_x;
+
+  { 
+    #no#'svcnum'   => $session->{'svcnum'},
+    'custnum'  => $custnum,
+    'svcs'     => [ map { 
+                          my $svc_x = $_->svc_x;
+                          my($label, $value) = $_->label;
+                          my $part_pkg = $svc_x->cust_svc->cust_pkg->part_pkg;
+
+                          { 'svcnum'    => $_->svcnum,
+                            'label'     => $label,
+                            'value'     => $value,
+                            'username'  => $svc_x->username,
+                            'email'     => $svc_x->email,
+                            'seconds'   => $svc_x->seconds,
+                            'upbytes'   => FS::UI::bytecount::display_bytecount($svc_x->upbytes),
+                            'downbytes' => FS::UI::bytecount::display_bytecount($svc_x->downbytes),
+                            'totalbytes'=> FS::UI::bytecount::display_bytecount($svc_x->totalbytes),
+                            'recharge_amount' => $part_pkg->option('recharge_amount', 1),
+                            'recharge_seconds' => $part_pkg->option('recharge_seconds', 1),
+                            'recharge_upbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_upbytes', 1)),
+                            'recharge_downbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_downbytes', 1)),
+                            'recharge_totalbytes' => FS::UI::bytecount::display_bytecount($part_pkg->option('recharge_totalbytes', 1)),
+                            # more...
+                          };
+                        }
+                        @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 _usage_details {
+  my ($callback, $p) = (shift,shift);
+
+  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 $svc_acct = qsearchs ( 'svc_acct', $search );
+  return { 'error' => 'No service selected in list_svc_usage' } 
+    unless $svc_acct;
+
+  my $freq   = $svc_acct->cust_svc->cust_pkg->part_pkg->freq;
+  my $start  = $svc_acct->cust_svc->cust_pkg->setup;
+  #my $end    = $svc_acct->cust_svc->cust_pkg->bill; # or time?
+  my $end    = time;
+
+  unless($p->{beginning}){
+    $p->{beginning} = $svc_acct->cust_svc->cust_pkg->last_bill;
+    $p->{ending} = $end;
+  }
+
+  my (@usage) = &$callback($svc_acct,$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,
+    '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_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 cancel_pkg {
+  my $p = shift;
+  my $session = _cache->get($p->{'session_id'})
+    or return { 'error' => "Can't resume session" }; #better error message
+
+  my $custnum = $session->{'custnum'};
+
+  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  my $pkgnum = $p->{'pkgnum'};
+
+  my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
+                                        'pkgnum'  => $pkgnum,   } )
+    or return { 'error' => "unknown pkgnum $pkgnum" };
+
+  my $error = $cust_pkg->cancel( 'quiet'=>1 );
+  return { 'error' => $error };
+
+}
+
+sub provision_acct {
+  my $p = shift;
+
+  return { 'error' => gettext('passwords_dont_match') }
+    if $p->{'_password'} ne $p->{'_password2'};
+  return { 'error' => gettext('empty_password') }
+    unless length($p->{'_password'});
+  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'}});
+  }
+
+  _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);
+
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  return { 'error' => $session } if $context eq 'error';
+
+  my $search = { 'custnum' => $custnum };
+  $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+  my $cust_main = qsearchs('cust_main', $search )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  my $pkgnum = $p->{'pkgnum'};
+
+  my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
+                                        'pkgnum'  => $pkgnum,
+                                                               } )
+    or return { 'error' => "unknown pkgnum $pkgnum" };
+
+  my $part_svc = qsearchs('part_svc', { 'svcpart' => $p->{'svcpart'} } )
+    or return { 'error' => "unknown svcpart $p->{'svcpart'}" };
+
+  my $svc_x = $class->new( {
+    'pkgnum'  => $p->{'pkgnum'},
+    'svcpart' => $p->{'svcpart'},
+    map { $_ => $p->{$_} } @$fields
+  } );
+  my $error = $svc_x->insert;
+  $svc_x = qsearchs($svc_x->table, { 'svcnum' => $svc_x->svcnum })
+    unless $error;
+
+  return { 'svc'   => $part_svc->svc,
+           'error' => $error,
+           map { $_ => $svc_x->get($_) } @$return_fields
+         };
+
+}
+
+sub part_svc_info {
+  my $p = shift;
+
+  my($context, $session, $custnum) = _custoragent_session_custnum($p);
+  return { 'error' => $session } if $context eq 'error';
+
+  my $search = { 'custnum' => $custnum };
+  $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent';
+  my $cust_main = qsearchs('cust_main', $search )
+    or return { 'error' => "unknown custnum $custnum" };
+
+  my $pkgnum = $p->{'pkgnum'};
+
+  my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
+                                        'pkgnum'  => $pkgnum,
+                                                               } )
+    or return { 'error' => "unknown pkgnum $pkgnum" };
+
+  my $svcpart = $p->{'svcpart'};
+
+  my $pkg_svc = qsearchs('pkg_svc', { 'pkgpart' => $cust_pkg->pkgpart,
+                                      'svcpart' => $svcpart,           } )
+    or return { 'error' => "unknown svcpart $svcpart for pkgnum $pkgnum" };
+  my $part_svc = $pkg_svc->part_svc;
+
+  my $conf = new FS::Conf;
+
+  return {
+    'svc'     => $part_svc->svc,
+    'svcdb'   => $part_svc->svcdb,
+    'pkgnum'  => $pkgnum,
+    'svcpart' => $svcpart,
+    'custnum' => $custnum,
+
+    'security_phrase' => 0, #XXX !
+    'svc_acct_pop'    => [], #XXX !
+    'popnum'          => '',
+    'init_popstate'   => '',
+    'popac'           => '',
+    'acstate'         => '',
+
+    'small_custview' =>
+      small_custview( $cust_main, $conf->config('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 _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/Signup.pm b/FS/FS/ClientAPI/Signup.pm
new file mode 100644 (file)
index 0000000..61325b9
--- /dev/null
@@ -0,0 +1,514 @@
+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::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::acct_snarf;
+use FS::queue;
+use FS::reg_code;
+
+$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 $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 $href = $_->pkgpart_hashref;
+          $_->agentnum =>
+            [
+              map { { 'payby'       => [ $_->payby ],
+                      'freq_pretty' => $_->freq_pretty,
+                      'options'     => { $_->options },
+                      %{$_->hashref}
+                  } }
+                grep { $_->svcpart('svc_acct') && $href->{ $_->pkgpart } }
+                  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;
+
+    $signup_info_cache = {
+      'cust_main_county' => [ map $_->hashref,
+                                  qsearch('cust_main_county', {} )
+                            ],
+
+      'agent' => [ map $_->hashref,
+                       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'),
+
+      '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')),
+
+    };
+
+    $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_acct') }
+          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_acct') }
+            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 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;
+
+  }
+  # 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;
+  
+  #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
+    )
+
+  } );
+
+  return { 'error' => "Illegal payment type" }
+    unless grep { $_ eq $packet->{'payby'} }
+                $conf->config('signup_server-payby');
+
+  $cust_main->payinfo($cust_main->daytime)
+    if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
+
+  my @invoicing_list = $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_acct');
+
+  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;
+
+  my $svc_acct = new FS::svc_acct ( {
+    'svcpart'   => $svcpart,
+    map { $_ => $packet->{$_} }
+      qw( username _password sec_phrase popnum ),
+  } );
+
+  my @acct_snarf;
+  my $snarfnum = 1;
+  while (    exists($packet->{"snarf_machine$snarfnum"})
+          && length($packet->{"snarf_machine$snarfnum"}) ) {
+    my $acct_snarf = new FS::acct_snarf ( {
+      'machine'   => $packet->{"snarf_machine$snarfnum"},
+      'protocol'  => $packet->{"snarf_protocol$snarfnum"},
+      'username'  => $packet->{"snarf_username$snarfnum"},
+      '_password' => $packet->{"snarf_password$snarfnum"},
+    } );
+    $snarfnum++;
+    push @acct_snarf, $acct_snarf;
+  }
+  $svc_acct->child_objects( \@acct_snarf );
+
+  my $y = $svc_acct->setdefault; # arguably should be in new method
+  return { 'error' => $y } if $y && !ref($y);
+
+  #$error = $svc_acct->check;
+  #return { 'error' => $error } if $error;
+
+  #setup a job dependancy to delay provisioning
+  my $placeholder = new FS::queue ( {
+    'job'    => 'FS::ClientAPI::Signup::__placeholder',
+    'status' => 'locked',
+  } );
+  my $error = $placeholder->insert;
+  return { 'error' => $error } if $error;
+
+  use Tie::RefHash;
+  tie my %hash, 'Tie::RefHash';
+  %hash = ( $cust_pkg => [ $svc_acct ] );
+  #msgcat
+  $error = $cust_main->insert(
+    \%hash,
+    \@invoicing_list,
+    'depend_jobnum' => $placeholder->jobnum,
+  );
+  if ( $error ) {
+    my $perror = $placeholder->delete;
+    $error .= " (Additionally, error removing placeholder: $perror)" if $perror;
+    return { 'error' => $error };
+  }
+
+  if ( $conf->exists('signup_server-realtime') ) {
+
+    #warn "[fs_signup_server] Billing customer...\n" if $Debug;
+
+    my $bill_error = $cust_main->bill;
+    #warn "[fs_signup_server] error billing new customer: $bill_error"
+    #  if $bill_error;
+
+    $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;
+
+    $bill_error = $cust_main->collect('realtime' => 1);
+    #warn "[fs_signup_server] error collecting from new customer: $bill_error"
+    #  if $bill_error;
+
+    if ( $cust_main->balance > 0 ) {
+
+      #this makes sense.  credit is "un-doing" the invoice
+      $cust_main->credit( $cust_main->balance, 'signup server decline',
+                          '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;
+
+  return { error => '' };
+
+}
+
+1;
diff --git a/FS/FS/ClientAPI/passwd.pm b/FS/FS/ClientAPI/passwd.pm
new file mode 100644 (file)
index 0000000..b22d761
--- /dev/null
@@ -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 (file)
index 0000000..bfab805
--- /dev/null
@@ -0,0 +1,78 @@
+package FS::ClientAPI_SessionCache;
+
+use strict;
+use vars qw($module);
+use FS::UID qw(datasrc);
+
+#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 (file)
index 0000000..a763ed4
--- /dev/null
@@ -0,0 +1,2206 @@
+package FS::Conf;
+
+use vars qw($base_dir @config_items @base_items @card_types $DEBUG);
+use IO::File;
+use File::Basename;
+use MIME::Base64;
+use FS::ConfItem;
+use FS::ConfDefaults;
+use FS::Conf_compat17;
+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 config KEY [ AGENTNUM ]
+
+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.
+
+=cut
+
+sub _usecompat {
+  my ($self, $method) = (shift, shift);
+  warn "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)=@_;
+  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 (!$cv && defined($agentnum)) {
+    $hashref->{agentnum} = '';
+    $cv = FS::Record::qsearchs('conf', $hashref);
+  }
+  return $cv;
+}
+
+sub config {
+  my $self = shift;
+  return $self->_usecompat('config', @_) if use_confcompat;
+
+  my($name,$agentnum)=@_;
+  my $cv = $self->_config($name, $agentnum) 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 ]
+
+Returns the exact scalar value for key.
+
+=cut
+
+sub config_binary {
+  my $self = shift;
+  return $self->_usecompat('config_binary', @_) if use_confcompat;
+
+  my($name,$agentnum)=@_;
+  my $cv = $self->_config($name, $agentnum) or return;
+  decode_base64($cv->value);
+}
+
+=item exists KEY [ AGENTNUM ]
+
+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)=@_;
+  defined($self->_config($name, $agentnum));
+}
+
+#=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 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}++;
+      }
+    }
+  }
+  
+  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 eq 'binary') {
+      $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);
+
+  unless ($type eq 'binary') {
+    {
+      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;
+    }
+  }
+
+  if ($type eq 'binary') {
+    $error .= "$key fails binary comparison; "
+      unless scalar($self->config_binary($key)) eq scalar($compat->config_binary($key));
+  }
+
+  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' => $_,
+                                   'section' => $proto->section,
+                                   'description' => 'Alternate ' . $proto->description . '  See the <a href="http://www.sisd.com/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 non-deprecated 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_html
+                   invoice_htmlreturnaddress
+                   invoice_htmlfooter
+                   invoice_htmlnotes
+                   logo.png
+                   logo.eps
+                 );
+
+@base_items = qw (
+                   invoice_template
+                   invoice_latex
+                   invoice_latexreturnaddress
+                   invoice_latexfooter
+                   invoice_latexsmallfooter
+                   invoice_latexnotes
+                   invoice_html
+                   invoice_htmlreturnaddress
+                   invoice_htmlfooter
+                   invoice_htmlnotes
+                   logo.png
+                   logo.eps
+                 );
+
+@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="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Credit_cards_and_Electronic_checks">billing documentation</a> for details.',
+    'type'        => 'textarea',
+  },
+
+  {
+    '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'         => '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'         => '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'         => 'deletecustomers',
+    'section'     => 'UI',
+    'description' => 'Enable customer deletions.  Be very careful!  Deleting a customer will remove all traces that this customer ever existed!  It should probably only be used when auditing a legacy database.  Normally, you cancel all of a customers\' packages if they cancel service.',
+    'type'        => 'checkbox',
+  },
+
+  {
+    'key'         => 'deletepayments',
+    'section'     => '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'         => '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'         => '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',
+  },
+
+  {
+    'key'         => 'invoice_template',
+    'section'     => 'billing',
+    '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.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Plaintext_invoice_templates">billing documentation</a> for details.',
+    'type'        => 'textarea',
+  },
+
+  {
+    'key'         => 'invoice_html',
+    'section'     => 'billing',
+    'description' => 'Optional HTML template for invoices.  See the <a href="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#HTML_invoice_templates">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="http://www.sisd.com/mediawiki/index.php/Freeside:1.7:Documentation:Administration#Typeset_.28LaTeX.29_invoice_templates">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_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_sections',
+    'section'     => 'billing',
+    'description' => 'Split invoice into sections and label according to package type when enabled.',
+    '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'         => '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'         => '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'         => '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'         => 'signupurl',
+    'section'     => 'UI',
+    'description' => 'if you are using customer-to-customer referrals, and you enter the URL of your <a href="http://www.sisd.com/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'         => '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',
+  },
+
+  {
+    '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'         => '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'     => '',
+    'description' => 'Acceptable payment types for the signup server',
+    'type'        => 'selectmultiple',
+    'select_enum' => [ qw(CARD DCRD CHEK DCHK LECB PREPAY BILL COMP) ],
+  },
+
+  {
+    '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.  Make sure to fill in the cancelmessage and cancelsubject configuration values as well.',
+    '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',
+    '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/~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'     => 'UI',
+    'description' => 'Causes 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' ],
+  },
+
+  {
+    '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'         => '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="http://www.sisd.com/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-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',
+  },
+
+  {
+    'key'         => 'company_address',
+    'section'     => 'required',
+    'description' => 'Your company address',
+    'type'        => 'textarea',
+  },
+
+  {
+    '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'         => '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'     => '',
+    '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'         => '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',
+  },
+
+  {
+    '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'         => 'batch-enable',
+    'section'     => 'billing',
+    'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.',
+    'type'        => 'checkbox',
+  },
+
+  {
+    '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'         => '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-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'         => 'logo.png',
+    'section'     => 'billing',  #? 
+    'description' => 'An image to include in some types of invoices',
+    'type'        => 'binary',
+  },
+
+  {
+    'key'         => 'logo.eps',
+    'section'     => 'billing',  #? 
+    'description' => 'An image to include in some types of invoices',
+    'type'        => 'binary',
+  },
+
+  {
+    'key'         => 'selfservice-ignore_quantity',
+    'section'     => '',
+    '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'     => '',
+    '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'        => 'textarea',
+  },
+
+  {
+    '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'         => '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'     => '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',
+                     ],
+  },
+
+);
+
+1;
diff --git a/FS/FS/ConfDefaults.pm b/FS/FS/ConfDefaults.pm
new file mode 100644 (file)
index 0000000..7978259
--- /dev/null
@@ -0,0 +1,73 @@
+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 address if present)',
+  'Cust# | Cust. Status | (bill) Name | (bill) Company | (service) Name | (service) Company' =>
+    'custnum | Status | Last, First | Company | (same for service address 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',
+
+); }
+
+=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 (file)
index 0000000..a0e997a
--- /dev/null
@@ -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 (file)
index 0000000..bcd78e8
--- /dev/null
@@ -0,0 +1,2196 @@
+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'         => '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 this customer ever existed!  It should probably only be used when auditing a legacy database.  Normally, you cancel all of a customers\' packages if they cancel service.',
+    'type'        => 'checkbox',
+  },
+
+  {
+    'key'         => 'deletepayments',
+    'section'     => '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_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_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'         => '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'     => 'UI',
+    'description' => 'Causes 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' ],
+  },
+
+  {
+    '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'         => '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'         => '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'     => '',
+    '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'         => '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'         => 'batch-enable',
+    'section'     => 'billing',
+    'description' => 'Enable credit card and/or ACH batching - leave disabled for real-time installations.',
+    'type'        => 'checkbox',
+  },
+
+  {
+    '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'         => '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'         => '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 : '';
+                         },
+  },
+
+);
+
+1;
+
diff --git a/FS/FS/Cron/backup.pm b/FS/FS/Cron/backup.pm
new file mode 100644 (file)
index 0000000..204069a
--- /dev/null
@@ -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 (file)
index 0000000..7de2ff2
--- /dev/null
@@ -0,0 +1,150 @@
+package FS::Cron::bill;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK );
+use Exporter;
+use Date::Parse;
+use FS::UID qw(dbh);
+use FS::Record qw(qsearchs);
+use FS::cust_main;
+use FS::part_event;
+use FS::part_event_condition;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw ( bill );
+
+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 @search = ();
+
+  push @search, "cust_main.payby    = '". $opt{'p'}. "'"
+    if $opt{'p'};
+  push @search, "cust_main.agentnum =  ". $opt{'a'}
+    if $opt{'a'};
+
+  if ( @ARGV ) {
+    push @search, "( ".
+      join(' OR ', map "cust_main.custnum = $_", @ARGV ).
+    " )";
+  }
+
+  ###
+  # generate where_pkg/where_event search clause
+  ###
+
+  #we're at now now (and later).
+  my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T;
+  $time += $opt{'y'} * 86400 if $opt{'y'};
+
+  my $invoice_time = $opt{'n'} ? $^T : $time;
+
+  # select * from cust_main where
+  my $where_pkg = <<"END";
+    0 < ( select count(*) from cust_pkg
+            where cust_main.custnum = cust_pkg.custnum
+              and ( cancel is null or cancel = 0 )
+              and (    setup is null or setup =  0
+                    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,
+                                                              );
+
+    my $are_part_event = 
+      "0 < ( SELECT COUNT(*) FROM part_event $join
+               WHERE check_freq = '$check_freq'
+                 AND eventtable = '$eventtable'
+                 AND ( disabled = '' OR disabled IS NULL )
+                 AND $where
+           )
+      ";
+
+    if ( $eventtable eq 'cust_main' ) { 
+      $are_part_event;
+    } else {
+      "0 < ( SELECT COUNT(*) FROM $eventtable
+               WHERE cust_main.custnum = $eventtable.custnum
+                 AND $are_part_event
+           )
+      ";
+    }
+
+  } FS::part_event->eventtables);
+
+  push @search, "( $where_pkg OR $where_event )";
+
+  ###
+  # get a list of custnums
+  ###
+
+  warn "searching for customers:\n". join("\n", @search). "\n"
+    if $opt{'v'} || $opt{'l'};
+
+  my $sth = dbh->prepare(
+    "SELECT custnum FROM cust_main".
+    " WHERE ". join(' AND ', @search)
+  ) or die dbh->errstr;
+
+  $sth->execute or die $sth->errstr;
+
+  my @custnums = map { $_->[0] } @{ $sth->fetchall_arrayref };
+
+  ###
+  # 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 ) {
+
+    if ( $opt{'m'} ) {
+
+      #add job to queue that calls bill_and_collect with options
+        my $queue = new FS::queue {
+          'job'    => 'FS::cust_main::queued_bill',
+          'secure' => 'Y',
+        };
+        my $error = $queue->insert(
+        'custnum'      => $custnum,
+        'time'         => $time,
+        'invoice_time' => $invoice_time,
+        'check_freq'   => $check_freq,
+        'resetup'      => $opt{'s'} ? $opt{'s'} : 0,
+      );
+
+    } else {
+
+      my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } );
+
+      $cust_main->bill_and_collect(
+        'time'         => $time,
+        'invoice_time' => $invoice_time,
+        'check_freq'   => $check_freq,
+        'resetup'      => $opt{'s'},
+        'debug'        => $debug,
+      );
+
+    }
+
+  }
+
+}
diff --git a/FS/FS/Cron/expire_user_pref.pm b/FS/FS/Cron/expire_user_pref.pm
new file mode 100644 (file)
index 0000000..3226927
--- /dev/null
@@ -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 (file)
index 0000000..23cf920
--- /dev/null
@@ -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 part_pkg_option.optionvalue > 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 cust_pkg_option.optionvalue = 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/vacuum.pm b/FS/FS/Cron/vacuum.pm
new file mode 100644 (file)
index 0000000..075572d
--- /dev/null
@@ -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 (file)
index 0000000..bcd337d
--- /dev/null
@@ -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 (file)
index 0000000..7e0d45c
--- /dev/null
@@ -0,0 +1,92 @@
+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 sigint sigterm logfile );
+
+$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 _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 (file)
index 0000000..5038cf3
--- /dev/null
@@ -0,0 +1,91 @@
+package FS::InitHandler;
+
+# this leaks memory under graceful restarts and i wouldn't use it on any
+# modern server.  useful for very slow machines with memory to spare, just
+# always do a full restart
+
+use strict;
+use vars qw($DEBUG);
+use FS::UID qw(adminsuidsetup);
+use FS::Record;
+
+$DEBUG = 1;
+
+sub handler {
+
+  use Date::Format;
+  use Date::Parse;
+  use Tie::IxHash;
+  use HTML::Entities;
+  use IO::Handle;
+  use IO::File;
+  use String::Approx;
+  use HTML::Widgets::SelectLayers 0.02;
+  #use FS::UID;
+  #use FS::Record;
+  use FS::Conf;
+  use FS::CGI;
+  use FS::Msgcat;
+  
+  use FS::agent;
+  use FS::agent_type;
+  use FS::domain_record;
+  use FS::cust_bill;
+  use FS::cust_bill_pay;
+  use FS::cust_credit;
+  use FS::cust_credit_bill;
+  use FS::cust_main;
+  use FS::cust_main_county;
+  use FS::cust_pay;
+  use FS::cust_pkg;
+  use FS::cust_refund;
+  use FS::cust_svc;
+  use FS::nas;
+  use FS::part_bill_event;
+  use FS::part_pkg;
+  use FS::part_referral;
+  use FS::part_svc;
+  use FS::pkg_svc;
+  use FS::port;
+  use FS::queue;
+  use FS::raddb;
+  use FS::session;
+  use FS::svc_acct;
+  use FS::svc_acct_pop;
+  use FS::svc_domain;
+  use FS::svc_forward;
+  use FS::svc_www;
+  use FS::type_pkgs;
+  use FS::part_export;
+  use FS::part_export_option;
+  use FS::export_svc;
+  use FS::msgcat;
+
+  warn "[FS::InitHandler] handler called\n" if $DEBUG;
+
+  #this is sure to be broken on freebsd
+  $> = $FS::UID::freeside_uid;
+
+  open(MAPSECRETS,"<$FS::UID::conf_dir/mapsecrets")
+    or die "can't read $FS::UID::conf_dir/mapsecrets: $!";
+
+  my %seen;
+  while (<MAPSECRETS>) {
+    next if /^\s*(#|$)/;
+    /^([\w\-\.]+)\s(.*)$/
+      or do { warn "strange line in mapsecrets: $_"; next; };
+    my($user, $datasrc) = ($1, $2);
+    next if $seen{$datasrc}++;
+    warn "[FS::InitHandler] preloading $datasrc for $user\n" if $DEBUG;
+    adminsuidsetup($user);
+  }
+
+  close MAPSECRETS;
+
+  #lalala probably broken on freebsd
+  ($<, $>) = ($>, $<);
+  $< = 0;
+
+}
+
+1;
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
new file mode 100644 (file)
index 0000000..54467a1
--- /dev/null
@@ -0,0 +1,576 @@
+package FS::Misc;
+
+use strict;
+use vars qw ( @ISA @EXPORT_OK $DEBUG );
+use Exporter;
+use Carp;
+use Data::Dumper;
+#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( send_email send_fax
+                 states_hash counties state_label
+                 card_types
+                 generate_ps do_print
+               );
+
+$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 send_email OPTION => VALUE ...
+
+Options:
+
+I<from> - (required)
+
+I<to> - (required) comma-separated scalar or arrayref of recipients
+
+I<subject> - (required)
+
+I<content-type> - (optional) MIME type for the body
+
+I<body> - (required unless I<nobody> is true) arrayref of body text lines
+
+I<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().
+
+I<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.
+
+I<content-encoding> - (optional) when using nobody, optional top-level MIME
+encoding which, if specified, overrides the default "7bit".
+
+I<type> - (optional) type parameter for multipart/related messages
+
+=cut
+
+use vars qw( $conf );
+use Date::Format;
+use Mail::Header;
+use Mail::Internet 2.00;
+use MIME::Entity;
+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"
+  }
+
+  $ENV{MAILADDRESS} = $options{'from'};
+  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 @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!";
+    }
+
+  }
+
+  my $smtpmachine = $conf->config('smtpmachine');
+  $!=0;
+
+  $message->mysmtpsend( 'Host'     => $smtpmachine,
+                        'MailFrom' => $options{'from'},
+                      );
+
+}
+
+#this kludges a "mysmtpsend" method into Mail::Internet for send_email above
+#now updated for MailTools v2!
+package Mail::Internet;
+
+use Mail::Address;
+use Net::SMTP;
+use Net::Domain;
+
+sub Mail::Internet::mysmtpsend($@) {
+    my ($self, %opt) = @_;
+
+    my $host     = $opt{Host};
+    my $envelope = $opt{MailFrom}; # || mailaddress();
+    my $quit     = 1;
+
+    my ($smtp, @hello);
+
+    push @hello, Hello => $opt{Hello}
+        if defined $opt{Hello};
+
+    push @hello, Port => $opt{Port}
+        if exists $opt{Port};
+
+    push @hello, Debug => $opt{Debug}
+        if exists $opt{Debug};
+
+#    if(!defined $host)
+#    {   local $SIG{__DIE__};
+#        my @hosts = qw(mailhost localhost);
+#        unshift @hosts, split /\:/, $ENV{SMTPHOSTS}
+#            if defined $ENV{SMTPHOSTS};
+#
+#        foreach $host (@hosts)
+#        {   $smtp = eval { Net::SMTP->new($host, @hello) };
+#            last if defined $smtp;
+#        }
+#    }
+#    elsif(ref($host) && UNIVERSAL::isa($host,'Net::SMTP'))
+    if(ref($host) && UNIVERSAL::isa($host,'Net::SMTP'))
+    {   $smtp = $host;
+        $quit = 0;
+    }
+    else
+    {   #local $SIG{__DIE__};
+        #$smtp = eval { Net::SMTP->new($host, @hello) };
+        $smtp = Net::SMTP->new($host, @hello);
+    }
+
+    unless ( defined($smtp) ) {
+      my $err = $!;
+      $err =~ s/Invalid argument/Unknown host/;
+      return "can't connect to $host: $err"
+    }
+
+    my $head = $self->cleaned_header_dup;
+
+    $head->delete('Bcc');
+
+    # Who is it to
+
+    my @rcpt = map { ref $_ ? @$_ : $_ } grep { defined } @opt{'To','Cc','Bcc'};
+    @rcpt    = map { $head->get($_) } qw(To Cc Bcc)
+        unless @rcpt;
+
+    my @addr = map {$_->address} Mail::Address->parse(@rcpt);
+    #@addr or return ();
+    return 'No valid destination addresses found!'
+       unless(@addr);
+
+    # Send it
+
+    my $ok = $smtp->mail($envelope)
+          && $smtp->to(@addr)
+          && $smtp->data(join("", @{$head->header}, "\n", @{$self->body}));
+
+    #$quit && $smtp->quit;
+    #$ok ? @addr : ();
+    if ( $ok ) {
+      $quit && $smtp->quit;
+      return '';
+    } else {
+      return $smtp->code. ' '. $smtp->message;
+    }
+}
+package FS::Misc;
+#eokludge
+
+=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 ) = @_;
+
+  sort map { s/[\n\r]//g; $_; }
+       map { $_->county }
+           qsearch({
+             'select'  => 'DISTINCT county',
+             'table'   => 'cust_main_county',
+             'hashref' => { '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);
+
+  my $sfile = shell_quote $file;
+
+  system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+    or die "pslatex $file.tex failed; see $file.log for details?\n";
+  system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+    or die "pslatex $file.tex failed; see $file.log for details?\n";
+
+  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 print ARRAYREF
+
+Sends the lines in ARRAYREF to the printer.
+
+=cut
+
+use IPC::Run3;
+
+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";
+  }
+
+}
+
+=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/prune.pm b/FS/FS/Misc/prune.pm
new file mode 100644 (file)
index 0000000..371f31c
--- /dev/null
@@ -0,0 +1,126 @@
+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();
+
+=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 (file)
index 0000000..625743d
--- /dev/null
@@ -0,0 +1,98 @@
+package FS::Msgcat;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $conf $locale $debug );
+use Exporter;
+use FS::UID;
+#use FS::Record qw( qsearchs ); # wtf?  won't import...
+use FS::Record;
+use FS::Conf;
+use FS::msgcat;
+
+@ISA = qw(Exporter);
+@EXPORT_OK = qw( gettext geterror );
+
+$FS::UID::callback{'Msgcat'} = sub {
+  $conf = new FS::Conf;
+  $locale = $conf->config('locale') || 'en_US';
+  $debug = $conf->exists('show-msgcat-codes')
+};
+
+=head1 NAME
+
+FS::Msgcat - Message catalog functions
+
+=head1 SYNOPSIS
+
+  use FS::Msgcat qw(gettext geterror);
+
+  #simple interface for retreiving messages...
+  $message = gettext('msgcode');
+  #or errors (includes the error code)
+  $message = geterror('msgcode');
+
+=head1 DESCRIPTION
+
+FS::Msgcat provides functions to use the message catalog.  If you want to
+maintain the message catalog database, see L<FS::msgcat> instead.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item gettext MSGCODE
+
+Returns the full message for the supplied message code.
+
+=cut
+
+sub gettext {
+  $debug ? geterror(@_) : _gettext(@_);
+}
+
+sub _gettext {
+  my $msgcode = shift;
+  my $msgcat = FS::Record::qsearchs('msgcat', {
+    'msgcode' => $msgcode,
+    'locale' => $locale
+  } );
+  if ( $msgcat ) {
+    $msgcat->msg;
+  } else {
+    warn "WARNING: message for msgcode $msgcode in locale $locale not found";
+    $msgcode;
+  }
+
+}
+
+=item geterror MSGCODE
+
+Returns the full message for the supplied message code, including the message
+code.
+
+=cut
+
+sub geterror {
+  my $msgcode = shift;
+  my $msg = _gettext($msgcode);
+  if ( $msg eq $msgcode ) {
+    "Error code $msgcode (message for locale $locale not found)";
+  } else {
+    "$msg (error code $msgcode)";
+  }
+}
+
+=back
+
+=head1 BUGS
+
+i18n/l10n, eek
+
+=head1 SEE ALSO
+
+L<FS::msgcat>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/Pony.pm b/FS/FS/Pony.pm
new file mode 100644 (file)
index 0000000..c37dd78
--- /dev/null
@@ -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 (file)
index 0000000..db94003
--- /dev/null
@@ -0,0 +1,2351 @@
+package FS::Record;
+
+use strict;
+use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG
+             $conf $me
+             %virtual_fields_cache $nowarn_identical $no_update_diff );
+use Exporter;
+use Carp qw(carp cluck croak confess);
+use File::CounterFile;
+use Locale::Country;
+use DBI qw(:sql_types);
+use DBIx::DBSchema 0.33;
+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;
+
+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);
+
+$DEBUG = 0;
+$me = '[FS::Record]';
+
+$nowarn_identical = 0;
+$no_update_diff = 0;
+
+my $rsa_module;
+my $rsa_loaded;
+my $rsa_encrypt;
+my $rsa_decrypt;
+
+FS::UID->install_callback( sub {
+  $conf = new FS::Conf; 
+  $File::CounterFile::DEFAULT_DIR = $conf->base_dir . "/counters.". datasrc;
+} );
+
+
+=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'};
+  }
+  
+  $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:
+
+  my @records = qsearch( {
+                           'table'     => 'table_name',
+                           'hashref'   => { 'field' => 'value'
+                                            'field' => { 'op'    => '<',
+                                                         'value' => '420',
+                                                       },
+                                          },
+
+                           #these are optional...
+                           'select'    => '*',
+                           'extra_sql' => 'AND field ',
+                           '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' } );
+
+###oops, argh, FS::Record::new only lets us create database fields.
+#Normal behaviour if SELECT is not specified is `*', as in
+#C<SELECT * FROM table WHERE ...>.  However, there is an experimental new
+#feature where you can specify SELECT - remember, the objects returned,
+#although blessed into the appropriate `FS::TABLE' package, will only have the
+#fields you specify.  This might have unwanted results if you then go calling
+#regular FS::TABLE methods
+#on it.
+
+=cut
+
+sub qsearch {
+  my($stable, $record, $select, $extra_sql, $order_by, $cache, $addl_from );
+  my $debug = '';
+  if ( ref($_[0]) ) { #hashref for now, eventually maybe accept a list too
+    my $opt = shift;
+    $stable    = $opt->{'table'}     or die "table name is required";
+    $record    = $opt->{'hashref'}   || {};
+    $select    = $opt->{'select'}    || '*';
+    $extra_sql = $opt->{'extra_sql'} || '';
+    $order_by  = $opt->{'order_by'}  || '';
+    $cache     = $opt->{'cache_obj'} || '';
+    $addl_from = $opt->{'addl_from'} || '';
+    $debug     = $opt->{'debug'}     || '';
+  } else {
+    ($stable, $record, $select, $extra_sql, $cache, $addl_from ) = @_;
+    $select ||= '*';
+  }
+
+  #$stable =~ /^([\w\_]+)$/ or die "Illegal table: $table";
+  #for jsearch
+  $stable =~ /^([\w\s\(\)\.\,\=]+)$/ or die "Illegal table: $stable";
+  $stable = $1;
+  my $dbh = dbh;
+
+  my $table = $cache ? $cache->table : $stable;
+  my $dbdef_table = dbdef->table($table)
+    or die "No schema for table $table found - ".
+           "do you need to 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";
+    @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);
+
+  warn "[debug]$me $statement\n" if $DEBUG > 1 || $debug;
+  my $sth = $dbh->prepare($statement)
+    or croak "$dbh->errstr doing $statement";
+
+  my $bind = 1;
+
+  foreach my $field (
+    grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields
+  ) {
+    if ( $record->{$field} =~ /^\d+(\.\d+)?$/
+         && dbdef->table($table)->column($field)->type =~ /(int|(big)?serial)/i
+    ) {
+      $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_INTEGER } );
+    } else {
+      $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_VARCHAR } );
+    }
+  }
+
+#  $sth->execute( map $record->{$_},
+#    grep defined( $record->{$_} ) && $record->{$_} ne '', @fields
+#  ) or croak "Error executing \"$statement\": ". $sth->errstr;
+
+  $sth->execute or croak "Error executing \"$statement\": ". $sth->errstr;
+
+  if ( eval 'scalar(@FS::'. $table. '::ISA);' ) {
+    @virtual_fields = "FS::$table"->virtual_fields;
+  } else {
+    cluck "warning: FS::$table not loaded; virtual fields not returned either";
+    @virtual_fields = ();
+  }
+
+  my %result;
+  tie %result, "Tie::IxHash";
+  my @stuff = @{ $sth->fetchall_arrayref( {} ) };
+  if ( $pkey && 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 && $conf->exists('encryption') # $conf doesn't exist when doing
+                                              # the initial search for
+                                              # access_user
+         && 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";
+    @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 = $_;
+      if ( ref($record->{$_}) ) {
+        $op = $record->{$_}{'op'} if $record->{$_}{'op'};
+        #$op = 'LIKE' if $op =~ /^ILIKE$/i && driver_name ne 'Pg';
+        if ( uc($op) eq 'ILIKE' ) {
+          $op = 'LIKE';
+          $record->{$_}{'value'} = lc($record->{$_}{'value'});
+          $column = "LOWER($_)";
+        }
+        $record->{$_} = $record->{$_}{'value'}
+      }
+
+      if ( ! defined( $record->{$_} ) || $record->{$_} eq '' ) {
+        if ( $op eq '=' ) {
+          if ( driver_name eq 'Pg' ) {
+            my $type = dbdef->table($table)->column($column)->type;
+            if ( $type =~ /(int|(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' ) {
+            my $type = dbdef->table($table)->column($column)->type;
+            if ( $type =~ /(int|(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 "" )-;
+          }
+        }
+      } 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 ref($self) && $self->can('setfield');
+    $self->setfield($field,$value);
+  } else {
+    confess "errant AUTOLOAD $field for $self (no args)"
+      unless ref($self) && $self->can('getfield');
+    $self->getfield($field);
+  }    
+}
+
+# efficient
+#sub AUTOLOAD {
+#  my $field = $AUTOLOAD;
+#  $field =~ s/.*://;
+#  if ( defined($_[1]) ) {
+#    $_[0]->setfield($field, $_[1]);
+#  } else {
+#    $_[0]->getfield($field);
+#  }    
+#}
+
+=item hash
+
+Returns a list of the column/value pairs, usually for assigning to a new hash.
+
+To make a distinct duplicate of an FS::Record object, you can do:
+
+    $new = new FS::Record ( $old->table, { $old->hash } );
+
+=cut
+
+sub hash {
+  my($self) = @_;
+  confess $self. ' -> hash: Hash attribute is undefined'
+    unless defined($self->{'Hash'});
+  %{ $self->{'Hash'} }; 
+}
+
+=item hashref
+
+Returns a reference to the column/value hash.  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 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->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
+  my $conf = new FS::Conf;
+  if ($conf->exists('encryption') && defined(eval '@FS::'. $table . '::encrypted_fields')) {
+    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)->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 $conf = new FS::Conf;
+  my $saved = {};
+  if ($conf->exists('encryption') && defined(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, $_);
+    }
+  }
+  '';
+}
+
+sub _h_statement {
+  my( $self, $action, $time ) = @_;
+
+  $time ||= time;
+
+  my @fields =
+    grep { defined($self->getfield($_)) && $self->getfield($_) ne "" }
+    real_fields($self->table);
+  ;
+
+  # If we're encrypting then don't ever store the payinfo or CVV2 in the history....
+  # You can see if it changed by the paymask...
+  my $conf = new FS::Conf;
+  if ($conf->exists('encryption') ) {
+    @fields = grep  $_ ne 'payinfo' && $_ ne 'cvv2', @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) =~ /^(\d+\.\d+)$/ ||
+   $self->getfield($field) =~ /^(\d+)$/ ||
+   $self->getfield($field) =~ /^(\d+\.\d+e\d+)$/ ||
+   $self->getfield($field) =~ /^(\d+e\d+)$/)
+    or return "Illegal or empty (float) $field: ". $self->getfield($field);
+  $self->setfield($field,$1);
+  '';
+}
+=item ut_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) =~ /^(-?\d+\.\d+)$/ ||
+   $self->getfield($field) =~ /^(-?\d+)$/ ||
+   $self->getfield($field) =~ /^(-?\d+\.\d+[eE]-?\d+)$/ ||
+   $self->getfield($field) =~ /^(-?\d+[eE]-?\d+)$/)
+    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*(\d+)$/
+    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*(\d*)$/
+    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) =~ /^(\d+)$/
+    or return "Illegal or empty (numeric) $field: ". $self->getfield($field);
+  $self->setfield($field,$1);
+  '';
+}
+
+=item ut_numbern COLUMN
+
+Check/untaint simple numeric data (whole numbers).  May be null.  If there is
+an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_numbern {
+  my($self,$field)=@_;
+  $self->getfield($field) =~ /^(\d*)$/
+    or return "Illegal (numeric) $field: ". $self->getfield($field);
+  $self->setfield($field,$1);
+  '';
+}
+
+=item ut_money COLUMN
+
+Check/untaint monetary numbers.  May be negative.  Set to 0 if null.  If there
+is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_money {
+  my($self,$field)=@_;
+  $self->setfield($field, 0) if $self->getfield($field) eq '';
+  $self->getfield($field) =~ /^(\-)? ?(\d*)(\.\d{2})?$/
+    or return "Illegal (money) $field: ". $self->getfield($field);
+  #$self->setfield($field, "$1$2$3" || 0);
+  $self->setfield($field, ( ($1||''). ($2||''). ($3||'') ) || 0);
+  '';
+}
+
+=item ut_text COLUMN
+
+Check/untaint text.  Alphanumerics, spaces, and the following punctuation
+symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ]
+May not be null.  If there is an error, returns the error, otherwise returns
+false.
+
+=cut
+
+sub ut_text {
+  my($self,$field)=@_;
+  #warn "msgcat ". \&msgcat. "\n";
+  #warn "notexist ". \&notexist. "\n";
+  #warn "AUTOLOAD ". \&AUTOLOAD. "\n";
+  $self->getfield($field)
+    =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)$/
+      or return gettext('illegal_or_empty_text'). " $field: ".
+                 $self->getfield($field);
+  $self->setfield($field,$1);
+  '';
+}
+
+=item ut_textn COLUMN
+
+Check/untaint text.  Alphanumerics, spaces, and the following punctuation
+symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? /
+May be null.  If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_textn {
+  my($self,$field)=@_;
+  $self->getfield($field)
+    =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
+      or return gettext('illegal_text'). " $field: ". $self->getfield($field);
+  $self->setfield($field,$1);
+  '';
+}
+
+=item ut_alpha COLUMN
+
+Check/untaint alphanumeric strings (no spaces).  May not be null.  If there is
+an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_alpha {
+  my($self,$field)=@_;
+  $self->getfield($field) =~ /^(\w+)$/
+    or return "Illegal or empty (alphanumeric) $field: ".
+              $self->getfield($field);
+  $self->setfield($field,$1);
+  '';
+}
+
+=item ut_alpha COLUMN
+
+Check/untaint alphanumeric strings (no spaces).  May be null.  If there is an
+error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_alphan {
+  my($self,$field)=@_;
+  $self->getfield($field) =~ /^(\w*)$/ 
+    or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
+  $self->setfield($field,$1);
+  '';
+}
+
+=item ut_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 =~ /^(\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.
+
+=cut
+
+sub ut_ip {
+  my( $self, $field ) = @_;
+  $self->getfield($field) =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
+    or return "Illegal (IP address) $field: ". $self->getfield($field);
+  for ( $1, $2, $3, $4 ) { return "Illegal (IP address) $field" if $_ > 255; }
+  $self->setfield($field, "$1.$2.$3.$4");
+  '';
+}
+
+=item ut_ipn COLUMN
+
+Check/untaint ip addresses.  IPv4 only for now.  May be null.
+
+=cut
+
+sub ut_ipn {
+  my( $self, $field ) = @_;
+  if ( $self->getfield($field) =~ /^()$/ ) {
+    $self->setfield($field,'');
+    '';
+  } else {
+    $self->ut_ip($field);
+  }
+}
+
+=item ut_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($choice);
+      return '';
+    }
+  }
+  return "Illegal (enum) field $field: ". $self->getfield($field);
+}
+
+=item ut_foreign_key COLUMN FOREIGN_TABLE FOREIGN_COLUMN
+
+Check/untaint a foreign column key.  Call a regular ut_ method (like ut_number)
+on the column first.
+
+=cut
+
+sub ut_foreign_key {
+  my( $self, $field, $table, $foreign ) = @_;
+  qsearchs($table, { $foreign => $self->getfield($field) })
+    or return "Can't find ". $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
+
+Checks this column as an agentnum, taking into account the current users's
+ACLs.
+
+=cut
+
+sub ut_agentnum_acl {
+  my( $self, $field, $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 deined"
+      unless $curuser->agentnum($self->$field());
+
+  } else {
+
+    return "Access denied"
+      unless $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;
+
+  my $conf = new FS::Conf;
+  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.
+  my $conf = new FS::Conf;
+  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
+
+    my $conf = new FS::Conf;
+    if ($conf->exists('encryptionmodule') && $conf->config_binary('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_binary('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_binary('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 : '';
+}
+
+=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;
+  } 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 ' ) ';
+}
+
+=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 (file)
index 0000000..181fea2
--- /dev/null
@@ -0,0 +1,46 @@
+package FS::Report;
+
+use strict;
+
+=head1 NAME
+
+FS::Report - Report data objects
+
+=head1 SYNOPSIS
+
+  #see the more speicific report objects, currently only FS::Report::Table
+
+=head1 DESCRIPTION
+
+See the more specific report objects, currently only FS::Report::Table
+
+=head1 METHODS
+
+=over 4
+
+=item new [ OPTION => VALUE ... ]
+
+Constructor.  Takes a list of options and their values.
+
+=cut
+
+sub new {
+  my $proto = shift;
+  my $class = ref($proto) || $proto;
+  my $self = @_ ? ( ref($_[0]) ? shift : { @_ } ) : {};
+  bless( $self, $class );
+}
+
+=back
+
+=head1 BUGS
+
+Documentation.
+
+=head1 SEE ALSO
+
+L<FS::Report::Table>, reports in the web interface.
+
+=cut
+
+1;
diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm
new file mode 100644 (file)
index 0000000..9f636fa
--- /dev/null
@@ -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 (file)
index 0000000..33fdfd8
--- /dev/null
@@ -0,0 +1,363 @@
+package FS::Report::Table::Monthly;
+
+use strict;
+use vars qw( @ISA $expenses_kludge );
+use Time::Local;
+use FS::UID qw( dbh );
+use FS::Report::Table;
+use FS::CurrentUser;
+
+@ISA = qw( FS::Report::Table );
+
+$expenses_kludge = 0;
+
+=head1 NAME
+
+FS::Report::Table::Monthly - Tables of report data, indexed monthly
+
+=head1 SYNOPSIS
+
+  use FS::Report::Table::Monthly;
+
+  my $report = new FS::Report::Table::Monthly (
+    'items' => [ 'invoiced', 'netsales', 'credits', 'receipts', ],
+    'start_month' => 4,
+    'start_year'  => 2000,
+    'end_month'   => 4,
+    'end_year'    => 2020,
+    #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 ) = @_;
+
+  my $credited = $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'
+                                             )
+  );
+
+  #horrible local kludge
+  my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql("
+    SELECT SUM(cust_bill_pkg.setup)
+      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 )
+      WHERE ". $self->in_time_period_and_agent( $speriod,
+                                                $eperiod,
+                                                $agentnum,
+                                                'cust_bill._date'
+                                              ). "
+        AND LOWER(part_pkg.pkg) LIKE 'expense _%'
+  ");
+
+  $self->invoiced($speriod,$eperiod,$agentnum) - $credited - $expenses;
+}
+
+#deferred revenue
+
+sub receipts { #cashflow
+  my( $self, $speriod, $eperiod, $agentnum ) = @_;
+
+  my $refunded = $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)
+  );
+
+  #horrible local kludge that doesn't even really work right
+  my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql("
+    SELECT SUM(cust_bill_pay.amount)
+      FROM cust_bill_pay
+        LEFT JOIN cust_bill USING ( invnum  )
+        LEFT JOIN cust_main USING ( custnum )
+    WHERE ". $self->in_time_period_and_agent( $speriod,
+                                              $eperiod,
+                                              $agentnum,
+                                              'cust_bill_pay._date'
+                                            ). "
+    AND 0 < ( SELECT COUNT(*) from cust_bill_pkg, cust_pkg, part_pkg
+              WHERE cust_bill.invnum = cust_bill_pkg.invnum
+              AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum
+              AND cust_pkg.pkgpart = part_pkg.pkgpart
+              AND LOWER(part_pkg.pkg) LIKE 'expense _%'
+            )
+  ");
+  #    my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'";
+  
+  $self->payments($speriod, $eperiod, $agentnum) - $refunded - $expenses;
+}
+
+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)
+  );
+}
+
+#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);
+}
+
+#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_bill_pkg {
+  my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_;
+
+  my $where = '';
+  if ( $opt{'classnum'} =~ /^(\d+)$/ ) {
+    if ( $1 == 0 ) {
+      $where = "classnum IS NULL";
+    } else {
+      $where = "classnum = $1";
+    }
+  }
+
+  $agentnum ||= $opt{'agentnum'};
+
+  $self->scalar_sql("
+    SELECT SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)
+      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 )
+      WHERE pkgnum != 0
+        AND $where
+        AND ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum)
+  );
+  
+}
+
+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;
+  $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 (file)
index 0000000..9548aa7
--- /dev/null
@@ -0,0 +1,1962 @@
+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 );
+
+      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'};
+    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;
+
+    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('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 $username_len = 32; #usernamemax config file
+
+    # name type nullability length default local
+
+  return {
+
+    'agent' => {
+      'columns' => [
+        'agentnum',          'serial',    '',       '', '', '', 
+        'agent',            'varchar',    '',  $char_d, '', '', 
+        'typenum',              'int',    '',       '', '', '', 
+        'disabled',            'char', 'NULL',       1, '', '', 
+        'ticketing_queueid',    'int', 'NULL',      '', '', '', 
+        'invoice_template', 'varchar', 'NULL', $char_d, '', '',
+        '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' => [],
+      'index' => [ ['typenum'], ['disabled'] ],
+    },
+
+    '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_bill' => {
+      'columns' => [
+        'invnum',    'serial',  '', '', '', '', 
+        'custnum',   'int',  '', '', '', '', 
+        '_date',     @date_type, '', '', 
+        'charged',   @money_type, '', '', 
+        'printed',   'int',  '', '', '', '', 
+        'closed',    'char', 'NULL', 1, '', '', 
+      ],
+      'primary_key' => 'invnum',
+      'unique' => [],
+      'index' => [ ['custnum'], ['_date'] ],
+    },
+
+    'cust_bill_event' => {
+      'columns' => [
+        'eventnum',    'serial',  '', '', '', '', 
+        'invnum',   'int',  '', '', '', '', 
+        'eventpart',   'int',  '', '', '', '', 
+        '_date',     @date_type, '', '', 
+        'status', 'varchar', '', $char_d, '', '', 
+        'statustext', 'text', 'NULL', '', '', '', 
+      ],
+      'primary_key' => 'eventnum',
+      #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
+      'unique' => [],
+      'index' => [ ['invnum'], ['status'] ],
+    },
+
+    'part_bill_event' => {
+      'columns' => [
+        'eventpart',    'serial',  '', '', '', '', 
+        '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'] ],
+    },
+
+    'cust_bill_pkg' => {
+      'columns' => [
+        'billpkgnum', 'serial', '', '', '', '', 
+        'pkgnum',  'int', '', '', '', '', 
+        'invnum',  'int', '', '', '', '', 
+        'setup',   @money_type, '', '', 
+        'recur',   @money_type, '', '', 
+        'sdate',   @date_type, '', '', 
+        'edate',   @date_type, '', '', 
+        'itemdesc', 'varchar', 'NULL', $char_d, '', '', 
+      ],
+      'primary_key' => 'billpkgnum',
+      'unique' => [],
+      'index' => [ ['invnum'], [ 'pkgnum' ] ],
+    },
+
+    'cust_bill_pkg_detail' => {
+      'columns' => [
+        'detailnum', 'serial', '', '', '', '', 
+        'pkgnum',  'int', '', '', '', '', 
+        'invnum',  'int', '', '', '', '', 
+        'detail',  'varchar', '', $char_d, '', '', 
+      ],
+      'primary_key' => 'detailnum',
+      'unique' => [],
+      'index' => [ [ 'pkgnum', 'invnum' ] ],
+    },
+
+    'cust_credit' => {
+      'columns' => [
+        'crednum',  'serial', '', '', '', '', 
+        'custnum',  'int', '', '', '', '', 
+        '_date',    @date_type, '', '', 
+        'amount',   @money_type, '', '', 
+        'otaker',   'varchar', '', 32, '', '', 
+        'reason',   'text', 'NULL', '', '', '', 
+        'reasonnum', 'int', 'NULL', '', '', '', 
+        'closed',    'char', 'NULL', 1, '', '', 
+      ],
+      'primary_key' => 'crednum',
+      'unique' => [],
+      'index' => [ ['custnum'], ['_date'] ],
+    },
+
+    'cust_credit_bill' => {
+      'columns' => [
+        'creditbillnum', 'serial', '', '', '', '', 
+        'crednum',  'int', '', '', '', '', 
+        'invnum',  'int', '', '', '', '', 
+        '_date',    @date_type, '', '', 
+        'amount',   @money_type, '', '', 
+      ],
+      'primary_key' => 'creditbillnum',
+      'unique' => [],
+      'index' => [ ['crednum'], ['invnum'] ],
+    },
+
+    'cust_credit_bill_pkg' => {
+      'columns' => [
+        'creditbillpkgnum', 'serial', '',      '', '', '',
+        'creditbillnum',       'int', '',      '', '', '',
+        'billpkgnum',          'int', '',      '', '', '',
+        'amount',            @money_type,          '', '',
+        'setuprecur',      'varchar', '', $char_d, '', '',
+        'sdate',   @date_type, '', '', 
+        'edate',   @date_type, '', '', 
+      ],
+      'primary_key' => 'creditbillpkgnum',
+      'unique'      => [],
+      'index'       => [ [ 'creditbillnum' ], [ 'billpkgnum' ], ],
+    },
+
+    'cust_main' => {
+      'columns' => [
+        'custnum',  'serial',  '',     '', '', '', 
+        'agentnum', 'int',  '',     '', '', '', 
+        'agent_custid', '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, '', '', 
+        '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, '', '', 
+        'tax',      'char', 'NULL', 1, '', '', 
+        'otaker',   'varchar', '',    32, '', '', 
+        'refnum',   'int',  '',     '', '', '', 
+        'referral_custnum', 'int',  'NULL', '', '', '', 
+        'comments', 'text', 'NULL', '', '', '', 
+        'spool_cdr','char', 'NULL', 1, '', '', 
+        'invoice_terms', 'varchar', 'NULL', $char_d, '', '',
+      ],
+      'primary_key' => 'custnum',
+      'unique' => [ [ 'agentnum', 'agent_custid' ] ],
+      #'index' => [ ['last'], ['company'] ],
+      'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ],
+                   [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ],
+                   [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ],
+                   [ 'ship_last' ], [ 'ship_company' ],
+                   [ 'ship_daytime' ], [ 'ship_night' ], [ 'ship_fax' ],
+                   [ 'payby' ], [ 'paydate' ],
+                 ],
+    },
+
+    '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', '',    32, '', '', 
+        'comments', 'text', 'NULL', '', '', '', 
+      ],
+      'primary_key' => 'notenum',
+      'unique' => [],
+      'index' => [ [ 'custnum' ], [ '_date' ], ],
+    },
+
+    'cust_main_county' => { #county+state+country are checked off the
+                            #cust_main_county for validation and to provide
+                            # a tax rate.
+      'columns' => [
+        'taxnum',   'serial',   '',    '', '', '', 
+        'state',    'varchar',  'NULL',    $char_d, '', '', 
+        'county',   'varchar',  'NULL',    $char_d, '', '', 
+        'country',  'char',  '', 2, '', '', 
+        'taxclass',   'varchar', 'NULL', $char_d, '', '', 
+        'exempt_amount', @money_type, '', '', 
+        'tax',      'real',  '',    '', '', '', #tax %
+        'taxname',  'varchar',  'NULL',    $char_d, '', '', 
+        'setuptax',  'char', 'NULL', 1, '', '', # Y = setup tax exempt
+        'recurtax',  'char', 'NULL', 1, '', '', # Y = recur tax exempt
+      ],
+      'primary_key' => 'taxnum',
+      'unique' => [],
+  #    'unique' => [ ['taxnum'], ['state', 'county'] ],
+      'index' => [ [ 'county' ], [ 'state' ], [ 'country' ] ],
+    },
+
+    '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, '', '', 
+        #'paybatch',     'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
+        'payunique',    'varchar', 'NULL', $char_d, '', '', #separate paybatch "unique" functions from current usage
+
+        'status',       'varchar',     '', $char_d, '', '', 
+        'statustext',   'text',    'NULL',  '', '', '', 
+        'gatewaynum',   'int',     'NULL',  '', '', '',
+        #'cust_balance', @money_type,            '', '',
+        'paynum',       '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, '', '',  #NULL for the upgrade so we can create & populate the field
+        '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, '', '', 
+      ],
+      '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' ] ],
+    },
+
+    '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, '', '', 
+        'void_date', @date_type, '', '', 
+        'reason',    'varchar',   'NULL', $char_d, '', '', 
+        'otaker',   'varchar', '', 32, '', '', 
+      ],
+      'primary_key' => 'paynum',
+      'unique' => [],
+      'index' => [ [ 'custnum' ] ],
+    },
+
+    'cust_bill_pay' => {
+      'columns' => [
+        'billpaynum', 'serial',     '',   '', '', '', 
+        'invnum',  'int',     '',   '', '', '', 
+        'paynum',  'int',     '',   '', '', '', 
+        'amount',  @money_type, '', '', 
+        '_date',   @date_type, '', '', 
+      ],
+      'primary_key' => 'billpaynum',
+      'unique' => [],
+      'index' => [ [ 'paynum' ], [ 'invnum' ] ],
+    },
+
+    'cust_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', '', '', '', '',
+        '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',    '',   '', '', '', 
+        'otaker',         'varchar', '', 32, '', '', 
+        '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', '', '', '',
+        'manual_flag',    'char', 'NULL', 1, '', '', 
+      ],
+      'primary_key' => 'pkgnum',
+      'unique' => [],
+      'index' => [ ['custnum'], ['pkgpart'],
+                   ['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_reason' => {
+      'columns' => [
+        'num',      'serial',    '',   '', '', '', 
+        'pkgnum',   'int',    '',   '', '', '', 
+        'reasonnum','int',    '',   '', '', '', 
+        'otaker',   'varchar', '', 32, '', '', 
+        'date',     @date_type, '', '', 
+      ],
+      'primary_key' => 'num',
+      'unique' => [],
+      'index' => [],
+    },
+
+    'cust_refund' => {
+      'columns' => [
+        'refundnum',    'serial',    '',   '', '', '', 
+        'custnum',  'int',    '',   '', '', '', 
+        '_date',        @date_type, '', '', 
+        'refund',       @money_type, '', '', 
+        'otaker',       'varchar',   '',   32, '', '', 
+        'reason',       'varchar',   '',   $char_d, '', '', 
+        'payby',        'char',   '',     4, '', '', # CARD/BILL/COMP, should
+                                                     # be index into 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'] ],
+    },
+
+    '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'] ],
+    },
+
+    '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, '', '', 
+        'taxclass',      'varchar', 'NULL', $char_d, '', '', 
+        'classnum',      'int',     'NULL', '', '', '', 
+        'pay_weight',    'real',    'NULL', '', '', '',
+        'credit_weight', 'real',    'NULL', '', '', '',
+        'agentnum',      'int',     'NULL', '', '', '', 
+
+      ],
+      'primary_key' => 'pkgpart',
+      'unique' => [],
+      'index' => [ [ 'promo_code' ], [ 'disabled' ], [ 'agentnum' ], ],
+    },
+
+    'part_pkg_taxclass' => {
+      'columns' => [
+        'taxclassnum',  'serial', '',       '', '', '',
+        'taxclass',     'varchar', '', $char_d, '', '', 
+      ],
+      'primary_key' => 'taxclassnum',
+      'unique'      => [ [ 'taxclass' ] ],
+      'index'       => [],
+    },
+
+#    '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'] ],
+    },
+
+    '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, '', '', 
+        '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',   '',  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', '',   '', '', '', 
+        'last_login',  @date_type, '', '', 
+        'last_logout', @date_type, '', '', 
+      ],
+      'primary_key' => 'svcnum',
+      #'unique' => [ [ 'username', 'domsvc' ] ],
+      'unique' => [],
+      'index' => [ ['username'], ['domsvc'] ],
+    },
+
+    '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, '', '',
+      ],
+      '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'       => [],
+    },
+
+    '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', 'text', '', '', '', '', 
+        '_date', 'int', '', '', '', '', 
+        'status', 'varchar', '', $char_d, '', '', 
+        'statustext', 'text', 'NULL', '', '', '', 
+        'svcnum', 'int', 'NULL', '', '', '', 
+        'secure',  'char', 'NULL', 1, '', '', # Y = needs to be run on machine
+                                              #     w/private key
+      ],
+      'primary_key' => 'jobnum',
+      'unique'      => [],
+      'index'       => [ [ 'svcnum' ], [ 'status' ] ],
+    },
+
+    'queue_arg' => {
+      'columns' => [
+        'argnum', 'serial', '', '', '', '', 
+        'jobnum', 'int', '', '', '', '', 
+        'arg', 'text', 'NULL', '', '', '', 
+      ],
+      'primary_key' => 'argnum',
+      'unique'      => [],
+      'index'       => [ [ 'jobnum' ] ],
+    },
+
+    'queue_depend' => {
+      'columns' => [
+        'dependnum', 'serial', '', '', '', '', 
+        'jobnum', 'int', '', '', '', '', 
+        'depend_jobnum', 'int', '', '', '', '', 
+      ],
+      'primary_key' => 'dependnum',
+      'unique'      => [],
+      'index'       => [ [ 'jobnum' ], [ 'depend_jobnum' ] ],
+    },
+
+    'export_svc' => {
+      'columns' => [
+        'exportsvcnum' => 'serial', '', '', '', '', 
+        'exportnum'    => 'int', '', '', '', '', 
+        'svcpart'      => 'int', '', '', '', '', 
+      ],
+      'primary_key' => 'exportsvcnum',
+      'unique'      => [ [ 'exportnum', 'svcpart' ] ],
+      'index'       => [ [ 'exportnum' ], [ 'svcpart' ] ],
+    },
+
+    'part_export' => {
+      'columns' => [
+        'exportnum', 'serial', '', '', '', '', 
+        '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', '', '', '', '', 
+        'amount',       @money_type, '', '', 
+      ],
+      'primary_key' => 'exemptpkgnum',
+      'unique' => [],
+      'index'  => [ [ 'taxnum', 'year', 'month' ],
+                    [ 'billpkgnum' ],
+                    [ 'taxnum' ]
+                  ],
+    },
+
+    'router' => {
+      'columns' => [
+        'routernum', 'serial', '', '', '', '', 
+        'routername', 'varchar', '', $char_d, '', '', 
+        'svcnum', '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', '', '', '', '', 
+      ],
+      'primary_key' => 'blocknum',
+      'unique'      => [ [ 'blocknum', 'routernum' ] ],
+      'index'       => [],
+    },
+
+    'svc_broadband' => {
+      'columns' => [
+        'svcnum', 'int', '', '', '', '', 
+        'description', 'varchar', 'NULL', $char_d, '', '', 
+        'blocknum', 'int', '', '', '', '', 
+        'speed_up', 'int', '', '', '', '', 
+        'speed_down', 'int', '', '', '', '', 
+        'ip_addr', 'varchar', '', 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, '', '', 
+      ],
+      'primary_key' => 'svcnum',
+      'unique'      => [],
+      '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' ] ],
+    },
+
+    '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',     '', '', '', '', 
+        #'min_charge',      @money_type, '', '', 
+        'min_charge',      'decimal', '', '10,5', '', '', 
+        'sec_granularity', 'int',     '', '', '', '', 
+        #time period (link to table of periods)?
+      ],
+      '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', 6, '', '', 
+        'nxx',         'varchar', 'NULL', 3, '', '', 
+      ],
+      'primary_key' => 'prefixnum',
+      'unique'      => [],
+      'index'       => [ [ 'countrycode' ], [ 'regionnum' ] ],
+    },
+
+    '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_module',   'varchar',  '',     $char_d, '', '', 
+        'gateway_username', 'varchar',  'NULL', $char_d, '', '', 
+        'gateway_password', 'varchar',  'NULL', $char_d, '', '', 
+        'gateway_action',   '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',  '',     32, '', '', 
+        'reason',  'varchar',  'NULL', $char_d, '', '', 
+      ],
+      'primary_key' => 'bannum',
+      'unique'      => [ [ 'payby', 'payinfo' ] ],
+      'index'       => [],
+    },
+
+    'pkg_class' => {
+      'columns' => [
+        'classnum',   'serial',  '', '', '', '', 
+        'classname',  'varchar', '', $char_d, '', '', 
+        '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
+        ###
+
+        #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,2', '', '',
+
+        '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,   '', '', 
+
+      ],
+      'primary_key' => 'acctid',
+      'unique' => [],
+      'index' => [ [ 'calldate' ], [ 'dst' ], [ 'accountcode' ], [ 'freesidestatus' ] ],
+    },
+
+    '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'       => [],
+    },
+
+    #map upstream rateid to ours...
+    'cdr_upstream_rate' => {
+      'columns' => [
+        'upstreamratenum', 'serial',  '', '', '', '',
+        'upstream_rateid', 'varchar', '', $char_d, '', '', 
+        'ratedetailnum',   'int', 'NULL', '', '', '',
+      ],
+      'primary_key' => 'upstreamratenum', #XXX need a primary key
+      'unique' => [ [ 'upstream_rateid' ] ], #unless we add another field, yeah
+      'index'  => [],
+    },
+
+    'inventory_item' => {
+      'columns' => [
+        'itemnum',  'serial',      '',      '', '', '',
+        'classnum', 'int',         '',      '', '', '',
+        'item',     'varchar',     '', $char_d, '', '',
+        'svcnum',   'int',     'NULL',      '', '', '',
+      ],
+      'primary_key' => 'itemnum',
+      'unique' => [ [ 'classnum', 'item' ] ],
+      'index'  => [ [ 'classnum' ], [ '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, '', '', 
+        'disabled',     'char', 'NULL',   1, '', '', 
+      ],
+      'primary_key' => 'usernum',
+      'unique' => [ [ 'username' ] ],
+      'index'  => [],
+    },
+
+    '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, '', '',
+      ],
+      'primary_key' => 'svcnum',
+      'unique' => [],
+      'index'  => [ [ 'countrycode', 'phonenum' ] ],
+    },
+
+    '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' ] ],
+    },
+    # 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 (file)
index 0000000..4218acf
--- /dev/null
@@ -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 (file)
index 0000000..d265d93
--- /dev/null
@@ -0,0 +1,525 @@
+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 ) {
+
+    my $class = "FS::$table";
+    eval "use $class;";
+    die $@ if $@;
+
+    $class->_populate_initial_data(%opt)
+      if $class->can('_populate_inital_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;
+    }
+
+  }
+
+}
+
+sub initial_data {
+  my %opt = @_;
+
+  #tie my %hash, 'Tie::DxHash', 
+  tie my %hash, 'Tie::IxHash', 
+
+    #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....
+
+  #)
+  ;
+
+  \%hash;
+
+}
+
+sub populate_access {
+
+  use FS::AccessRight;
+  use FS::access_right;
+
+  foreach my $rightname ( FS::AccessRight->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',
+    },
+
+    '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 (file)
index 0000000..a80a827
--- /dev/null
@@ -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 (file)
index 0000000..3a9c7e8
--- /dev/null
@@ -0,0 +1,353 @@
+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;
+
+$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 _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 Links  ON ( Tickets.id    = Links.LocalBase )
+                    JOIN Users  ON ( Tickets.Owner = Users.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_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 $queueid = $cust_main->agent->ticketing_queueid || $default_queueid;
+
+  $self->baseurl.
+  'Ticket/Create.html?'.
+    "Queue=$queueid".
+    "&new-MemberOf=freeside://freeside/cust_main/$custnum".
+    ( $requestors ? '&Requestors='. uri_escape($requestors) : '' )
+    ;
+}
+
+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);
+}
+
+1;
+
diff --git a/FS/FS/TicketSystem/RT_Internal.pm b/FS/FS/TicketSystem/RT_Internal.pm
new file mode 100644 (file)
index 0000000..d24a96c
--- /dev/null
@@ -0,0 +1,29 @@
+package FS::TicketSystem::RT_Internal;
+
+use strict;
+use vars qw( @ISA );
+use FS::UID qw(dbh);
+use FS::CGI qw(popurl);
+use FS::TicketSystem::RT_Libs;
+
+@ISA = qw( FS::TicketSystem::RT_Libs );
+
+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/';
+  }
+}
+
+1;
+
diff --git a/FS/FS/TicketSystem/RT_Libs.pm b/FS/FS/TicketSystem/RT_Libs.pm
new file mode 100644 (file)
index 0000000..aebe8c5
--- /dev/null
@@ -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/UI/Web.pm b/FS/FS/UI/Web.pm
new file mode 100644 (file)
index 0000000..6a97f96
--- /dev/null
@@ -0,0 +1,565 @@
+package FS::UI::Web;
+
+use strict;
+use vars qw($DEBUG @ISA @EXPORT_OK $me);
+use Exporter;
+use FS::Conf;
+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 = str2time($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 = str2time($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 '';
+  my $ahref = svc_url(
+    'ahref'    => 1,
+    'm'        => $m,
+    'action'   => 'view',
+    'part_svc' => $part_svc,
+    'svc'      => $cust_svc,
+  );
+
+  "$ahref$x</A>";
+}
+
+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 %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',
+    'Invoicing email(s)'       => 'invoicing_list_emailonly_scalar',
+    'Payment Type'             => 'payby',
+    'Current Balance'          => 'current_balance',
+  );
+
+  my %header2colormethod = (
+    'Cust. Status' => 'cust_statuscolor',
+  );
+  my %header2style = (
+    'Cust. Status' => 'b',
+  );
+  my %header2align = (
+    'Cust. Status' => 'c',
+  );
+
+  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 {
+
+    my $conf = new FS::Conf;
+    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
+  push @fields,
+    grep { my $field = $_; grep { $_ eq $field } @cust_fields }
+         qw( address1 address2 city state zip daytime night fax payby );
+
+  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 $svc_x = shift;
+  warn "FS::UI::Web::cust_fields called for $svc_x ".
+       "(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 ( $svc_x->custnum ) {
+      warn "  $svc_x -> $_"
+        if $DEBUG > 1;
+      $svc_x->$_(@_);
+    } else {
+      warn "  ($svc_x unlinked)"
+        if $DEBUG > 1;
+      $seen_unlinked++ ? '' : '(unlinked)';
+    }
+  } @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' ) {
+    @return = ( 'progress', $job->statustext );
+  } elsif ( !$job ) { #handle job gone case : job successful
+                      # so close popup, redirect parent window...
+    @return = ( 'complete' );
+  } else {
+    @return = ( 'error', $job ? $job->statustext : $jobnum );
+  }
+
+  objToJson(\@return);
+
+}
+
+1;
+
diff --git a/FS/FS/UI/bytecount.pm b/FS/FS/UI/bytecount.pm
new file mode 100644 (file)
index 0000000..d278dbe
--- /dev/null
@@ -0,0 +1,96 @@
+package FS::UI::bytecount;
+
+use strict;
+use vars qw($DEBUG $me);
+use FS::Conf;
+use Number::Format 1.50;
+
+$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/1000))
+    if ($bc < 1000000);
+  return(sprintf("%.2f Mbytes", $bc/1000000))
+    if ($bc < 1000000000);
+  return(sprintf("%.2f Gbytes", $bc/1000000000));
+}
+
+=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 (file)
index 0000000..065db61
--- /dev/null
@@ -0,0 +1,373 @@
+package FS::UID;
+
+use strict;
+use vars qw(
+  @ISA @EXPORT_OK $cgi $dbh $freeside_uid $user 
+  $conf_dir $secrets $datasrc $db_user $db_pass %callback @callback
+  $driver_name $AutoCommit $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);
+
+$freeside_uid = scalar(getpwnam('freeside'));
+
+$conf_dir = "%%%FREESIDE_CONF%%%";
+
+$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;
+
+  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!" unless checkeuid();
+
+  if ( $FS::CurrentUser::upgrade_hack && $olduser ) {
+    $dbh = &myconnect($olduser);
+  } else {
+    $dbh = &myconnect();
+  }
+
+  use FS::Schema qw(reload_dbdef);
+  reload_dbdef("$conf_dir/dbdef.$datasrc")
+    unless $FS::Schema::setup_hack;
+
+  FS::CurrentUser->load_user($user);
+
+  if ($dbh && ! $callback_hack) {
+    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";
+    }
+  }
+
+  unless($callback_hack) {
+    foreach ( keys %callback ) {
+      &{$callback{$_}};
+      # breaks multi-database installs # delete $callback{$_}; #run once
+    }
+
+    &{$_} foreach @callback;
+  }
+
+  $dbh;
+}
+
+sub myconnect {
+  DBI->connect( getsecrets(@_), { 'AutoCommit'         => 0,
+                                  'ChopBlanks'         => 1,
+                                  'ShowErrorStatement' => 1,
+                                }
+              )
+    or die "DBI->connect error: $DBI::errstr\n";
+}
+
+=item install_callback
+
+A package can install a callback to be run in adminsuidsetup by passing
+a coderef to the FS::UID->install_callback class method.  If adminsuidsetup has
+run already, the callback will also be run immediately.
+
+    $coderef = sub { warn "Hi, I'm returning your call!" };
+    FS::UID->install_callback($coderef);
+
+    install_callback FS::UID sub { 
+      warn "Hi, I'm returning your call!"
+    };
+
+=cut
+
+sub install_callback {
+  my $class = shift;
+  my $callback = shift;
+  push @callback, $callback;
+  &{$callback} if $dbh;
+}
+
+=item cgisuidsetup CGI_object
+
+Takes a single argument, which is a CGI (see L<CGI>) or Apache (see L<Apache>)
+object (CGI::Base is depriciated).  Runs cgisetotaker and then adminsuidsetup.
+
+=cut
+
+sub cgisuidsetup {
+  $cgi=shift;
+  if ( $cgi->isa('CGI::Base') ) {
+    carp "Use of CGI::Base is depriciated";
+  } elsif ( $cgi->isa('Apache') ) {
+
+  } elsif ( ! $cgi->isa('CGI') ) {
+    croak "fatal: unrecognized object $cgi";
+  }
+  cgisetotaker; 
+  adminsuidsetup($user);
+}
+
+=item cgi
+
+Returns the CGI (see L<CGI>) object.
+
+=cut
+
+sub cgi {
+  carp "warning: \$FS::UID::cgi isa Apache" if $cgi->isa('Apache');
+  $cgi;
+}
+
+=item dbh
+
+Returns the DBI database handle.
+
+=cut
+
+sub dbh {
+  $dbh;
+}
+
+=item datasrc
+
+Returns the DBI data source.
+
+=cut
+
+sub datasrc {
+  $datasrc;
+}
+
+=item driver_name
+
+Returns just the driver name portion of the DBI data source.
+
+=cut
+
+sub driver_name {
+  return $driver_name if defined $driver_name;
+  $driver_name = ( split(':', $datasrc) )[1];
+}
+
+sub suidsetup {
+  croak "suidsetup depriciated";
+}
+
+=item getotaker
+
+Returns the current Freeside user.
+
+=cut
+
+sub getotaker {
+  $user;
+}
+
+=item cgisetotaker
+
+Sets and returns the CGI REMOTE_USER.  $cgi should be defined as a CGI.pm
+object (see L<CGI>) or an Apache object (see L<Apache>).  Support for CGI::Base
+and derived classes is depriciated.
+
+=cut
+
+sub cgisetotaker {
+  if ( $cgi && $cgi->isa('CGI::Base') && defined $cgi->var('REMOTE_USER')) {
+    carp "Use of CGI::Base is depriciated";
+    $user = lc ( $cgi->var('REMOTE_USER') );
+  } elsif ( $cgi && $cgi->isa('CGI') && defined $cgi->remote_user ) {
+    $user = lc ( $cgi->remote_user );
+  } elsif ( $cgi && $cgi->isa('Apache') ) {
+    $user = lc ( $cgi->connection->user );
+  } else {
+    die "fatal: Can't get REMOTE_USER! for cgi $cgi - you need to setup ".
+        "Apache user authentication as documented in httemplate/docs/install.html";
+  }
+  $user;
+}
+
+=item checkeuid
+
+Returns true if effective UID is that of the freeside user.
+
+=cut
+
+sub checkeuid {
+  ( $> == $freeside_uid );
+}
+
+=item checkruid
+
+Returns true if the real UID is that of the freeside user.
+
+=cut
+
+sub checkruid {
+  ( $< == $freeside_uid );
+}
+
+=item getsecrets [ USER ]
+
+Sets the user to USER, if supplied.
+Sets and returns the DBI datasource, username and password for this user from
+the `/usr/local/etc/freeside/mapsecrets' file.
+
+=cut
+
+sub getsecrets {
+  my($setuser) = shift;
+  $user = $setuser if $setuser;
+
+  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) = 
+    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 (file)
index 0000000..cb48230
--- /dev/null
@@ -0,0 +1,117 @@
+package FS::Upgrade;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK );
+use Exporter;
+use Tie::IxHash;
+use FS::UID qw( dbh driver_name );
+use FS::Record;
+
+use FS::svc_domain;
+$FS::svc_domain::whois_hack = 1;
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( upgrade );
+
+=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 $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  $FS::UID::AutoCommit = 0;
+
+  my $data = upgrade_data(%opt);
+
+  foreach my $table ( keys %$data ) {
+
+    my $class = "FS::$table";
+    eval "use $class;";
+    die $@ if $@;
+
+    if ( $class->can('_upgrade_data') ) {
+      $class->_upgrade_data(%opt);
+    } 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;
+#    }
+
+  }
+
+  if ( $oldAutoCommit ) {
+    dbh->commit or die dbh->errstr;
+  }
+
+}
+
+
+sub upgrade_data {
+  my %opt = @_;
+
+  tie my %hash, 'Tie::IxHash', 
+
+    #reason type and reasons
+    'reason_type' => [],
+    'reason'      => [],
+
+    #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' => [],
+
+  ;
+
+  \%hash;
+
+}
+
+
+=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 (file)
index 0000000..fb0e5ac
--- /dev/null
@@ -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/access_group.pm b/FS/FS/access_group.pm
new file mode 100644 (file)
index 0000000..b5b693a
--- /dev/null
@@ -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 (file)
index 0000000..3de8fee
--- /dev/null
@@ -0,0 +1,134 @@
+package FS::access_groupagent;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::agent;
+
+@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 } );
+}
+
+=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 (file)
index 0000000..cf9730d
--- /dev/null
@@ -0,0 +1,127 @@
+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;
+}
+
+=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/access_user.pm b/FS/FS/access_user.pm
new file mode 100644 (file)
index 0000000..a755daf
--- /dev/null
@@ -0,0 +1,433 @@
+package FS::access_user;
+
+use strict;
+use vars qw( @ISA $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_usergroup;
+use FS::agent;
+
+@ISA = qw( FS::m2m_Common FS::option_Common FS::Record );
+#@ISA = qw( FS::m2m_Common FS::option_Common );
+
+#kludge htpasswd for now (i hope this bootstraps okay)
+FS::UID->install_callback( sub {
+  my $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;
+    }
+  }
+
+  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_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 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;
+
+  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
+
+Given a right name, returns true if this user has this right (currently via
+group membership, eventually also via user overrides).
+
+=cut
+
+sub access_right {
+  my( $self, $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 rightname = ?
+  ") or die dbh->errstr;
+  $sth->execute($self->usernum, $rightname) or die $sth->errstr;
+  my $row = $sth->fetchrow_arrayref;
+  $row ? $row->[0] : '';
+}
+
+=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 (file)
index 0000000..a445d31
--- /dev/null
@@ -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 (file)
index 0000000..8e83060
--- /dev/null
@@ -0,0 +1,145 @@
+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
+
+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/acct_rt_transaction.pm b/FS/FS/acct_rt_transaction.pm
new file mode 100644 (file)
index 0000000..ef0a275
--- /dev/null
@@ -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 (file)
index 0000000..b4e88bf
--- /dev/null
@@ -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 (executable)
index 0000000..1fb6060
--- /dev/null
@@ -0,0 +1,331 @@
+package FS::addr_block;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch dbh );
+use FS::router;
+use FS::svc_broadband;
+use FS::Conf;
+use NetAddr::IP;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::addr_block - Object methods for addr_block records
+
+=head1 SYNOPSIS
+
+  use FS::addr_block;
+
+  $record = new FS::addr_block \%hash;
+  $record = new FS::addr_block { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::addr_block record describes an address block assigned for broadband 
+access.  FS::addr_block inherits from FS::Record.  The following fields are 
+currently supported:
+
+=over 4
+
+=item blocknum - primary key, used in FS::svc_broadband to associate 
+services to the block.
+
+=item routernum - the router (see FS::router) to which this 
+block is assigned.
+
+=item ip_gateway - the gateway address used by customers within this block.  
+
+=item ip_netmask - the netmask of the block, expressed as an integer.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record.  To add the record to the database, see "insert".
+
+=cut
+
+sub table { 'addr_block'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database.  If there is an error, returns the
+error, otherwise returns false.
+
+sub delete {
+  my $self = shift;
+  return 'Block must be deallocated before deletion'
+    if $self->router;
+
+  $self->SUPER::delete;
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is an error,
+returns the error, otherwise returns false.  Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error =
+    $self->ut_number('routernum')
+    || $self->ut_ip('ip_gateway')
+    || $self->ut_number('ip_netmask')
+  ;
+  return $error if $error;
+
+
+  # A routernum of 0 indicates an unassigned block and is allowed
+  return "Unknown routernum"
+    if ($self->routernum and not $self->router);
+
+  my $self_addr = $self->NetAddr;
+  return "Cannot parse address: ". $self->ip_gateway . '/' . $self->ip_netmask
+    unless $self_addr;
+
+  if (not $self->blocknum) {
+    my @block = grep {
+      my $block_addr = $_->NetAddr;
+      if($block_addr->contains($self_addr) 
+      or $self_addr->contains($block_addr)) { $_; };
+    } qsearch( 'addr_block', {});
+    foreach(@block) {
+      return "Block intersects existing block ".$_->ip_gateway."/".$_->ip_netmask;
+    }
+  }
+
+  $self->SUPER::check;
+}
+
+
+=item router
+
+Returns the FS::router object corresponding to this object.  If the 
+block is unassigned, returns undef.
+
+=cut
+
+sub router {
+  my $self = shift;
+  return qsearchs('router', { routernum => $self->routernum });
+}
+
+=item svc_broadband
+
+Returns a list of FS::svc_broadband objects associated
+with this object.
+
+=cut
+
+sub svc_broadband {
+  my $self = shift;
+  return qsearch('svc_broadband', { blocknum => $self->blocknum });
+}
+
+=item NetAddr
+
+Returns a NetAddr::IP object for this block's address and netmask.
+
+=cut
+
+sub NetAddr {
+  my $self = shift;
+
+  return new NetAddr::IP ($self->ip_gateway, $self->ip_netmask);
+}
+
+=item next_free_addr
+
+Returns a NetAddr::IP object corresponding to the first unassigned address 
+in the block (other than the network, broadcast, or gateway address).  If 
+there are no free addresses, returns false.
+
+=cut
+
+sub next_free_addr {
+  my $self = shift;
+
+  my $conf = new FS::Conf;
+  my @excludeaddr = $conf->config('exclude_ip_addr');
+  
+my @used =
+( (map { $_->NetAddr->addr }
+    ($self,
+     qsearch('svc_broadband', { blocknum => $self->blocknum }))
+  ), @excludeaddr
+);
+
+  my @free = $self->NetAddr->hostenum;
+  while (my $ip = shift @free) {
+    if (not grep {$_ eq $ip->addr;} @used) { return $ip; };
+  }
+
+  '';
+
+}
+
+=item allocate
+
+Allocates this address block to a router.  Takes an FS::router object 
+as an argument.
+
+At present it's not possible to reallocate a block to a different router 
+except by deallocating it first, which requires that none of its addresses 
+be assigned.  This is probably as it should be.
+
+=cut
+
+sub allocate {
+  my ($self, $router) = @_;
+
+  return 'Block is already allocated'
+    if($self->router);
+
+  return 'Block must be allocated to a router'
+    unless(ref $router eq 'FS::router');
+
+  my @svc = $self->svc_broadband;
+  if (@svc) {
+    return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc;
+  }
+
+  my $new = new FS::addr_block {$self->hash};
+  $new->routernum($router->routernum);
+  return $new->replace($self);
+
+}
+
+=item deallocate
+
+Deallocates the block (i.e. sets the routernum to 0).  If any addresses in the 
+block are assigned to services, it fails.
+
+=cut
+
+sub deallocate {
+  my $self = shift;
+
+  my @svc = $self->svc_broadband;
+  if (@svc) {
+    return 'Block has assigned addresses: '. join ', ', map {$_->ip_addr} @svc;
+  }
+
+  my $new = new FS::addr_block {$self->hash};
+  $new->routernum(0);
+  return $new->replace($self);
+}
+
+=item split_block
+
+Splits this address block into two equal blocks, occupying the same space as
+the original block.  The first of the two will also have the same blocknum.
+The gateway address of each block will be set to the first usable address, i.e.
+(network address)+1.  Since this method is designed for use on unallocated
+blocks, this is probably the correct behavior.
+
+(At present, splitting allocated blocks is disallowed.  Anyone who wants to
+implement this is reminded that each split costs three addresses, and any
+customers who were using these addresses will have to be moved; depending on
+how full the block was before being split, they might have to be moved to a
+different block.  Anyone who I<still> wants to implement it is asked to tie it
+to a configuration switch so that site admins can disallow it.)
+
+=cut
+
+sub split_block {
+
+  # We should consider using Attribute::Handlers/Aspect/Hook::LexWrap/
+  # something to atomicize functions, so that we can say 
+  #
+  # sub split_block : atomic {
+  # 
+  # instead of repeating all this AutoCommit verbage in every 
+  # sub that does more than one database operation.
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $self = shift;
+  my $error;
+
+  if ($self->router) {
+    return 'Block is already allocated';
+  }
+
+  #TODO: Smallest allowed block should be a config option.
+  if ($self->NetAddr->masklen() ge 30) {
+    return 'Cannot split blocks with a mask length >= 30';
+  }
+
+  my (@new, @ip);
+  $ip[0] = $self->NetAddr;
+  @ip = map {$_->first()} $ip[0]->split($self->ip_netmask + 1);
+
+  foreach (0,1) {
+    $new[$_] = new FS::addr_block {$self->hash};
+    $new[$_]->ip_gateway($ip[$_]->addr);
+    $new[$_]->ip_netmask($ip[$_]->masklen);
+  }
+
+  $new[1]->blocknum('');
+
+  $error = $new[0]->replace($self);
+  if ($error) {
+    $dbh->rollback;
+    return $error;
+  }
+
+  $error = $new[1]->insert;
+  if ($error) {
+    $dbh->rollback;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  return '';
+}
+
+=item merge
+
+To be implemented.
+
+=back
+
+=head1 BUGS
+
+Minimum block size should be a config option.  It's hardcoded at /30 right
+now because that's the smallest block that makes any sense at all.
+
+=cut
+
+1;
+
diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm
new file mode 100644 (file)
index 0000000..57cc945
--- /dev/null
@@ -0,0 +1,445 @@
+package FS::agent;
+
+use strict;
+use vars qw( @ISA );
+#use Crypt::YAPassGen;
+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;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::agent - Object methods for agent records
+
+=head1 SYNOPSIS
+
+  use FS::agent;
+
+  $record = new FS::agent \%hash;
+  $record = new FS::agent { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  $agent_type = $record->agent_type;
+
+  $hashref = $record->pkgpart_hashref;
+  #may purchase $pkgpart if $hashref->{$pkgpart};
+
+=head1 DESCRIPTION
+
+An FS::agent object represents an agent.  Every customer has an agent.  Agents
+can be used to track things like resellers or salespeople.  FS::agent inherits
+from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item agentnum - primary key (assigned automatically for new agents)
+
+=item agent - Text name of this agent
+
+=item typenum - Agent type.  See L<FS::agent_type>
+
+=item prog - For future use.
+
+=item freq - For future use.
+
+=item disabled - Disabled flag, empty or 'Y'
+
+=item username - Username for the Agent interface
+
+=item _password - Password for the Agent interface
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new agent.  To add the agent to the database, see L<"insert">.
+
+=cut
+
+sub table { 'agent'; }
+
+=item insert
+
+Adds this agent to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this agent from the database.  Only agents with no customers can be
+deleted.  If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  return "Can't delete an agent with customers!"
+    if qsearch( 'cust_main', { 'agentnum' => $self->agentnum } );
+
+  $self->SUPER::delete;
+}
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid agent.  If there is an error,
+returns the error, otherwise returns false.  Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error =
+    $self->ut_numbern('agentnum')
+      || $self->ut_text('agent')
+      || $self->ut_number('typenum')
+      || $self->ut_numbern('freq')
+      || $self->ut_textn('prog')
+      || $self->ut_textn('invoice_template')
+  ;
+  return $error if $error;
+
+  if ( $self->dbdef_table->column('disabled') ) {
+    $error = $self->ut_enum('disabled', [ '', 'Y' ] );
+    return $error if $error;
+  }
+
+  if ( $self->dbdef_table->column('username') ) {
+    $error = $self->ut_alphan('username');
+    return $error if $error;
+    if ( length($self->username) ) {
+      my $conflict = qsearchs('agent', { 'username' => $self->username } );
+      return 'duplicate agent username (with '. $conflict->agent. ')'
+        if $conflict && $conflict->agentnum != $self->agentnum;
+      $error = $self->ut_text('password'); # ut_text... arbitrary choice
+    } else {
+      $self->_password('');
+    }
+  }
+
+  return "Unknown typenum!"
+    unless $self->agent_type;
+
+  $self->SUPER::check;
+}
+
+=item agent_type
+
+Returns the FS::agent_type object (see L<FS::agent_type>) for this agent.
+
+=cut
+
+sub agent_type {
+  my $self = shift;
+  qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
+}
+
+=item pkgpart_hashref
+
+Returns a hash reference.  The keys of the hash are pkgparts.  The value is
+true if this agent may purchase the specified package definition.  See
+L<FS::part_pkg>.
+
+=cut
+
+sub pkgpart_hashref {
+  my $self = shift;
+  $self->agent_type->pkgpart_hashref;
+}
+
+=item 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 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 (file)
index 0000000..bd99d0c
--- /dev/null
@@ -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 (file)
index 0000000..2660bb4
--- /dev/null
@@ -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 (file)
index 0000000..1ad87f5
--- /dev/null
@@ -0,0 +1,136 @@
+package FS::banned_pay;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::UID qw( getotaker );
+
+@ISA = qw(FS::Record);
+
+=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);
+
+  $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.pm b/FS/FS/cdr.pm
new file mode 100644 (file)
index 0000000..5078ff6
--- /dev/null
@@ -0,0 +1,672 @@
+package FS::cdr;
+
+use strict;
+use vars qw( @ISA );
+use Date::Parse;
+use Date::Format;
+use Time::Local;
+use FS::UID qw( dbh );
+use FS::Record qw( qsearch qsearchs );
+use FS::cdr_type;
+use FS::cdr_calltype;
+use FS::cdr_carrier;
+use FS::cdr_upstream_rate;
+
+@ISA = qw(FS::Record);
+
+=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)
+
+=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'; }
+
+=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')
+#  ;
+#  return $error if $error;
+
+  $self->calldate( $self->startdate_sql )
+    if !$self->calldate && $self->startdate;
+
+  unless ( $self->charged_party ) {
+    if ( $self->dst =~ /^(\+?1)?8[02-8]{2}/ ) {
+      $self->charged_party($self->dst);
+    } else {
+      $self->charged_party($self->src);
+    }
+  }
+
+  #check the foreign keys even?
+  #do we want to outright *reject* the CDR?
+  my $error =
+       $self->ut_numbern('acctid')
+
+    #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 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->freesidestatus($status);
+  $self->rated_price($rated_price);
+  $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 cdr_upstream_rate
+
+Returns the upstream rate mapping (see L<FS::cdr_upstream_rate>), or the empty
+string if no FS::cdr_upstream_rate object is associated with this CDR.
+
+=cut
+
+sub cdr_upstream_rate {
+  my $self = shift;
+  return '' unless $self->upstream_rateid;
+  qsearchs('cdr_upstream_rate', { 'upstream_rateid' => $self->upstream_rateid })
+    or '';
+}
+
+=item _convergent_format COLUMN [ COUNTRYCODE ]
+
+Returns the number in COLUMN formatted as follows:
+
+If the country code does not match COUNTRYCODE (default "61"), it is returned
+unchanged.
+
+If the country code does match COUNTRYCODE (default "61"), it is removed.  In
+addiiton, "0" is prepended unless the number starts with 13, 18 or 19. (???)
+
+=cut
+
+sub _convergent_format {
+  my( $self, $field ) = ( shift, shift );
+  my $countrycode = scalar(@_) ? shift : '61'; #+61 = australia
+  #my $number = $self->$field();
+  my $number = $self->get($field);
+  #if ( $number =~ s/^(\+|011)$countrycode// ) {
+  if ( $number =~ s/^\+$countrycode// ) {
+    $number = "0$number"
+      unless $number =~ /^1[389]/; #???
+  }
+  $number;
+}
+
+=item downstream_csv [ OPTION => VALUE, ... ]
+
+=cut
+
+my %export_formats = (
+  'convergent' => [
+    'carriername', #CARRIER
+    sub { shift->_convergent_format('src') }, #SERVICE_NUMBER
+    sub { shift->_convergent_format('charged_party') }, #CHARGED_NUMBER
+    sub { time2str('%Y-%m-%d', shift->calldate_unix ) }, #DATE
+    sub { time2str('%T',       shift->calldate_unix ) }, #TIME
+    'billsec', #'duration', #DURATION
+    sub { shift->_convergent_format('dst') }, #NUMBER_DIALED
+    '', #XXX add (from prefixes in most recent email) #FROM_DESC
+    '', #XXX add (from prefixes in most recent email) #TO_DESC
+    'calltypename', #CLASS_CODE
+    'rated_price', #PRICE
+    sub { shift->rated_price ? 'Y' : 'N' }, #RATED
+    '', #OTHER_INFO
+  ],
+);
+
+sub downstream_csv {
+  my( $self, %opt ) = @_;
+
+  my $format = $opt{'format'}; # 'convergent';
+  return "Unknown format $format" unless exists $export_formats{$format};
+
+  eval "use Text::CSV_XS;";
+  die $@ if $@;
+  my $csv = new Text::CSV_XS;
+
+  my @columns =
+    map {
+          ref($_) ? &{$_}($self) : $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 batch_import
+
+=cut
+
+my($tmp_mday, $tmp_mon, $tmp_year);
+
+sub _cdr_date_parser_maker {
+  my $field = shift;
+  return sub {
+    my( $cdr, $date ) = @_;
+    $cdr->$field( _cdr_date_parse($date) );
+  };
+}
+
+sub _cdr_date_parse {
+  my $date = shift;
+
+  return '' unless length($date); #that's okay, it becomes NULL
+
+  #$date =~ /^\s*(\d{4})[\-\/]\(\d{1,2})[\-\/](\d{1,2})\s+(\d{1,2}):(\d{1,2}):(\d{1,2})\s*$/
+  $date =~ /^\s*(\d{4})\D(\d{1,2})\D(\d{1,2})\s+(\d{1,2})\D(\d{1,2})\D(\d{1,2})\s*$/
+    or die "unparsable date: $date"; #maybe we shouldn't die...
+  my($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+
+  timelocal($sec, $min, $hour, $day, $mon-1, $year);
+}
+
+#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,
+);
+
+my %import_formats = (
+  'asterisk' => [
+    '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',
+  ],
+  'unitel' => [
+    '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',
+  ],
+  'simple' => [
+
+    # Date
+    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
+    sub { my($cdr, $min) = @_;
+          my $sec = sprintf('%.0f', $min * 60 );
+          $cdr->billsec(  $sec );
+          $cdr->duration( $sec );
+        },
+
+  ],
+);
+
+sub batch_import {
+  my $param = shift;
+
+  my $fh = $param->{filehandle};
+  my $format = $param->{format};
+
+  return "Unknown format $format" unless exists $import_formats{$format};
+
+  eval "use Text::CSV_XS;";
+  die $@ if $@;
+
+  my $csv = new Text::CSV_XS;
+
+  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;
+
+  if ( $format eq 'simple' ) { # and other formats with a header too?
+
+  }
+
+  my $body = 0;
+  my $line;
+  while ( defined($line=<$fh>) ) {
+
+    #skip header...
+    if ( ! $body++ && $format eq 'simple' && $line =~ /^[\w\, ]+$/ ) {
+      next;
+    }
+
+    $csv->parse($line) or do {
+      $dbh->rollback if $oldAutoCommit;
+      return "can't parse: ". $csv->error_input();
+    };
+
+    my @columns = $csv->fields();
+    #warn join('-',@columns);
+
+    if ( $format eq 'simple' ) {
+      @columns = map { s/^ +//; $_; } @columns;
+    }
+
+    my @later = ();
+    my %cdr =
+      map {
+
+        my $field_or_sub = $_;
+        if ( ref($field_or_sub) ) {
+          push @later, $field_or_sub, shift(@columns);
+          ();
+        } else {
+          ( $field_or_sub => shift @columns );
+        }
+
+      }
+      @{ $import_formats{$format} }
+    ;
+
+    my $cdr = new FS::cdr ( \%cdr );
+
+    while ( scalar(@later) ) {
+      my $sub = shift @later;
+      my $data = shift @later;
+      &{$sub}($cdr, $data);  # $cdr->&{$sub}($data); 
+    }
+
+    my $error = $cdr->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+
+      #or just skip?
+      #next;
+    }
+
+    $imported++;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  #might want to disable this if we skip records for any reason...
+  return "Empty file!" unless $imported;
+
+  '';
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+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 (file)
index 0000000..fe45608
--- /dev/null
@@ -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 (file)
index 0000000..609c939
--- /dev/null
@@ -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_type.pm b/FS/FS/cdr_type.pm
new file mode 100644 (file)
index 0000000..e258bf8
--- /dev/null
@@ -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/cdr_upstream_rate.pm b/FS/FS/cdr_upstream_rate.pm
new file mode 100644 (file)
index 0000000..2fd9782
--- /dev/null
@@ -0,0 +1,138 @@
+package FS::cdr_upstream_rate;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::rate_detail;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cdr_upstream_rate - Object methods for cdr_upstream_rate records
+
+=head1 SYNOPSIS
+
+  use FS::cdr_upstream_rate;
+
+  $record = new FS::cdr_upstream_rate \%hash;
+  $record = new FS::cdr_upstream_rate { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cdr_upstream_rate object represents an upstream rate mapping to 
+internal rate detail (see L<FS::rate_detail>).  FS::cdr_upstream_rate inherits
+from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item upstreamratenum - primary key
+
+=item upstream_rateid - CDR upstream Rate ID (cdr.upstream_rateid - see L<FS::cdr>)
+
+=item ratedetailnum - Rate detail - see L<FS::rate_detail>
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new upstream rate mapping.  To add the upstream 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 { 'cdr_upstream_rate'; }
+
+=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 upstream 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('upstreamratenum')
+    #|| $self->ut_number('upstream_rateid')
+    || $self->ut_alpha('upstream_rateid')
+    #|| $self->ut_text('upstream_rateid')
+    || $self->ut_foreign_key('ratedetailnum', 'rate_detail', 'ratedetailnum' )
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item rate_detail
+
+Returns the internal rate detail object for this upstream rate (see
+L<FS::rate_detail>).
+
+=cut
+
+sub rate_detail {
+  my $self = shift;
+  qsearchs('rate_detail', { 'ratedetailnum' => $self->ratedetailnum } );
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/clientapi_session.pm b/FS/FS/clientapi_session.pm
new file mode 100644 (file)
index 0000000..f71a126
--- /dev/null
@@ -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 (file)
index 0000000..bfa487d
--- /dev/null
@@ -0,0 +1,126 @@
+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
+
+The author forgot to customize this manpage.
+
+=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 (file)
index 0000000..6126372
--- /dev/null
@@ -0,0 +1,114 @@
+package FS::conf;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@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/cust_bill.pm b/FS/FS/cust_bill.pm
new file mode 100644 (file)
index 0000000..ee95be8
--- /dev/null
@@ -0,0 +1,2862 @@
+package FS::cust_bill;
+
+use strict;
+use vars qw( @ISA $DEBUG $me $conf $money_char );
+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 FS::UID qw( datasrc );
+use FS::Misc qw( send_email send_fax generate_ps do_print );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_main_Mixin;
+use FS::cust_main;
+use FS::cust_bill_pkg;
+use FS::cust_credit;
+use FS::cust_pay;
+use FS::cust_pkg;
+use FS::cust_credit_bill;
+use FS::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') || '$';  
+} );
+
+=head1 NAME
+
+FS::cust_bill - Object methods for cust_bill records
+
+=head1 SYNOPSIS
+
+  use FS::cust_bill;
+
+  $record = new FS::cust_bill \%hash;
+  $record = new FS::cust_bill { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  ( $total_previous_balance, @previous_cust_bill ) = $record->previous;
+
+  @cust_bill_pkg_objects = $cust_bill->cust_bill_pkg;
+
+  ( $total_previous_credits, @previous_cust_credit ) = $record->cust_credit;
+
+  @cust_pay_objects = $cust_bill->cust_pay;
+
+  $tax_amount = $record->tax;
+
+  @lines = $cust_bill->print_text;
+  @lines = $cust_bill->print_text $time;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill object represents an invoice; a declaration that a customer
+owes you money.  The specific charges are itemized as B<cust_bill_pkg> records
+(see L<FS::cust_bill_pkg>).  FS::cust_bill inherits from FS::Record.  The
+following fields are currently supported:
+
+=over 4
+
+=item invnum - primary key (assigned automatically for new invoices)
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item charged - amount of this invoice
+
+=item printed - deprecated
+
+=item closed - books closed flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new invoice.  To add the invoice to the database, see L<"insert">.
+Invoices are normally created by calling the bill method of a customer object
+(see L<FS::cust_main>).
+
+=cut
+
+sub table { 'cust_bill'; }
+
+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;
+  $self->SUPER::delete(@_);
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+Only printed may be changed.  printed is normally updated by calling the
+collect method of a customer object (see L<FS::cust_main>).
+
+=cut
+
+#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_number('custnum')
+    || $self->ut_numbern('_date')
+    || $self->ut_money('charged')
+    || $self->ut_numbern('printed')
+    || $self->ut_enum('closed', [ '', 'Y' ])
+  ;
+  return $error if $error;
+
+  return "Unknown customer"
+    unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+  $self->_date(time) unless $self->_date;
+
+  $self->printed(0) if $self->printed eq '';
+
+  $self->SUPER::check;
+}
+
+=item previous
+
+Returns a list consisting of the total previous balance for this customer, 
+followed by the previous outstanding invoices (as FS::cust_bill objects also).
+
+=cut
+
+sub previous {
+  my $self = shift;
+  my $total = 0;
+  my @cust_bill = sort { $a->_date <=> $b->_date }
+    grep { $_->owed != 0 && $_->_date < $self->_date }
+      qsearch( 'cust_bill', { 'custnum' => $self->custnum } ) 
+  ;
+  foreach ( @cust_bill ) { $total += $_->owed; }
+  $total, @cust_bill;
+}
+
+=item cust_bill_pkg
+
+Returns the line items (see L<FS::cust_bill_pkg>) for this invoice.
+
+=cut
+
+sub cust_bill_pkg {
+  my $self = shift;
+  qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum } );
+}
+
+=item cust_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 { $_->cust_pkg } $self->cust_bill_pkg;
+  my %saw = ();
+  grep { ! $saw{$_->pkgnum}++ } @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 } )
+  #;
+}
+
+=item cust_bill_pay
+
+Returns all payment applications (see L<FS::cust_bill_pay>) for this invoice.
+
+=cut
+
+sub cust_bill_pay {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_bill_pay', { 'invnum' => $self->invnum } );
+}
+
+=item cust_credited
+
+Returns all applied credits (see L<FS::cust_credit_bill>) for this invoice.
+
+=cut
+
+sub cust_credited {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_credit_bill', { 'invnum' => $self->invnum } )
+  ;
+}
+
+=item tax
+
+Returns the tax amount (see L<FS::cust_bill_pkg>) for this invoice.
+
+=cut
+
+sub tax {
+  my $self = shift;
+  my $total = 0;
+  my @taxlines = qsearch( 'cust_bill_pkg', { 'invnum' => $self->invnum ,
+                                             'pkgnum' => 0 } );
+  foreach (@taxlines) { $total += $_->setup; }
+  $total;
+}
+
+=item owed
+
+Returns the amount owed (still outstanding) on this invoice, which is charged
+minus all payment applications (see L<FS::cust_bill_pay>) and credit
+applications (see L<FS::cust_credit_bill>).
+
+=cut
+
+sub owed {
+  my $self = shift;
+  my $balance = $self->charged;
+  $balance -= $_->amount foreach ( $self->cust_bill_pay );
+  $balance -= $_->amount foreach ( $self->cust_credited );
+  $balance = sprintf( "%.2f", $balance);
+  $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+  $balance;
+}
+
+=item apply_payments_and_credits
+
+=cut
+
+sub apply_payments_and_credits {
+  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->select_for_update; #mutex
+
+  my @payments = grep { $_->unapplied > 0 } $self->cust_main->cust_pay;
+  my @credits  = grep { $_->credited > 0 } $self->cust_main->cust_credit;
+
+  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";
+    }
+
+    if ( $app eq 'pay' ) {
+
+      my $payment = shift @payments;
+
+      $app = new FS::cust_bill_pay {
+        'paynum'  => $payment->paynum,
+       'amount'  => sprintf('%.2f', min( $payment->unapplied, $self->owed ) ),
+      };
+
+    } elsif ( $app eq 'credit' ) {
+
+      my $credit = shift @credits;
+
+      $app = new FS::cust_credit_bill {
+        'crednum' => $credit->crednum,
+       'amount'  => sprintf('%.2f', min( $credit->credited, $self->owed ) ),
+      };
+
+    } else {
+      die "guru meditation #12 and 35";
+    }
+
+    $app->invnum( $self->invnum );
+
+    my $error = $app->insert;
+    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 PARAMHASH
+
+PARAMHASH can contain the following:
+
+=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
+
+=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'),
+  );
+
+  if (ref($args{'to'}) eq 'ARRAY') {
+    $return{'to'} = $args{'to'};
+  } else {
+    $return{'to'} = [ grep { $_ !~ /^(POST|FAX)$/ }
+                           $self->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('', $args{'template'}) ];
+      }
+
+    }
+
+    $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 $path = "$FS::UID::conf_dir/conf.$FS::UID::datasrc";
+    my $file;
+    if ( defined($args{'template'}) && length($args{'template'})
+         && -e "$path/logo_". $args{'template'}. ".png"
+       )
+    {
+      $file = "$path/logo_". $args{'template'}. ".png";
+    } else {
+      $file = "$path/logo.png";
+    }
+
+    my $image = build MIME::Entity
+      'Type'       => 'image/png',
+      'Encoding'   => 'base64',
+      'Path'       => $file,
+      '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('', $args{'template'}, $content_id),
+                         '  </body>',
+                         '</html>',
+                       ],
+      'Disposition' => 'inline',
+      #'Filename'    => 'invoice.pdf',
+    );
+
+    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('', $args{'template'});
+
+      $return{'mimeparts'} = [ $related, $pdf ];
+
+    } else {
+
+      #no other attachment:
+      # multipart/related
+      #   multipart/alternative
+      #     text/plain
+      #     text/html
+      #   image/png
+
+      $return{'content-type'} = 'multipart/related';
+      $return{'mimeparts'} = [ $alternative, $image ];
+      $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('', $args{'template'}) }
+      ];
+    }
+  
+    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('', $args{'template'}) ];
+      }
+
+    }
+
+  }
+
+  %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.pdf',
+  );
+}
+
+=item send [ TEMPLATENAME [ , AGENTNUM [ , INVOICE_FROM ] ] ]
+
+Sends this invoice to the destinations configured for this customer: sends
+email, prints and/or faxes.  See L<FS::cust_main_invoice>.
+
+TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
+
+AGENTNUM, if specified, means that this invoice will only be sent for customers
+of the specified agent or agent(s).  AGENTNUM can be a scalar agentnum (for a
+single agent) or an arrayref of agentnums.
+
+INVOICE_FROM, if specified, overrides the default email invoice From: address.
+
+=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 = scalar(@_) ? shift : '';
+  if ( scalar(@_) && $_[0]  ) {
+    my $agentnums = ref($_[0]) ? shift : [ shift ];
+    return 'N/A' unless grep { $_ == $self->cust_main->agentnum } @$agentnums;
+  }
+
+  my $invoice_from =
+    scalar(@_)
+      ? shift
+      : ( $self->_agent_invoice_from || $conf->config('invoice_from') );
+
+  my @invoicing_list = $self->cust_main->invoicing_list;
+
+  $self->email($template, $invoice_from)
+    if grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list or !@invoicing_list;
+
+  $self->print($template)
+    if grep { $_ eq 'POST' } @invoicing_list; #postal
+
+  $self->fax($template)
+    if grep { $_ eq 'FAX' } @invoicing_list; #fax
+
+  '';
+
+}
+
+=item email [ TEMPLATENAME  [ , INVOICE_FROM ] ] 
+
+Emails this invoice.
+
+TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
+
+INVOICE_FROM, if specified, overrides the default email invoice From: address.
+
+=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 {
+  my $self = shift;
+  my $template = scalar(@_) ? shift : '';
+  my $invoice_from =
+    scalar(@_)
+      ? shift
+      : ( $self->_agent_invoice_from || $conf->config('invoice_from') );
+
+  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 $error = send_email(
+    $self->generate_email(
+      'from'       => $invoice_from,
+      'to'         => [ grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ],
+      'template'   => $template,
+    )
+  );
+  die "can't email invoice: $error\n" if $error;
+  #die "$error\n" if $error;
+
+}
+
+=item lpr_data [ TEMPLATENAME ]
+
+Returns the postscript or plaintext for this invoice as an arrayref.
+
+TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
+
+=cut
+
+sub lpr_data {
+  my( $self, $template) = @_;
+  $conf->exists('invoice_latex')
+    ? [ $self->print_ps('', $template) ]
+    : [ $self->print_text('', $template) ];
+}
+
+=item print [ TEMPLATENAME ]
+
+Prints this invoice.
+
+TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
+
+=cut
+
+sub print {
+  my $self = shift;
+  my $template = scalar(@_) ? shift : '';
+
+  do_print $self->lpr_data($template);
+}
+
+=item fax [ TEMPLATENAME ] 
+
+Faxes this invoice.
+
+TEMPLATENAME, if specified, is the name of a suffix for alternate invoices.
+
+=cut
+
+sub fax {
+  my $self = shift;
+  my $template = scalar(@_) ? shift : '';
+
+  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 $error = send_fax( 'docdata'    => $self->lpr_data($template),
+                        'dialstring' => $dialstring,
+                      );
+  die $error if $error;
+
+}
+
+=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->cust_pkg->part_pkg->pkg,
+          ( $cust_bill_pkg->setup != 0
+            ? sprintf("%.2f", $cust_bill_pkg->setup )
+            : '' ),
+          ( $cust_bill_pkg->recur != 0
+            ? sprintf("%.2f", $cust_bill_pkg->recur )
+            : '' ),
+          ( $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;
+        my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc')
+                         ? ( $cust_bill_pkg->itemdesc || 'Tax' )
+                         : 'Tax';
+        ($pkg, $setup, $recur, $sdate, $edate) =
+          ( $itemdesc, sprintf("%10.2f",$cust_bill_pkg->setup), '', '', '' );
+      }
+  
+      $csv->combine(
+        'cust_bill_pkg',
+        $self->invnum,
+        ( map { '' } (1..11) ),
+        ($pkg, $setup, $recur, $sdate, $edate)
+      ) or die "can't create csv";
+
+      $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 { $_->cust_pkg->part_pkg->pkg }
+        grep { $_->pkgnum } $self->cust_bill_pkg
+    );
+    $description = eval qq("$dtempl");
+  }
+
+  $cust_main->realtime_bop($method, $amount,
+    'description' => $description,
+    'invnum'      => $self->invnum,
+  );
+
+}
+
+=item batch_card 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 [ TIME [ , TEMPLATE ] ]
+
+Returns an text invoice, as a list of lines.
+
+TIME an optional value used to control the printing of overdue messages.  The
+default is now.  It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub print_text {
+  my( $self, $today, $template ) = @_;
+
+  my %params = ( 'format' => 'template' );
+  $params{'time'} = $today if $today;
+  $params{'template'} = $template if $template;
+
+  $self->print_generic( %params );
+}
+
+=item print_latex [ TIME [ , TEMPLATE ] ]
+
+Internal method - returns a filename of a filled-in LaTeX template for this
+invoice (Note: add ".tex" to get the actual filename), 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.
+
+TIME an optional value used to control the printing of overdue messages.  The
+default is now.  It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub print_latex {
+
+  my( $self, $today, $template ) = @_;
+
+  my %params = ( 'format' => 'latex' );
+  $params{'time'} = $today if $today;
+  $params{'template'} = $template if $template;
+
+  $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";
+
+  if ($template && $conf->exists("logo_${template}.eps")) {
+    print $lh $conf->config_binary("logo_${template}.eps")
+      or die "can't write temp file: $!\n";
+  }else{
+    print $lh $conf->config_binary('logo.eps')
+      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 OPTIONS_HASH
+
+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 - 
+
+=cut
+
+sub print_generic {
+
+  my( $self, %params ) = @_;
+  my $today = $params{today} ? $params{today} : time;
+  warn "FS::cust_bill::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 !~ /^(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 file $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 'While compiling ' . $templatefile . ': ' . $Text::Template::ERROR;
+
+
+  # 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 "$_", @_ },
+               },
+    '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/\\\\\*/ /;
+                       s/\\dollar ?/\$/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\-]+}//;
+                       $_;
+                     }  @_
+                   },
+               },
+    '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\-]+}//;
+                       $_;
+                     }  @_
+                   },
+               },
+  );
+
+
+  # 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') ) {
+
+    $returnaddress = join( "\n", $conf->config('company_address') );
+
+    $returnaddress =
+      join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg,
+                            $conf->config('company_address')
+          )
+        if $format eq 'latex';
+
+  } 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 = (
+    'company_name'    => scalar( $conf->config('company_name') ),
+    'company_address' => join("\n", $conf->config('company_address') ). "\n",
+    'custnum'         => $self->custnum,
+    'invnum'          => $self->invnum,
+    'date'            => time2str($date_format, $self->_date),
+    'today'           => time2str('%b %o, %Y', $today),
+    'agent'           => &$escape_function($cust_main->agent->agent),
+    'payname'         => &$escape_function($cust_main->payname),
+    'company'         => &$escape_function($cust_main->company),
+    'address1'        => &$escape_function($cust_main->address1),
+    'address2'        => &$escape_function($cust_main->address2),
+    'city'            => &$escape_function($cust_main->city),
+    'state'           => &$escape_function($cust_main->state),
+    'zip'             => &$escape_function($cust_main->zip),
+    'returnaddress'   => $returnaddress,
+    'quantity'        => 1,
+    'terms'           => $self->terms,
+    'template'        => $params{'template'},
+    #'notes'           => join("\n", $conf->config('invoice_latexnotes') ),
+    # better hang on to conf_dir for a while
+    'conf_dir'        => "$FS::UID::conf_dir/conf.$FS::UID::datasrc",
+    'page'            => 1,
+    'total_pages'     => 1,
+  );
+
+  $invoice_data{'cid'} = $params{'cid'}
+    if $params{'cid'};
+
+  my $countrydefault = $conf->config('countrydefault') || 'US';
+  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);
+
+  #do variable substitution in notes, footer, smallfooter
+  foreach my $include (qw( notes footer smallfooter )) {
+
+    my @inc_src = $conf->config_orbase("invoice_latex$include", $template );
+    my $convert_map = $convert_maps{$format}{$include};
+
+    if (
+           defined( $conf->config_orbase("invoice_${format}$include", $template) )
+        && length(  $conf->config_orbase('invoice_${format}$include', $template) )
+    ) {
+      @inc_src = $conf->config_orbase("invoice_${format}$include", $template );
+    } else {
+      @inc_src =
+        map { s/\[@--/$delimiters{$format}[0]/g;
+              s/--@]/$delimiters{$format}[1]/g;
+              $_;
+            } 
+        &$convert_map(
+                       $conf->config_orbase("invoice_latex$include", $template )
+                     );
+    }
+
+    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";
+
+    $inc_tt->compile()
+      or die "can't compile template: $Text::Template::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( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
+#  my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
+  #my $balance_due = $self->owed + $pr_total - $cr_total;
+  my $balance_due = $self->owed + $pr_total;
+
+  my %money_chars = ( 'latex'    => '',
+                      'html'     => $conf->config('money_char') || '$',
+                      'template' => '',
+                    );
+  my $money_char = $money_chars{$format};
+
+  my %other_money_chars = ( 'latex'    => '\dollar ',
+                            'html'     => $conf->config('money_char') || '$',
+                            'template' => '',
+                          );
+  my $other_money_char = $other_money_chars{$format};
+
+  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),
+                         };
+
+  my $multisection = $conf->exists('invoice_sections', $cust_main->agentnum);
+  if ( $multisection ) {
+    push @sections, $self->_items_sections;
+  }else{
+    push @sections, { 'description' => '', 'subtotal' => '' };
+  }
+
+  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'}};
+    }
+    {
+      my $money = $old_latex ? '' : $money_char;
+      $detail->{'amount'} = $money. $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) {
+    push @buf, ['','-----------'];
+    push @buf, [ 'Total Previous Balance',
+                 $money_char. sprintf("%10.2f", $pr_total) ];
+    push @buf, ['',''];
+  }
+
+  foreach my $section (@sections) {
+
+    $section->{'subtotal'} = $other_money_char.
+                             sprintf('%.2f', $section->{'subtotal'})
+      if $multisection;
+
+    if ( $section->{'description'} ) {
+      push @buf, ( [ &$escape_function($section->{'description'}), '' ],
+                   [ '', '' ],
+                 );
+    }
+
+    my %options = ();
+    $options{'section'} = $section if $multisection;
+
+    foreach my $line_item ( $self->_items_pkg(%options) ) {
+      my $detail = {
+        ext_description => [],
+      };
+      $detail->{'ref'} = $line_item->{'pkgnum'};
+      $detail->{'quantity'} = 1;
+      $detail->{'section'} = $section;
+      $detail->{'description'} = &$escape_function($line_item->{'description'});
+      if ( exists $line_item->{'ext_description'} ) {
+        @{$detail->{'ext_description'}} = map {
+          &$escape_function($_);
+        } @{$line_item->{'ext_description'}};
+      }
+      {
+        my $money = $old_latex ? '' : $money_char;
+        $detail->{'amount'} = $money. $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'}),
+                   ],
+                   map { [ " ". $_, '' ] } @{$detail->{'ext_description'}},
+                 );
+    }
+
+    if ( $section->{'description'} ) {
+      push @buf, ( ['','-----------'],
+                   [ $section->{'description'}. ' sub-total',
+                      $money_char. sprintf("%10.2f", $section->{'subtotal'})
+                   ],
+                   [ '', '' ],
+                   [ '', '' ],
+                 );
+    }
+  
+  }
+  
+  if ( $multisection ) {
+    unshift @sections, $previous_section;
+  }
+
+  my $taxtotal = 0;
+  foreach my $tax ( $self->_items_tax ) {
+    my $total = {};
+    $total->{'total_item'} = &$escape_function($tax->{'description'});
+    $taxtotal += $tax->{'amount'};
+    $total->{'total_amount'} = $other_money_char. $tax->{'amount'};
+    push @total_items, $total;
+    push @buf,[ $total->{'total_item'},
+                $money_char. sprintf("%10.2f", $total->{'total_amount'}),
+              ];
+
+  }
+  
+  if ( $taxtotal ) {
+    my $total = {};
+    if ( $multisection ) {
+      $total->{'total_item'} = 'New charges sub-total';
+    }else{
+      $total->{'total_item'} = 'Sub-total';
+    }
+    $total->{'total_amount'} =
+      $other_money_char. sprintf('%.2f', $self->charged - $taxtotal );
+    unshift @total_items, $total;
+  }
+  
+  push @buf,['','-----------'];
+  push @buf,['Total New Charges',
+             $money_char. sprintf("%10.2f",$self->charged) ];
+  push @buf,['',''];
+
+  {
+    my $total = {};
+    $total->{'total_item'} = &$embolden_function('Total');
+    $total->{'total_amount'} =
+    $total->{'total_amount'} =
+      &$embolden_function(
+        $other_money_char.  sprintf('%.2f', $self->charged + $pr_total )
+      );
+    push @total_items, $total;
+    push @buf,['','-----------'];
+    push @buf,['Total Charges',
+               $money_char. sprintf("%10.2f",$self->charged + $pr_total) ];
+    push @buf,['',''];
+  }
+  
+
+  #foreach my $thing ( sort { $a->_date <=> $b->_date } $self->_items_credits, $self->_items_payments
+  
+  # credits
+  foreach my $credit ( $self->_items_credits ) {
+    my $total;
+    $total->{'total_item'} = &$escape_function($credit->{'description'});
+    #$credittotal
+    $total->{'total_amount'} = '-'. $other_money_char. $credit->{'amount'};
+    push @total_items, $total;
+  }
+  
+  # credits (again)
+  foreach ( $self->cust_credited ) {
+
+    #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+
+    my $reason = substr($_->cust_credit->reason,0,32);
+    $reason .= '...' if length($reason) < length($_->cust_credit->reason);
+    $reason = " ($reason) " if $reason;
+    push @buf,[
+      "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")".        $reason,
+      $money_char. sprintf("%10.2f",$_->amount)
+    ];
+  }
+
+  # payments
+  foreach my $payment ( $self->_items_payments ) {
+    my $total = {};
+    $total->{'total_item'} = &$escape_function($payment->{'description'});
+    #$paymenttotal
+    $total->{'total_amount'} = '-'. $other_money_char. $payment->{'amount'};
+    push @total_items, $total;
+    push @buf, [ $payment->{'description'},
+                 $money_char. sprintf("%10.2f", $payment->{'amount'}),
+               ];
+  }
+  
+  { 
+    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 )
+      );
+    push @total_items, $total;
+    push @buf,['','-----------'];
+    push @buf,[$self->balance_due_msg, $money_char. 
+      sprintf("%10.2f", $balance_due ) ];
+  }
+
+  $invoice_data{'logo_file'} = $params{'logo_file'}
+    if $params{'logo_file'};
+
+  $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);
+  }
+}
+
+=item print_ps [ TIME [ , TEMPLATE ] ]
+
+Returns an postscript invoice, as a scalar.
+
+TIME an optional value used to control the printing of overdue messages.  The
+default is now.  It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub print_ps {
+  my $self = shift;
+
+  my ($file, $lfile) = $self->print_latex(@_);
+  my $ps = generate_ps($file);
+  unlink($lfile);
+  $ps;
+
+}
+
+=item print_pdf [ TIME [ , TEMPLATE ] ]
+
+Returns an PDF invoice, as a scalar.
+
+TIME an optional value used to control the printing of overdue messages.  The
+default is now.  It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub print_pdf {
+  my $self = shift;
+
+  my ($file, $lfile) = $self->print_latex(@_);
+
+  my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+  chdir($dir);
+
+  #system('pdflatex', "$file.tex");
+  #system('pdflatex', "$file.tex");
+  #! LaTeX Error: Unknown graphics extension: .eps.
+
+  my $sfile = shell_quote $file;
+
+  system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+    or die "pslatex $file.tex failed; see $file.log for details?\n";
+  system("pslatex $sfile.tex >/dev/null 2>&1") == 0
+    or die "pslatex $file.tex failed; see $file.log for details?\n";
+
+  #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");
+  unlink("$lfile");
+
+  my $pdf = '';
+  while (<PDF>) {
+    $pdf .= $_;
+  }
+
+  close PDF;
+
+  return $pdf;
+
+}
+
+=item print_html [ TIME [ , TEMPLATE [ , CID ] ] ]
+
+Returns an HTML invoice, as a scalar.
+
+TIME an optional value used to control the printing of overdue messages.  The
+default is now.  It isn't the date of the invoice; that's the `_date' field.
+It is specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+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, $today, $template, $cid ) = @_;
+
+  my %params = ( 'format' => 'html' );
+  $params{'time'} = $today if $today;
+  $params{'template'} = $template if $template;
+  $params{'cid'} = $cid if $cid;
+
+  $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, '}',
+                      '--@]';
+
+    } 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 (eventually)
+  
+  #check for a customer- specific override
+  return $self->cust_main->invoice_terms
+    if $self->cust_main->invoice_terms;
+
+  #use configured default or default default
+  $conf->config('invoice_default_terms') || 'Payable upon receipt';
+}
+
+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('%x');
+  } elsif ( $self->terms ) {
+    $msg .= ' - '. $self->terms;
+  }
+  $msg;
+}
+
+sub _items_sections {
+  my $self = shift;
+
+  my %s = ();
+  foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
+
+    if ( $cust_bill_pkg->pkgnum > 0 ) {
+
+      my $desc = $cust_bill_pkg->cust_pkg->part_pkg->classname;
+
+      $s{$desc} += $cust_bill_pkg->setup
+        if ( $cust_bill_pkg->setup != 0 );
+
+      $s{$desc} += $cust_bill_pkg->recur
+        if ( $cust_bill_pkg->recur != 0 );
+
+    }
+
+  }
+
+  map { {'description' => $_, 'subtotal' => $s{$_}} } sort keys %s;
+
+}
+
+sub _items {
+  my $self = shift;
+  my @display = scalar(@_)
+                ? @_
+                : qw( _items_previous _items_pkg );
+                #: qw( _items_pkg );
+                #: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments );
+  my @b = ();
+  foreach my $display ( @display ) {
+    push @b, $self->$display(@_);
+  }
+  @b;
+}
+
+sub _items_previous {
+  my $self = shift;
+  my $cust_main = $self->cust_main;
+  my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
+  my @b = ();
+  foreach ( @pr_cust_bill ) {
+    push @b, {
+      'description' => 'Previous Balance, Invoice #'. $_->invnum. 
+                       ' ('. time2str('%x',$_->_date). ')',
+      #'pkgpart'     => 'N/A',
+      'pkgnum'      => 'N/A',
+      'amount'      => sprintf("%.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 $section = delete $options{'section'};
+  my @cust_bill_pkg =
+    grep { $_->pkgnum &&
+           ( defined($section)
+               ? $_->cust_pkg->part_pkg->classname eq $section->{'description'}
+               : 1
+           )
+         } $self->cust_bill_pkg;
+  $self->_items_cust_bill_pkg(\@cust_bill_pkg, %options);
+}
+
+sub _items_tax {
+  my $self = shift;
+  my @cust_bill_pkg = grep { ! $_->pkgnum } $self->cust_bill_pkg;
+  $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_);
+}
+
+sub _items_cust_bill_pkg {
+  my $self = shift;
+  my $cust_bill_pkg = shift;
+
+  my @b = ();
+  foreach my $cust_bill_pkg ( @$cust_bill_pkg ) {
+
+    my $desc = $cust_bill_pkg->desc;
+
+    if ( $cust_bill_pkg->pkgnum > 0 ) {
+
+      if ( $cust_bill_pkg->setup != 0 ) {
+        my $description = $desc;
+        $description .= ' Setup' if $cust_bill_pkg->recur != 0;
+        my @d = $cust_bill_pkg->cust_pkg->h_labels_short($self->_date);
+        push @d, $cust_bill_pkg->details if $cust_bill_pkg->recur == 0;
+        push @b, {
+          description     => $description,
+          #pkgpart         => $part_pkg->pkgpart,
+          pkgnum          => $cust_bill_pkg->pkgnum,
+          amount          => sprintf("%.2f", $cust_bill_pkg->setup),
+          ext_description => \@d,
+        };
+      }
+
+      if ( $cust_bill_pkg->recur != 0 ) {
+        push @b, {
+          description     => $desc .
+                             ( $conf->exists('disable_line_item_date_ranges')
+                               ? ''
+                               : " (" .time2str("%x", $cust_bill_pkg->sdate).
+                                 " - ".time2str("%x", $cust_bill_pkg->edate).")"
+                             ),
+          #pkgpart         => $part_pkg->pkgpart,
+          pkgnum          => $cust_bill_pkg->pkgnum,
+          amount          => sprintf("%.2f", $cust_bill_pkg->recur),
+          ext_description =>
+            [ $cust_bill_pkg->cust_pkg->h_labels_short( $cust_bill_pkg->edate,
+                                                        $cust_bill_pkg->sdate),
+              $cust_bill_pkg->details,
+            ],
+        };
+      }
+
+    } 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("%x", $cust_bill_pkg->sdate). ' - '.
+                           time2str("%x", $cust_bill_pkg->edate). ')',
+          'amount'      => sprintf("%.2f", $cust_bill_pkg->recur),
+        };
+      }
+
+    }
+
+  }
+
+  @b;
+
+}
+
+sub _items_credits {
+  my $self = shift;
+
+  my @b;
+  #credits
+  foreach ( $self->cust_credited ) {
+
+    #something more elaborate if $_->amount ne $_->cust_credit->credited ?
+
+    my $reason = $_->cust_credit->reason;
+    #my $reason = substr($_->cust_credit->reason,0,32);
+    #$reason .= '...' if length($reason) < length($_->cust_credit->reason);
+    $reason = " ($reason) " if $reason;
+    push @b, {
+      #'description' => 'Credit ref\#'. $_->crednum.
+      #                 " (". time2str("%x",$_->cust_credit->_date) .")".
+      #                 $reason,
+      'description' => 'Credit applied '.
+                       time2str("%x",$_->cust_credit->_date). $reason,
+      'amount'      => sprintf("%.2f",$_->amount),
+    };
+  }
+  #foreach ( @cr_cust_credit ) {
+  #  push @buf,[
+  #    "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")",
+  #    $money_char. sprintf("%10.2f",$_->credited)
+  #  ];
+  #}
+
+  @b;
+
+}
+
+sub _items_payments {
+  my $self = shift;
+
+  my @b;
+  #get & print payments
+  foreach ( $self->cust_bill_pay ) {
+
+    #something more elaborate if $_->amount ne ->cust_pay->paid ?
+
+    push @b, {
+      'description' => "Payment received ".
+                       time2str("%x",$_->cust_pay->_date ),
+      'amount'      => sprintf("%.2f", $_->amount )
+    };
+  }
+
+  @b;
+
+}
+
+
+=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 );
+  warn "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(\%param);
+
+  my $addl_from = 'left join cust_main using ( custnum )';
+     
+  my @cust_bill = qsearch( 'cust_bill',
+                           {},
+                           #"$distinct cust_bill.*",
+                           "cust_bill.*",
+                           $extra_sql,
+                           '',
+                           $addl_from
+                         );
+
+  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 = shift;
+  'charged - '. $class->paid_sql. ' - '. $class->credited_sql;
+}
+
+=item net_sql
+
+Returns an SQL fragment to retreive the net amount (charged minus credited).
+
+=cut
+
+sub net_sql {
+  my $class = shift;
+  'charged - '. $class->credited_sql;
+}
+
+=item paid_sql
+
+Returns an SQL fragment to retreive the amount paid against this invoice.
+
+=cut
+
+sub paid_sql {
+  #my $class = shift;
+  "( SELECT COALESCE(SUM(amount),0) FROM cust_bill_pay
+       WHERE cust_bill.invnum = cust_bill_pay.invnum   )";
+}
+
+=item credited_sql
+
+Returns an SQL fragment to retreive the amount credited against this invoice.
+
+=cut
+
+sub credited_sql {
+  #my $class = shift;
+  "( SELECT COALESCE(SUM(amount),0) FROM cust_credit_bill
+       WHERE cust_bill.invnum = cust_credit_bill.invnum   )";
+}
+
+=item search_sql HASHREF
+
+Class method which returns an SQL WHERE fragment to search for parameters
+specified in HASHREF.  Valid parameters are
+
+=over 4
+
+=item begin
+
+Epoch date (UNIX timestamp) setting a lower bound for _date values
+
+=item end
+
+Epoch date (UNIX timestamp) setting an upper bound for _date values
+
+=item invnum_min
+
+=item invnum_max
+
+=item agentnum
+
+=item owed
+
+=item net
+
+=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 {
+  my($class, $param) = @_;
+  my @search = ();
+
+  if ( $param->{'begin'} =~ /^(\d+)$/ ) {
+    push @search, "cust_bill._date >= $1";
+  }
+  if ( $param->{'end'} =~ /^(\d+)$/ ) {
+    push @search, "cust_bill._date < $1";
+  }
+  if ( $param->{'invnum_min'} =~ /^(\d+)$/ ) {
+    push @search, "cust_bill.invnum >= $1";
+  }
+  if ( $param->{'invnum_max'} =~ /^(\d+)$/ ) {
+    push @search, "cust_bill.invnum <= $1";
+  }
+  if ( $param->{'agentnum'} =~ /^(\d+)$/ ) {
+    push @search, "cust_main.agentnum = $1";
+  }
+
+  push @search, '0 != '. FS::cust_bill->owed_sql
+    if $param->{'open'};
+
+  push @search, '0 != '. FS::cust_bill->net_sql
+    if $param->{'net'};
+
+  push @search, "cust_bill._date < ". (time-86400*$param->{'days'})
+    if $param->{'days'};
+
+  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
+    )";
+
+  }
+
+  push @search, $FS::CurrentUser::CurrentUser->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 (file)
index 0000000..24274e7
--- /dev/null
@@ -0,0 +1,390 @@
+package FS::cust_bill_ApplicationCommon;
+
+use strict;
+use vars qw( @ISA $DEBUG $me );
+use List::Util qw(min);
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearch qsearchs dbh );
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+$me = '[FS::cust_bill_ApplicationCommon]';
+
+=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
+
+=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 apply_to_lineitems {
+  my $self = shift;
+
+  my @apply = ();
+
+  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 @open = $self->cust_bill->open_cust_bill_pkg; #FOR UPDATE...?
+  warn "$me ". scalar(@open). " open line items for invoice ".
+       $self->cust_bill->invnum. ": ". join(', ', @open). "\n"
+    if $DEBUG;
+  my $total = 0;
+  $total += $_->setup + $_->recur foreach @open;
+  $total = sprintf('%.2f', $total);
+
+  if ( $self->amount > $total ) {
+    $dbh->rollback if $oldAutoCommit;
+    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 { [ $_, $_->setup || $_->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 == $self->amount
+                      || $_->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 )
+                               : 0; #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->setup || $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->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 );
+
+      #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
+       $dbh->rollback if $oldAutoCommit;
+       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 ) ) {
+        $dbh->rollback if $oldAutoCommit;
+       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;
+
+    }
+
+  }
+
+  # 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 ) = @$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,
+    });
+    my $error = $application->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  #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 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 (file)
index 0000000..7c2ad37
--- /dev/null
@@ -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 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 {
+  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($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 (file)
index 0000000..74a8bcd
--- /dev/null
@@ -0,0 +1,164 @@
+package FS::cust_bill_pay;
+
+use strict;
+use vars qw( @ISA $conf );
+use FS::Record qw( qsearchs );
+use FS::cust_bill_ApplicationCommon;
+use FS::cust_bill;
+use FS::cust_pay;
+
+@ISA = qw( 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')
+  ;
+  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 } );
+}
+
+=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 (file)
index 0000000..30fb744
--- /dev/null
@@ -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 (file)
index 0000000..cdbace9
--- /dev/null
@@ -0,0 +1,141 @@
+package FS::cust_bill_pay_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@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
+
+# 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 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_money('amount')
+    || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
+    || $self->ut_numbern('sdate')
+    || $self->ut_numbern('edate')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=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 (file)
index 0000000..9fddf6b
--- /dev/null
@@ -0,0 +1,320 @@
+package FS::cust_bill_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbdef dbh );
+use FS::cust_main_Mixin;
+use FS::cust_pkg;
+use FS::cust_bill;
+use FS::cust_bill_pkg_detail;
+use FS::cust_bill_pay_pkg;
+use FS::cust_credit_bill_pkg;
+
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+
+=head1 NAME
+
+FS::cust_bill_pkg - Object methods for cust_bill_pkg records
+
+=head1 SYNOPSIS
+
+  use FS::cust_bill_pkg;
+
+  $record = new FS::cust_bill_pkg \%hash;
+  $record = new FS::cust_bill_pkg { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg object represents an invoice line item.
+FS::cust_bill_pkg inherits from FS::Record.  The following fields are currently
+supported:
+
+=over 4
+
+=item 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 setup - setup fee
+
+=item recur - recurring fee
+
+=item sdate - starting date of recurring fee
+
+=item edate - ending date of recurring fee
+
+=item itemdesc - Line item description (currentlty used only when pkgnum is 0 or -1)
+
+=back
+
+sdate and edate are specified as UNIX timestamps; see L<perlfunc/"time">.  Also
+see L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new line item.  To add the line item to the database, see
+L<"insert">.  Line items are normally created by calling the bill method of a
+customer object (see L<FS::cust_main>).
+
+=cut
+
+sub table { 'cust_bill_pkg'; }
+
+=item insert
+
+Adds this line item to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+  my $self = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  unless ( defined dbdef->table('cust_bill_pkg_detail') && $self->get('details') ) {
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    return '';
+  }
+
+  foreach my $detail ( @{$self->get('details')} ) {
+    my $cust_bill_pkg_detail = new FS::cust_bill_pkg_detail {
+      'pkgnum' => $self->pkgnum,
+      'invnum' => $self->invnum,
+      'detail' => $detail,
+    };
+    $error = $cust_bill_pkg_detail->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+=item delete
+
+Currently unimplemented.  I don't remove line items because there would then be
+no record the items ever existed (which is bad, no?)
+
+=cut
+
+sub delete {
+  return "Can't delete cust_bill_pkg records!";
+}
+
+=item replace OLD_RECORD
+
+Currently unimplemented.  This would be even more of an accounting nightmare
+than deleteing the items.  Just don't do it.
+
+=cut
+
+sub replace {
+  return "Can't modify cust_bill_pkg records!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid line item.  If there is an
+error, returns the error, otherwise returns false.  Called by the insert
+method.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error =
+         $self->ut_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')
+  ;
+  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;
+  qsearchs( 'cust_pkg', { 'pkgnum' => $self->pkgnum } );
+}
+
+=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 details
+
+Returns an array of detail information for the invoice line item.
+
+=cut
+
+sub details {
+  my $self = shift;
+  return () unless defined dbdef->table('cust_bill_pkg_detail');
+  map { $_->detail }
+    qsearch ( 'cust_bill_pkg_detail', { 'pkgnum' => $self->pkgnum,
+                                        'invnum' => $self->invnum, } );
+    #qsearch ( 'cust_bill_pkg_detail', { 'lineitemnum' => $self->lineitemnum });
+}
+
+=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->cust_pkg->part_pkg->pkg;
+  } else {
+    $self->itemdesc || 'Tax';
+  }
+}
+
+=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;
+}
+
+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,
+                                   }
+         );
+}
+
+=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 (file)
index 0000000..4156816
--- /dev/null
@@ -0,0 +1,124 @@
+package FS::cust_bill_pkg_detail;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_bill_pkg_detail - Object methods for cust_bill_pkg_detail records
+
+=head1 SYNOPSIS
+
+  use FS::cust_bill_pkg_detail;
+
+  $record = new FS::cust_bill_pkg_detail \%hash;
+  $record = new FS::cust_bill_pkg_detail { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pkg_detail object represents additional detail information for
+an invoice line item (see L<FS::cust_bill_pkg>).  FS::cust_bill_pkg_detail
+inherits from FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item detailnum - primary key
+
+=item pkgnum -
+
+=item invnum -
+
+=item detail - detail description
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new line item detail.  To add the line item detail to the database,
+see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'cust_bill_pkg_detail'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid line item detail.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  $self->ut_numbern('detailnum')
+    || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum')
+    || $self->ut_foreign_key('invnum', 'cust_bill', 'invnum')
+    || $self->ut_text('detail')
+    || $self->SUPER::check
+    ;
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::cust_bill_pkg>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
new file mode 100644 (file)
index 0000000..d5b6ff4
--- /dev/null
@@ -0,0 +1,595 @@
+package FS::cust_credit;
+
+use strict;
+use vars qw( @ISA $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_Mixin;
+use FS::cust_main;
+use FS::cust_refund;
+use FS::cust_credit_bill;
+use FS::part_pkg;
+use FS::reason_type;
+use FS::reason;
+
+@ISA = qw( FS::cust_main_Mixin FS::Record );
+$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 otaker
+
+Order taker (assigned automatically, see L<FS::UID>)
+
+=item reason
+
+Text ( deprecated )
+
+=item reasonnum
+
+Reason (see L<FS::reason>)
+
+=item closed
+
+Books closed flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new credit.  To add the credit to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_credit'; }
+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'), #??? 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_alpha('otaker')
+    || $self->ut_textn('reason')
+    || $self->ut_foreign_key('reasonnum', 'reason', 'reasonnum')
+    || $self->ut_enum('closed', [ '', 'Y' ])
+  ;
+  return $error if $error;
+
+  return "amount must be > 0 " if $self->amount <= 0;
+
+  return "Unknown customer"
+    unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+  $self->_date(time) unless $self->_date;
+
+  $self->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;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_credit_refund', { 'crednum' => $self->crednum } )
+  ;
+}
+
+=item cust_credit_bill
+
+Returns all application to invoices (see L<FS::cust_credit_bill>) for this
+credit.
+
+=cut
+
+sub cust_credit_bill {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_credit_bill', { 'crednum' => $self->crednum } )
+  ;
+}
+
+=item 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', 
+                              } );
+      $reason->insert and $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 : '';
+}
+
+# _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;
+        }
+      }
+    }
+  }
+
+  '';
+
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item unapplied_sql
+
+Returns an SQL fragment to retreive the unapplied amount.
+
+=cut
+
+sub unapplied_sql {
+  #my $class = shift;
+
+  "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
+                  )
+  ";
+
+}
+
+=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 (file)
index 0000000..411bae2
--- /dev/null
@@ -0,0 +1,167 @@
+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_bill_ApplicationCommon;
+use FS::cust_bill;
+use FS::cust_credit;
+
+@ISA = qw( 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')
+  ;
+  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 (file)
index 0000000..7252be5
--- /dev/null
@@ -0,0 +1,141 @@
+package FS::cust_credit_bill_pkg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(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 creditbillpkg -  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
+
+# 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 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_money('amount')
+    || $self->ut_enum('setuprecur', [ 'setup', 'recur' ] )
+    || $self->ut_numbern('sdate')
+    || $self->ut_numbern('edate')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=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_credit_refund.pm b/FS/FS/cust_credit_refund.pm
new file mode 100644 (file)
index 0000000..f237efe
--- /dev/null
@@ -0,0 +1,186 @@
+package FS::cust_credit_refund;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+#use FS::UID qw(getotaker);
+use FS::cust_credit;
+use FS::cust_refund;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::cust_credit_refund - Object methods for cust_bill_pay records
+
+=head1 SYNOPSIS 
+
+  use FS::cust_credit_refund;
+
+  $record = new FS::cust_credit_refund \%hash;
+  $record = new FS::cust_credit_refund { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_credit_refund represents the application of a refund to a specific
+credit.  FS::cust_credit_refund inherits from FS::Record.  The following fields
+are currently supported:
+
+=over 4
+
+=item creditrefundnum - primary key (assigned automatically)
+
+=item crednum - Credit (see L<FS::cust_credit>)
+
+=item refundnum - Refund (see L<FS::cust_refund>)
+
+=item amount - Amount of the refund to apply to the specific credit.
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=back
+
+=head1 METHODS
+
+=over 4 
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_credit_refund'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+  my $self = shift;
+  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 (file)
index 0000000..5924851
--- /dev/null
@@ -0,0 +1,408 @@
+package FS::cust_event;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use Carp qw( croak confess );
+use FS::Record qw( qsearch qsearchs dbdef );
+use FS::cust_main_Mixin;
+use FS::part_event;
+#for cust_X
+use FS::cust_main;
+use FS::cust_pkg;
+use FS::cust_bill;
+
+@ISA = qw(FS::cust_main_Mixin FS::Record);
+
+$DEBUG = 0;
+
+=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); };
+  }
+
+  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);
+}
+
+=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->{'beginning'},
+    $param->{'ending'},
+    $param->{'failed'},
+    $job,
+  );
+
+}
+
+sub re_X {
+  my($method, $beginning, $ending, $failed, $job) = @_;
+
+  my $from = 'LEFT JOIN part_event USING ( eventpart )';
+
+              # yuck!  hardcoed *AND* sequential scans!
+  my $where = " WHERE action LIKE 'cust_bill_send%'".
+              "   AND cust_event._date >= $beginning".
+              "   AND cust_event._date <= $ending";
+  $where .= " AND statustext != '' AND statustext IS NOT NULL"
+    if $failed;
+
+  my @cust_event = qsearch({
+    'table'     => 'cust_event',
+    'addl_from' => $from,
+    'hashref'   => {},
+    'extra_sql' => $where,
+  });
+
+  my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo
+  foreach my $cust_event ( @cust_event ) {
+
+    # XXX 
+    $cust_event->cust_bill->$method(
+      $cust_event->part_event->templatename
+      || $cust_event->cust_main->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_main.pm b/FS/FS/cust_main.pm
new file mode 100644 (file)
index 0000000..73bdc14
--- /dev/null
@@ -0,0 +1,6249 @@
+package FS::cust_main;
+
+require 5.006;
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG $me $conf @encrypted_fields
+             $import $skip_fuzzyfiles $ignore_expired_card @paytypes);
+use vars qw( $realtime_bop_decline_quiet ); #ugh
+use Safe;
+use Carp;
+use Exporter;
+use Time::Local qw(timelocal_nocheck);
+use Data::Dumper;
+use Tie::IxHash;
+use Digest::MD5 qw(md5_base64);
+use Date::Format;
+use Date::Parse;
+#use Date::Manip;
+use String::Approx qw(amatch);
+use Business::CreditCard 0.28;
+use Locale::Country;
+use Data::Dumper;
+use FS::UID qw( getotaker dbh driver_name );
+use FS::Record qw( qsearchs qsearch dbdef );
+use FS::Misc qw( send_email generate_ps do_print );
+use FS::Msgcat qw(gettext);
+use FS::cust_pkg;
+use FS::cust_svc;
+use FS::cust_bill;
+use FS::cust_bill_pkg;
+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::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::cust_tax_exempt;
+use FS::cust_tax_exempt_pkg;
+use FS::type_pkgs;
+use FS::payment_gateway;
+use FS::agent_payment_gateway;
+use FS::banned_pay;
+use FS::payinfo_Mixin;
+use FS::TicketSystem;
+
+@ISA = qw( FS::Record FS::payinfo_Mixin );
+
+@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;
+$skip_fuzzyfiles = 0;
+$ignore_expired_card = 0;
+
+@encrypted_fields = ('payinfo', '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 - name
+
+=item last - name
+
+=item ss - social security number (optional)
+
+=item company - (optional)
+
+=item address1
+
+=item address2 - (optional)
+
+=item city
+
+=item county - (optional, see L<FS::cust_main_county>)
+
+=item state - (see L<FS::cust_main_county>)
+
+=item zip
+
+=item country - (see L<FS::cust_main_county>)
+
+=item daytime - phone (optional)
+
+=item night - phone (optional)
+
+=item fax - phone (optional)
+
+=item ship_first - name
+
+=item ship_last - name
+
+=item ship_company - (optional)
+
+=item ship_address1
+
+=item ship_address2 - (optional)
+
+=item ship_city
+
+=item ship_county - (optional, see L<FS::cust_main_county>)
+
+=item ship_state - (see L<FS::cust_main_county>)
+
+=item ship_zip
+
+=item ship_country - (see L<FS::cust_main_county>)
+
+=item ship_daytime - phone (optional)
+
+=item ship_night - phone (optional)
+
+=item ship_fax - phone (optional)
+
+=item payby - 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 otaker - order taker (assigned automatically, see L<FS::UID>)
+
+=item comments - comments (optional)
+
+=item referral_custnum - referring customer number
+
+=item spool_cdr - Enable individual CDR spooling, 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> and I<noexport>.
+
+If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
+on the supplied jobnum (they will not run until the specific job completes).
+This can be used to defer provisioning until some action completes (such
+as running the customer's credit card 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.)
+
+=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 ) = ( 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, \$seconds);
+    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;
+
+  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 );
+  }
+
+  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, \$seconds, %options);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  if ( $seconds ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "No svc_acct record to apply pre-paid time";
+  }
+
+  if ( $amount ) {
+    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;
+  '';
+
+}
+
+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_pkgs HASHREF, [ SECONDSREF, [ , OPTION => VALUE ... ] ]
+
+Like the insert method on an existing record, this method orders a package
+and included services atomicaly.  Pass a Tie::RefHash data structure to this
+method containing FS::cust_pkg and FS::svc_I<tablename> objects.  There should
+be a better explanation of this, but until then, here's an example:
+
+  use Tie::RefHash;
+  tie %hash, 'Tie::RefHash'; #this part is important
+  %hash = (
+    $cust_pkg => [ $svc_acct ],
+    ...
+  );
+  $cust_main->order_pkgs( \%hash, \'0', 'noexport'=>1 );
+
+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> and I<noexport>.
+
+If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
+on the supplied jobnum (they will not run until the specific job completes).
+This can be used to defer provisioning until some action completes (such
+as running the customer's credit card 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.)
+
+=cut
+
+sub order_pkgs {
+  my $self = shift;
+  my $cust_pkgs = shift;
+  my $seconds = shift;
+  my %options = @_;
+  my %svc_options = ();
+  $svc_options{'depend_jobnum'} = $options{'depend_jobnum'}
+    if exists $options{'depend_jobnum'};
+  warn "$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 ) {
+    $cust_pkg->custnum( $self->custnum );
+    my $error = $cust_pkg->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "inserting cust_pkg (transaction rolled back): $error";
+    }
+    foreach my $svc_something ( @{$cust_pkgs->{$cust_pkg}} ) {
+      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 ( $seconds && $$seconds && $svc_something->isa('FS::svc_acct') ) {
+          $svc_something->seconds( $svc_something->seconds + $$seconds );
+          $$seconds = 0;
+        }
+        $error = $svc_something->insert(%svc_options);
+      }
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        #return "inserting svc_ (transaction rolled back): $error";
+        return $error;
+      }
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  ''; #no error
+}
+
+=item 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, four scalar references can be passed as well.  They will have their
+values filled in with the amount, number of seconds, and number of upload and
+download bytes applied by this prepaid
+card.
+
+=cut
+
+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,
+                                \$seconds, \$upbytes, \$downbytes, \$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 , AMOUNTREF, SECONDSREF
+
+Looks up and deletes a prepaid card (see L<FS::prepay_credit>),
+specified either by I<identifier> or as an FS::prepay_credit object.
+
+References to I<amount> and I<seconds> scalars should be passed as arguments
+and 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, $amountref, $secondsref,
+      $upref, $downref, $totalref) = @_;
+
+  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";
+  }
+
+  $$amountref  += $prepay_credit->amount;
+  $$secondsref += $prepay_credit->seconds;
+  $$upref      += $prepay_credit->upbytes;
+  $$downref    += $prepay_credit->downbytes;
+  $$totalref   += $prepay_credit->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;
+    }
+  }
+
+  my $error = $self->SUPER::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+=item replace OLD_RECORD [ INVOICING_LIST_ARYREF ]
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+INVOICING_LIST_ARYREF: If you pass an arrarref to the insert method, it will
+be set as the invoicing list (see L<"invoicing_list">).  Errors return as
+expected and rollback the entire transaction; it is not necessary to call 
+check_invoicing_list first.  Here's an example:
+
+  $new_cust_main->replace( $old_cust_main, [ $email, 'POST' ] );
+
+=cut
+
+sub replace {
+  my $self = shift;
+  my $old = shift;
+  my @param = @_;
+  warn "$me replace called\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';
+
+  # We absolutely have to have an old vs. new record to make this work.
+  if (!defined($old)) {
+    $old = qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+  }
+
+  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 );
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::replace($old);
+
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  if ( @param ) { # INVOICING_LIST_ARYREF
+    my $invoicing_list = shift @param;
+    $error = $self->check_invoicing_list( $invoicing_list );
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+    $self->invoicing_list( $invoicing_list );
+  }
+
+  if ( $self->payby =~ /^(CARD|CHEK|LECB)$/ &&
+       grep { $self->get($_) ne $old->get($_) } qw(payinfo paydate payname) ) {
+    # card/check/lec info has changed, want to retry realtime_ invoice events
+    my $error = $self->retry_realtime;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  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($_),
+                                  qw(first last company)
+                            );
+  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_$_"),
+                                 qw(first last company)
+                           );
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "queueing job (transaction rolled back): $error";
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+=item check
+
+Checks all fields to make sure this is a valid customer record.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and 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_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')
+  ;
+  #barf.  need message catalogs.  i18n.  etc.
+  $error .= "Please select an advertising source."
+    if $error =~ /^Illegal or empty \(numeric\) refnum: /;
+  return $error if $error;
+
+  return "Unknown agent"
+    unless qsearchs( 'agent', { 'agentnum' => $self->agentnum } );
+
+  return "Unknown refnum"
+    unless qsearchs( 'part_referral', { 'refnum' => $self->refnum } );
+
+  return "Unknown referring custnum: ". $self->referral_custnum
+    unless ! $self->referral_custnum 
+           || qsearchs( 'cust_main', { 'custnum' => $self->referral_custnum } );
+
+  if ( $self->ss eq '' ) {
+    $self->ss('');
+  } else {
+    my $ss = $self->ss;
+    $ss =~ s/\D//g;
+    $ss =~ /^(\d{3})(\d{2})(\d{4})$/
+      or return "Illegal social security number: ". $self->ss;
+    $self->ss("$1-$2-$3");
+  }
+
+
+# bad idea to disable, causes billing to fail because of no tax rates later
+#  unless ( $import ) {
+    unless ( qsearch('cust_main_county', {
+      'country' => $self->country,
+      'state'   => '',
+     } ) ) {
+      return "Unknown state/county/country: ".
+        $self->state. "/". $self->county. "/". $self->country
+        unless qsearch('cust_main_county',{
+          'state'   => $self->state,
+          'county'  => $self->county,
+          'country' => $self->country,
+        } );
+    }
+#  }
+
+  $error =
+    $self->ut_phonen('daytime', $self->country)
+    || $self->ut_phonen('night', $self->country)
+    || $self->ut_phonen('fax', $self->country)
+    || $self->ut_zip('zip', $self->country)
+  ;
+  return $error if $error;
+
+  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 =~ /^(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 )) {
+    $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 all_pkgs
+
+Returns all packages (see L<FS::cust_pkg>) for this customer.
+
+=cut
+
+sub all_pkgs {
+  my $self = shift;
+
+  return $self->num_pkgs unless wantarray;
+
+  my @cust_pkg = ();
+  if ( $self->{'_pkgnum'} ) {
+    @cust_pkg = values %{ $self->{'_pkgnum'}->cache };
+  } else {
+    @cust_pkg = qsearch( 'cust_pkg', { 'custnum' => $self->custnum });
+  }
+
+  sort sort_packages @cust_pkg;
+}
+
+=item cust_pkg
+
+Synonym for B<all_pkgs>.
+
+=cut
+
+sub cust_pkg {
+  shift->all_pkgs(@_);
+}
+
+=item ncancelled_pkgs
+
+Returns all non-cancelled packages (see L<FS::cust_pkg>) for this customer.
+
+=cut
+
+sub ncancelled_pkgs {
+  my $self = 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;
+
+    @cust_pkg =
+      qsearch( 'cust_pkg', {
+                             'custnum' => $self->custnum,
+                             'cancel'  => '',
+                           });
+    push @cust_pkg,
+      qsearch( 'cust_pkg', {
+                             'custnum' => $self->custnum,
+                             'cancel'  => 0,
+                           });
+  }
+
+  sort sort_packages @cust_pkg;
+
+}
+
+# This should be generalized to use config options to determine order.
+sub sort_packages {
+  if ( $a->get('cancel') and $b->get('cancel') ) {
+    $a->pkgnum <=> $b->pkgnum;
+  } elsif ( $a->get('cancel') or $b->get('cancel') ) {
+    return -1 if $b->get('cancel');
+    return  1 if $a->get('cancel');
+    return 0;
+  } else {
+    $a->pkgnum <=> $b->pkgnum;
+  }
+}
+
+=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 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.
+
+=back
+
+Always returns a list: an empty list on success or a list of errors.
+
+=cut
+
+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;
+
+  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 bill_and_collect 
+
+Cancels and suspends any packages due, generates bills, applies payments and
+cred
+
+Warns on errors (Does not currently: 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
+
+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 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
+
+=cut
+
+sub bill_and_collect {
+  my( $self, %options ) = @_;
+
+  ###
+  # cancel packages
+  ###
+
+  #$^T not $options{time} because freeside-daily -d is for pre-printing invoices
+  foreach my $cust_pkg (
+    grep { $_->expire && $_->expire <= $^T } $self->ncancelled_pkgs
+  ) {
+    my $error = $cust_pkg->cancel;
+    warn "Error cancelling expired pkg ". $cust_pkg->pkgnum.
+         " for custnum ". $self->custnum. ": $error"
+      if $error;
+  }
+
+  ###
+  # suspend packages
+  ###
+
+  #$^T not $options{time} because freeside-daily -d is for pre-printing invoices
+  foreach my $cust_pkg (
+    grep { (    $_->part_pkg->is_prepaid && $_->bill && $_->bill < $^T
+             || $_->adjourn && $_->adjourn <= $^T
+           )
+           && ! $_->susp
+         }
+         $self->ncancelled_pkgs
+  ) {
+    my $error = $cust_pkg->suspend;
+    warn "Error suspending package ". $cust_pkg->pkgnum.
+         " for custnum ". $self->custnum. ": $error"
+      if $error;
+  }
+
+  ###
+  # bill and collect
+  ###
+
+  my $error = $self->bill( %options );
+  warn "Error billing, custnum ". $self->custnum. ": $error" if $error;
+
+  $self->apply_payments_and_credits;
+
+  $error = $self->collect( %options );
+  warn "Error collecting, custnum". $self->custnum. ": $error" if $error;
+
+}
+
+=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.
+
+=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 $error;
+
+  #put below somehow?
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  $self->select_for_update; #mutex
+
+  #create a new invoice
+  #(we'll remove it later if it doesn't actually need to be generated [contains
+  # no line items] and we're inside a transaciton so nothing else will see it)
+  my $cust_bill = new FS::cust_bill ( {
+    'custnum' => $self->custnum,
+    '_date'   => ( $options{'invoice_time'} || $time ),
+    #'charged' => $charged,
+    'charged' => 0,
+  } );
+  $error = $cust_bill->insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "can't create invoice for customer #". $self->custnum. ": $error";
+  }
+  my $invnum = $cust_bill->invnum;
+
+  ###
+  # find the packages which are due for billing, find out how much they are
+  # & generate invoice database.
+  ###
+
+  my( $total_setup, $total_recur ) = ( 0, 0 );
+  my %tax;
+  my @precommit_hooks = ();
+
+  foreach my $cust_pkg (
+    qsearch('cust_pkg', { 'custnum' => $self->custnum } )
+  ) {
+
+    #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 %hash = $cust_pkg->hash;
+    my $old_cust_pkg = new FS::cust_pkg \%hash;
+
+    my @details = ();
+
+    ###
+    # bill setup
+    ###
+
+    my $setup = 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;
+
+      $setup = eval { $cust_pkg->calc_setup( $time, \@details ) };
+      if ( $@ ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "$@ running calc_setup for $cust_pkg\n";
+      }
+
+      $cust_pkg->setfield('setup', $time) unless $cust_pkg->setup;
+    }
+
+    ###
+    # bill recurring fee
+    ### 
+
+    my $recur = 0;
+    my $sdate;
+    if ( $part_pkg->getfield('freq') ne '0' &&
+         ! $cust_pkg->getfield('susp') &&
+         ( $cust_pkg->getfield('bill') || 0 ) <= $time
+    ) {
+
+      # XXX should this be a package event?  probably.  events are called
+      # at collection time at the moment, though...
+      if ( $part_pkg->can('reset_usage') ) {
+        warn "    resetting usage counters" if $DEBUG > 1;
+        $part_pkg->reset_usage($cust_pkg);
+      }
+
+      warn "    bill recur\n" if $DEBUG > 1;
+
+      # XXX shared with $recur_prog
+      $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
+
+      #over two params!  lets at least switch to a hashref for the rest...
+      my %param = ( 'precommit_hooks' => \@precommit_hooks, );
+
+      $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details, \%param ) };
+      if ( $@ ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "$@ running calc_recur for $cust_pkg\n";
+      }
+
+      #change this bit to use Date::Manip? CAREFUL with timezones (see
+      # mailing list archive)
+      my ($sec,$min,$hour,$mday,$mon,$year) =
+        (localtime($sdate) )[0,1,2,3,4,5];
+
+      #pro-rating magic - if $recur_prog fiddles $sdate, want to use that
+      # only for figuring next bill date, nothing else, so, reset $sdate again
+      # here
+      $sdate = $cust_pkg->bill || $cust_pkg->setup || $time;
+      $cust_pkg->last_bill($sdate);
+
+      if ( $part_pkg->freq =~ /^\d+$/ ) {
+        $mon += $part_pkg->freq;
+        until ( $mon < 12 ) { $mon -= 12; $year++; }
+      } elsif ( $part_pkg->freq =~ /^(\d+)w$/ ) {
+        my $weeks = $1;
+        $mday += $weeks * 7;
+      } elsif ( $part_pkg->freq =~ /^(\d+)d$/ ) {
+        my $days = $1;
+        $mday += $days;
+      } elsif ( $part_pkg->freq =~ /^(\d+)h$/ ) {
+        my $hours = $1;
+        $hour += $hours;
+      } else {
+        $dbh->rollback if $oldAutoCommit;
+        return "unparsable frequency: ". $part_pkg->freq;
+      }
+      $cust_pkg->setfield('bill',
+        timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year));
+    }
+
+    warn "\$setup is undefined" unless defined($setup);
+    warn "\$recur is undefined" unless defined($recur);
+    warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill);
+
+    ###
+    # If $cust_pkg has been modified, update it and create cust_bill_pkg records
+    ###
+
+    if ( $cust_pkg->modified ) {  # hmmm.. and if the options are modified?
+
+      warn "  package ". $cust_pkg->pkgnum. " modified; updating\n"
+        if $DEBUG >1;
+
+      $error=$cust_pkg->replace($old_cust_pkg,
+                                options => { $cust_pkg->options },
+                               );
+      if ( $error ) { #just in case
+        $dbh->rollback if $oldAutoCommit;
+        return "Error modifying pkgnum ". $cust_pkg->pkgnum. ": $error";
+      }
+
+      $setup = sprintf( "%.2f", $setup );
+      $recur = sprintf( "%.2f", $recur );
+      if ( $setup < 0 && ! $conf->exists('allow_negative_charges') ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "negative setup $setup for pkgnum ". $cust_pkg->pkgnum;
+      }
+      if ( $recur < 0 && ! $conf->exists('allow_negative_charges') ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum;
+      }
+
+      if ( $setup != 0 || $recur != 0 ) {
+
+        warn "    charges (setup=$setup, recur=$recur); adding line items\n"
+          if $DEBUG > 1;
+        my $cust_bill_pkg = new FS::cust_bill_pkg ({
+          'invnum'  => $invnum,
+          'pkgnum'  => $cust_pkg->pkgnum,
+          'setup'   => $setup,
+          'recur'   => $recur,
+          'sdate'   => $sdate,
+          'edate'   => $cust_pkg->bill,
+          'details' => \@details,
+        });
+        $error = $cust_bill_pkg->insert;
+        if ( $error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return "can't create invoice line item for invoice #$invnum: $error";
+        }
+        $total_setup += $setup;
+        $total_recur += $recur;
+
+        ###
+        # handle taxes
+        ###
+
+        unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) {
+
+          my $prefix = 
+            ( $conf->exists('tax-ship_address') && length($self->ship_last) )
+            ? 'ship_'
+            : '';
+          my %taxhash = map { $_ => $self->get("$prefix$_") }
+                            qw( state county country );
+
+          $taxhash{'taxclass'} = $part_pkg->taxclass;
+
+          my @taxes = qsearch( 'cust_main_county', \%taxhash );
+
+          unless ( @taxes ) {
+            $taxhash{'taxclass'} = '';
+            @taxes =  qsearch( 'cust_main_county', \%taxhash );
+          }
+
+          #one more try at a whole-country tax rate
+          unless ( @taxes ) {
+            $taxhash{$_} = '' foreach qw( state county );
+            @taxes =  qsearch( 'cust_main_county', \%taxhash );
+          }
+
+          # maybe eliminate this entirely, along with all the 0% records
+          unless ( @taxes ) {
+            $dbh->rollback if $oldAutoCommit;
+            return
+              "fatal: can't find tax rate for state/county/country/taxclass ".
+              join('/', ( map $self->get("$prefix$_"),
+                              qw(state county country)
+                        ),
+                        $part_pkg->taxclass ). "\n";
+          }
+  
+          foreach my $tax ( @taxes ) {
+
+            my $taxable_charged = 0;
+            $taxable_charged += $setup
+              unless $part_pkg->setuptax =~ /^Y$/i
+                  || $tax->setuptax =~ /^Y$/i;
+            $taxable_charged += $recur
+              unless $part_pkg->recurtax =~ /^Y$/i
+                  || $tax->recurtax =~ /^Y$/i;
+            next unless $taxable_charged;
+
+            if ( $tax->exempt_amount && $tax->exempt_amount > 0 ) {
+              #my ($mon,$year) = (localtime($sdate) )[4,5];
+              my ($mon,$year) = (localtime( $sdate || $cust_bill->_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=> $self->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(
+                  $self->custnum,
+                  $tax->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;
+                
+                my $remaining_exemption =
+                  $tax->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 ( {
+                    'billpkgnum' => $cust_bill_pkg->billpkgnum,
+                    'taxnum'     => $tax->taxnum,
+                    'year'       => 1900+$year,
+                    'month'      => $mon,
+                    'amount'     => sprintf("%.2f", $addl ),
+                  } );
+                  $error = $cust_tax_exempt_pkg->insert;
+                  if ( $error ) {
+                    $dbh->rollback if $oldAutoCommit;
+                    return "fatal: can't insert cust_tax_exempt_pkg: $error";
+                  }
+                } # 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);
+
+            #$tax += $taxable_charged * $cust_main_county->tax / 100
+            $tax{ $tax->taxname || 'Tax' } +=
+              $taxable_charged * $tax->tax / 100
+
+          } #foreach my $tax ( @taxes )
+
+        } #unless $self->tax =~ /Y/i || $self->payby eq 'COMP'
+
+      } #if $setup != 0 || $recur != 0
+      
+    } #if $cust_pkg->modified
+
+  } #foreach my $cust_pkg
+
+  unless ( $cust_bill->cust_bill_pkg ) {
+    $cust_bill->delete; #don't create an invoice w/o line items
+
+   # XXX this seems to be broken
+   #( DBD::Pg::st execute failed: ERROR:  syntax error at or near "hcb" )
+#   # get rid of our fake history too, waste of unecessary space
+#    my $h_cleanup_query = q{
+#      DELETE FROM h_cust_bill hcb
+#       WHERE hcb.invnum = ?
+#      AND NOT EXISTS ( SELECT 1 FROM cust_bill cb where cb.invnum = hcb.invnum )
+#    };
+#    my $h_sth = $dbh->prepare($h_cleanup_query);
+#    $h_sth->execute($invnum);
+
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    return '';
+  }
+
+  my $charged = sprintf( "%.2f", $total_setup + $total_recur );
+
+  foreach my $taxname ( grep { $tax{$_} > 0 } keys %tax ) {
+    my $tax = sprintf("%.2f", $tax{$taxname} );
+    $charged = sprintf( "%.2f", $charged+$tax );
+  
+    my $cust_bill_pkg = new FS::cust_bill_pkg ({
+      'invnum'   => $invnum,
+      'pkgnum'   => 0,
+      'setup'    => $tax,
+      'recur'    => 0,
+      'sdate'    => '',
+      'edate'    => '',
+      'itemdesc' => $taxname,
+    });
+    $error = $cust_bill_pkg->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "can't create invoice line item for invoice #$invnum: $error";
+    }
+    $total_setup += $tax;
+
+  }
+
+  $cust_bill->charged( sprintf( "%.2f", $total_setup + $total_recur ) );
+  $error = $cust_bill->replace;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "can't update charged for invoice #$invnum: $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
+}
+
+=item collect OPTIONS
+
+(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 quiet
+
+set true to surpress email card/ACH decline notices.
+
+=item check_freq
+
+"1d" for the traditional, daily events (the default), or "1m" for the new monthly events (part_event.check_freq)
+
+=item payby
+
+allows for one time override of normal customer billing method
+
+=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
+
+=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;
+    }
+  }
+
+  # false laziness w/pay_batch::import_results
+
+  my $due_cust_event = $self->due_cust_event(
+    'debug'      => ( $options{'debug'} || 0 ),
+    'time'       => $invoice_time,
+    'check_freq' => $options{'check_freq'},
+  );
+  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' => $invoice_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 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).
+
+=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
+
+  ###
+  # 1: 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;
+
+  ##
+  # 2: 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 $DEBUG; # > 1;
+
+  ##
+  # 3: insert
+  ##
+
+  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;
+
+  ##
+  # 4: 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;
+  '';
+
+}
+
+=item realtime_bop METHOD AMOUNT [ OPTION => VALUE ... ]
+
+Runs a realtime credit card, ACH (electronic check) or phone bill transaction
+via a Business::OnlinePayment realtime gateway.  See
+L<http://420.am/business-onlinepayment> for supported gateways.
+
+Available methods are: I<CC>, I<ECHECK> and I<LEC>
+
+Available options are: I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>
+
+The additional options I<payname>, I<address1>, I<address2>, I<city>, I<state>,
+I<zip>, I<payinfo> and I<paydate> are also available.  Any of these options,
+if set, will override the value from the customer record.
+
+I<description> is a free-text field passed to the gateway.  It defaults to
+"Internet services".
+
+If an I<invnum> is specified, this payment (if 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.
+
+(moved from cust_bill) (probably should get realtime_{card,ach,lec} here too)
+
+=cut
+
+sub realtime_bop {
+  my( $self, $method, $amount, %options ) = @_;
+  if ( $DEBUG ) {
+    warn "$me realtime_bop: $method $amount\n";
+    warn "  $_ => $options{$_}\n" foreach keys %options;
+  }
+
+  $options{'description'} ||= 'Internet services';
+
+  return $self->fake_bop($method, $amount, %options) if $options{'fake'};
+
+  eval "use Business::OnlinePayment";  
+  die $@ if $@;
+
+  my $payinfo = exists($options{'payinfo'})
+                  ? $options{'payinfo'}
+                  : $self->payinfo;
+
+  my %method2payby = (
+    'CC'     => 'CARD',
+    'ECHECK' => 'CHEK',
+    'LEC'    => 'LECB',
+  );
+
+  ###
+  # check for banned credit card/ACH
+  ###
+
+  my $ban = qsearchs('banned_pay', {
+    'payby'   => $method2payby{$method},
+    'payinfo' => md5_base64($payinfo),
+  } );
+  return "Banned credit card" if $ban;
+
+  ###
+  # select a gateway
+  ###
+
+  my $taxclass = '';
+  if ( $options{'invnum'} ) {
+    my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{'invnum'} } );
+    die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
+    my @taxclasses =
+      map  { $_->part_pkg->taxclass }
+      grep { $_ }
+      map  { $_->cust_pkg }
+      $cust_bill->cust_bill_pkg;
+    unless ( grep { $taxclasses[0] ne $_ } @taxclasses ) { #unless there are
+                                                           #different taxclasses
+      $taxclass = $taxclasses[0];
+    }
+  }
+
+  #look for an agent gateway override first
+  my $cardtype;
+  if ( $method eq 'CC' ) {
+    $cardtype = cardtype($payinfo);
+  } elsif ( $method eq 'ECHECK' ) {
+    $cardtype = 'ACH';
+  } else {
+    $cardtype = $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 = '';
+  my( $processor, $login, $password, $action, @bop_options );
+  if ( $override ) { #use a payment gateway override
+
+    $payment_gateway = $override->payment_gateway;
+
+    $processor   = $payment_gateway->gateway_module;
+    $login       = $payment_gateway->gateway_username;
+    $password    = $payment_gateway->gateway_password;
+    $action      = $payment_gateway->gateway_action;
+    @bop_options = $payment_gateway->options;
+
+  } else { #use the standard settings from the config
+
+    ( $processor, $login, $password, $action, @bop_options ) =
+      $self->default_payment_gateway($method);
+
+  }
+
+  ###
+  # massage data
+  ###
+
+  my $address = exists($options{'address1'})
+                    ? $options{'address1'}
+                    : $self->address1;
+  my $address2 = exists($options{'address2'})
+                    ? $options{'address2'}
+                    : $self->address2;
+  $address .= ", ". $address2 if length($address2);
+
+  my $o_payname = exists($options{'payname'})
+                    ? $options{'payname'}
+                    : $self->payname;
+  my($payname, $payfirst, $paylast);
+  if ( $o_payname && $method ne 'ECHECK' ) {
+    ($payname = $o_payname) =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
+      or return "Illegal payname $payname";
+    ($payfirst, $paylast) = ($1, $2);
+  } else {
+    $payfirst = $self->getfield('first');
+    $paylast = $self->getfield('last');
+    $payname =  "$payfirst $paylast";
+  }
+
+  my @invoicing_list = $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 = ();
+
+  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') );
+      
+  my $paydate = '';
+  if ( $method eq 'CC' ) { 
+
+    $content{card_number} = $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;
+
+    $content{recurring_billing} = 'YES'
+      if qsearch('cust_pay', { 'custnum' => $self->custnum,
+                               'payby'   => 'CARD',
+                               'payinfo' => $payinfo,
+                             } )
+      || qsearch('cust_pay', { 'custnum' => $self->custnum,
+                               'payby'   => 'CARD',
+                               'paymask' => $self->mask_payinfo('CARD', $payinfo),
+                             } );
+
+
+  } elsif ( $method eq 'ECHECK' ) {
+    ( $content{account_number}, $content{routing_code} ) =
+      split('@', $payinfo);
+    $content{bank_name} = $o_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{account_name} = $payname;
+    $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 ( $method eq 'LEC' ) {
+    $content{phone} = $payinfo;
+  }
+
+  ###
+  # 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; $method transaction aborted."
+    if $self->balance < $balance;
+    #&& $self->balance < $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 ).
+         "); $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'       => $amount,
+    '_date'      => '',
+    'payby'      => $method2payby{$method},
+    'payinfo'    => $payinfo,
+    'paydate'    => $paydate,
+    'status'     => 'new',
+    'gatewaynum' => ( $payment_gateway ? $payment_gateway->gatewaynum : '' ),
+  };
+  $cust_pay_pending->payunique( $options{payunique} )
+    if 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*/, $action );
+
+  my $transaction = new Business::OnlinePayment( $processor, @bop_options );
+  $transaction->content(
+    'type'           => $method,
+    'login'          => $login,
+    'password'       => $password,
+    'action'         => $action1,
+    'description'    => $options{'description'},
+    'amount'         => $amount,
+    #'invoice_number' => $options{'invnum'},
+    'customer_id'    => $self->custnum,
+    'last_name'      => $paylast,
+    'first_name'     => $payfirst,
+    'name'           => $payname,
+    'address'        => $address,
+    'city'           => ( exists($options{'city'})
+                            ? $options{'city'}
+                            : $self->city          ),
+    'state'          => ( exists($options{'state'})
+                            ? $options{'state'}
+                            : $self->state          ),
+    'zip'            => ( exists($options{'zip'})
+                            ? $options{'zip'}
+                            : $self->zip          ),
+    'country'        => ( exists($options{'country'})
+                            ? $options{'country'}
+                            : $self->country          ),
+    'referer'        => 'http://cleanwhisker.420.am/',
+    'email'          => $email,
+    'phone'          => $self->daytime || $self->night,
+    %content, #after
+  );
+
+  $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() && $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( $processor, @bop_options );
+
+    my %capture = (
+      %content,
+      type           => $method,
+      action         => $action2,
+      login          => $login,
+      password       => $password,
+      order_number   => $ordernum,
+      amount         => $amount,
+      authorization  => $auth,
+      description    => $options{'description'},
+    );
+
+    foreach my $field (qw( authorization_source_code returned_ACI
+                           transaction_identifier validation_code           
+                           transaction_sequence_num local_transaction_date    
+                           local_transaction_time AVS_result_code          )) {
+      $capture{$field} = $transaction->$field() if $transaction->can($field);
+    }
+
+    $capture->content( %capture );
+
+    $capture->submit();
+
+    unless ( $capture->is_success ) {
+      my $e = "Authorization successful but capture failed, custnum #".
+              $self->custnum. ': '.  $capture->result_code.
+              ": ". $capture->error_message;
+      warn $e;
+      return $e;
+    }
+
+  }
+
+  $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;
+
+  ###
+  # remove paycvv after initial transaction
+  ###
+
+  #false laziness w/misc/process/payment.cgi - check both to make sure working
+  # correctly
+  if ( defined $self->dbdef_table->column('paycvv')
+       && length($self->paycvv)
+       && ! grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save')
+  ) {
+    my $error = $self->remove_cvv;
+    if ( $error ) {
+      warn "WARNING: error removing cvv: $error\n";
+    }
+  }
+
+  ###
+  # result handling
+  ###
+
+  if ( $transaction->is_success() ) {
+
+    my $paybatch = '';
+    if ( $payment_gateway ) { # agent override
+      $paybatch = $payment_gateway->gatewaynum. '-';
+    }
+
+    $paybatch .= "$processor:". $transaction->authorization;
+
+    $paybatch .= ':'. $transaction->order_number
+      if $transaction->can('order_number')
+      && length($transaction->order_number);
+
+    my $cust_pay = new FS::cust_pay ( {
+       'custnum'  => $self->custnum,
+       'invnum'   => $options{'invnum'},
+       'paid'     => $amount,
+       '_date'    => '',
+       'payby'    => $method2payby{$method},
+       'payinfo'  => $payinfo,
+       'paybatch' => $paybatch,
+       'paydate'  => $paydate,
+    } );
+    #doesn't hurt to know, even though the dup check is in cust_pay_pending now
+    $cust_pay->payunique( $options{payunique} ) if 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: $method captured but payment not recorded - ".
+                "error inserting payment ($processor): $error2".
+                " (previously tried insert with invnum #$options{'invnum'}" .
+                ": $error ) - pending payment saved as paypendingnum ".
+                $cust_pay_pending->paypendingnum. "\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');
+    my $cpp_done_err = $cust_pay_pending->replace;
+
+    if ( $cpp_done_err ) {
+
+      $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+      my $e = "WARNING: $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;
+      return ''; #no error
+
+    }
+
+  } else {
+
+    my $perror = "$processor error: ". $transaction->error_message;
+
+    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 $processor";
+      }
+
+      $perror .= "No error_message returned from $processor -- ".
+                 ( 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 = { error => $transaction->error_message };
+
+      my $error = send_email(
+        'from'    => $conf->config('invoice_from'),
+        'to'      => [ grep { $_ ne 'POST' } $self->invoicing_list ],
+        'subject' => 'Your payment could not be processed',
+        'body'    => [ $template->fill_in(HASH => $templ_hash) ],
+      );
+
+      $perror .= " (also received error sending decline notification: $error)"
+        if $error;
+
+    }
+
+    $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: $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 fake_bop
+
+=cut
+
+sub fake_bop {
+  my( $self, $method, $amount, %options ) = @_;
+
+  if ( $options{'fake_failure'} ) {
+     return "Error: No error; test failure requested with fake_failure";
+  }
+
+  my %method2payby = (
+    'CC'     => 'CARD',
+    'ECHECK' => 'CHEK',
+    'LEC'    => 'LECB',
+  );
+
+  #my $paybatch = '';
+  #if ( $payment_gateway ) { # 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'     => $amount,
+     '_date'    => '',
+     'payby'    => $method2payby{$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 default_payment_gateway
+
+=cut
+
+sub default_payment_gateway {
+  my( $self, $method ) = @_;
+
+  die "Real-time processing not enabled\n"
+    unless $conf->exists('business-onlinepayment');
+
+  #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, $method, %options ) = @_;
+  if ( $DEBUG ) {
+    warn "$me realtime_refund_bop: $method refund\n";
+    warn "  $_ => $options{$_}\n" foreach keys %options;
+  }
+
+  eval "use Business::OnlinePayment";  
+  die $@ if $@;
+
+  ###
+  # 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 ) ;
+  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;
+      @bop_options = $payment_gateway->options;
+
+    } else { #try the default gateway
+
+      my( $conf_processor, $unused_action );
+      ( $conf_processor, $login, $password, $unused_action, @bop_options ) =
+        $self->default_payment_gateway($method);
+
+      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 $cardtype;
+    if ( $method eq 'CC' ) {
+      $cardtype = cardtype($self->payinfo);
+    } elsif ( $method eq 'ECHECK' ) {
+      $cardtype = 'ACH';
+    } else {
+      $cardtype = $method;
+    }
+    my $override =
+           qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                               cardtype => $cardtype,
+                                               taxclass => '',              } )
+        || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+                                               cardtype => '',
+                                               taxclass => '',              } );
+
+    if ( $override ) { #use a payment gateway override
+      my $payment_gateway = $override->payment_gateway;
+
+      $processor   = $payment_gateway->gateway_module;
+      $login       = $payment_gateway->gateway_username;
+      $password    = $payment_gateway->gateway_password;
+      #$action      = $payment_gateway->gateway_action;
+      @bop_options = $payment_gateway->options;
+
+    } else { #use the standard settings from the config
+
+      my $unused_action;
+      ( $processor, $login, $password, $unused_action, @bop_options ) =
+        $self->default_payment_gateway($method);
+
+    }
+
+  }
+  return "neither amount nor paynum specified" unless $amount;
+
+  my %content = (
+    'type'           => $method,
+    'login'          => $login,
+    'password'       => $password,
+    'order_number'   => $order_number,
+    'amount'         => $amount,
+    'referer'        => 'http://cleanwhisker.420.am/',
+  );
+  $content{authorization} = $auth
+    if length($auth); #echeck/ACH transactions have an order # but no auth
+                      #(at least with authorize.net)
+
+  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 );
+    $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 && $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 ( $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 ( $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 ( $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 %method2payby = (
+    'CC'     => 'CARD',
+    'ECHECK' => 'CHEK',
+    'LEC'    => 'LECB',
+  );
+
+  my $paybatch = "$processor:". $refund->authorization;
+  $paybatch .= ':'. $refund->order_number
+    if $refund->can('order_number') && $refund->order_number;
+
+  while ( $cust_pay && $cust_pay->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'    => $method2payby{$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_credited + $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 total_owed
+
+Returns the total owed for this customer on all invoices
+(see L<FS::cust_bill/owed>).
+
+=cut
+
+sub total_owed {
+  my $self = shift;
+  $self->total_owed_date(2145859200); #12/31/2037
+}
+
+=item total_owed_date TIME
+
+Returns the total owed for this customer on all invoices with date earlier than
+TIME.  TIME is specified as a UNIX timestamp; see L<perlfunc/"time">).  Also
+see L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub total_owed_date {
+  my $self = shift;
+  my $time = shift;
+  my $total_bill = 0;
+  foreach my $cust_bill (
+    grep { $_->_date <= $time }
+      qsearch('cust_bill', { 'custnum' => $self->custnum, } )
+  ) {
+    $total_bill += $cust_bill->owed;
+  }
+  sprintf( "%.2f", $total_bill );
+}
+
+=item apply_payments_and_credits
+
+Applies unapplied payments and credits.
+
+In most cases, this new method should be used in place of sequential
+apply_payments and apply_credits methods.
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub apply_payments_and_credits {
+  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->select_for_update; #mutex
+
+  foreach my $cust_bill ( $self->open_cust_bill ) {
+    my $error = $cust_bill->apply_payments_and_credits;
+    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_credited ) {
+    $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';
+
+  my $credit;
+  foreach my $cust_bill ( @invoices ) {
+    my $amount;
+
+    if ( !defined($credit) || $credit->credited == 0) {
+      $credit = pop @credits or last;
+    }
+
+    if ($cust_bill->owed >= $credit->credited) {
+      $amount=$credit->credited;
+    }else{
+      $amount=$cust_bill->owed;
+    }
+    
+    my $cust_credit_bill = new FS::cust_credit_bill ( {
+      'crednum' => $credit->crednum,
+      'invnum'  => $cust_bill->invnum,
+      'amount'  => $amount,
+    } );
+    my $error = $cust_credit_bill->insert;
+    if ( $error ) {
+      $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+      die $error;
+    }
+    
+    redo if ($cust_bill->owed > 0);
+
+  }
+
+  my $total_credited = $self->total_credited;
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  return $total_credited;
+}
+
+=item apply_payments
+
+Applies (see L<FS::cust_bill_pay>) unapplied payments (see L<FS::cust_pay>)
+to outstanding invoice balances in chronological order.
+
+ #and returns the value of any remaining unapplied payments.
+
+Dies if there is an error.
+
+=cut
+
+sub apply_payments {
+  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->select_for_update; #mutex
+
+  #return 0 unless
+
+  my @payments = sort { $b->_date <=> $a->_date } ( grep { $_->unapplied > 0 }
+      qsearch('cust_pay', { 'custnum' => $self->custnum } ) );
+
+  my @invoices = sort { $a->_date <=> $b->_date} (grep { $_->owed > 0 }
+      qsearch('cust_bill', { 'custnum' => $self->custnum } ) );
+
+  my $payment;
+
+  foreach my $cust_bill ( @invoices ) {
+    my $amount;
+
+    if ( !defined($payment) || $payment->unapplied == 0 ) {
+      $payment = pop @payments or last;
+    }
+
+    if ( $cust_bill->owed >= $payment->unapplied ) {
+      $amount = $payment->unapplied;
+    } else {
+      $amount = $cust_bill->owed;
+    }
+
+    my $cust_bill_pay = new FS::cust_bill_pay ( {
+      'paynum' => $payment->paynum,
+      'invnum' => $cust_bill->invnum,
+      'amount' => $amount,
+    } );
+    my $error = $cust_bill_pay->insert;
+    if ( $error ) {
+      $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+      die $error;
+    }
+
+    redo if ( $cust_bill->owed > 0);
+
+  }
+
+  my $total_unapplied_payments = $self->total_unapplied_payments;
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  return $total_unapplied_payments;
+}
+
+=item total_credited
+
+Returns the total outstanding credit (see L<FS::cust_credit>) for this
+customer.  See L<FS::cust_credit/credited>.
+
+=cut
+
+sub total_credited {
+  my $self = shift;
+  my $total_credit = 0;
+  foreach my $cust_credit ( qsearch('cust_credit', {
+    'custnum' => $self->custnum,
+  } ) ) {
+    $total_credit += $cust_credit->credited;
+  }
+  sprintf( "%.2f", $total_credit );
+}
+
+=item total_unapplied_payments
+
+Returns the total unapplied payments (see L<FS::cust_pay>) for this customer.
+See L<FS::cust_pay/unapplied>.
+
+=cut
+
+sub total_unapplied_payments {
+  my $self = shift;
+  my $total_unapplied = 0;
+  foreach my $cust_pay ( qsearch('cust_pay', {
+    'custnum' => $self->custnum,
+  } ) ) {
+    $total_unapplied += $cust_pay->unapplied;
+  }
+  sprintf( "%.2f", $total_unapplied );
+}
+
+=item 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;
+  foreach my $cust_refund ( qsearch('cust_refund', {
+    'custnum' => $self->custnum,
+  } ) ) {
+    $total_unapplied += $cust_refund->unapplied;
+  }
+  sprintf( "%.2f", $total_unapplied );
+}
+
+=item balance
+
+Returns the balance for this customer (total_owed plus total_unrefunded, minus
+total_credited minus total_unapplied_payments).
+
+=cut
+
+sub balance {
+  my $self = shift;
+  sprintf( "%.2f",
+      $self->total_owed
+    + $self->total_unapplied_refunds
+    - $self->total_credited
+    - $self->total_unapplied_payments
+  );
+}
+
+=item balance_date TIME
+
+Returns the balance for this customer, only considering invoices with date
+earlier than TIME (total_owed_date minus total_credited minus
+total_unapplied_payments).  TIME is specified as a UNIX timestamp; see
+L<perlfunc/"time">).  Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+=cut
+
+sub balance_date {
+  my $self = shift;
+  my $time = shift;
+  sprintf( "%.2f",
+        $self->total_owed_date($time)
+      + $self->total_unapplied_refunds
+      - $self->total_credited
+      - $self->total_unapplied_payments
+  );
+}
+
+=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 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 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_cust_main [ DEPTH [ EXCLUDE_HASHREF ] ]
+
+Returns an array of customers referred by this customer (referral_custnum set
+to this custnum).  If DEPTH is given, recurses up to the given depth, returning
+customers referred by customers referred by this customer and so on, inclusive.
+The default behavior is DEPTH 1 (no recursion).
+
+=cut
+
+sub referral_cust_main {
+  my $self = shift;
+  my $depth = @_ ? shift : 1;
+  my $exclude = @_ ? shift : {};
+
+  my @cust_main =
+    map { $exclude->{$_->custnum}++; $_; }
+      grep { ! $exclude->{ $_->custnum } }
+        qsearch( 'cust_main', { 'referral_custnum' => $self->custnum } );
+
+  if ( $depth > 1 ) {
+    push @cust_main,
+      map { $_->referral_cust_main($depth-1, $exclude) }
+        @cust_main;
+  }
+
+  @cust_main;
+}
+
+=item referral_cust_main_ncancelled
+
+Same as referral_cust_main, except only returns customers with uncancelled
+packages.
+
+=cut
+
+sub referral_cust_main_ncancelled {
+  my $self = shift;
+  grep { scalar($_->ncancelled_pkgs) } $self->referral_cust_main;
+}
+
+=item referral_cust_pkg [ DEPTH ]
+
+Like referral_cust_main, except returns a flat list of all unsuspended (and
+uncancelled) packages for each customer.  The number of items in this list may
+be useful for comission calculations (perhaps after a C<grep { my $pkgpart = $_->pkgpart; grep { $_ == $pkgpart } @commission_worthy_pkgparts> } $cust_main-> ).
+
+=cut
+
+sub referral_cust_pkg {
+  my $self = shift;
+  my $depth = @_ ? shift : 1;
+
+  map { $_->unsuspended_pkgs }
+    grep { $_->unsuspended_pkgs }
+      $self->referral_cust_main($depth);
+}
+
+=item 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
+
+Applies a credit to this customer.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub credit {
+  my( $self, $amount, $reason, %options ) = @_;
+  my $cust_credit = new FS::cust_credit {
+    'custnum' => $self->custnum,
+    'amount'  => $amount,
+    'reason'  => $reason,
+  };
+  $cust_credit->insert(%options);
+}
+
+=item charge AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ]
+
+Creates a one-time charge for this customer.  If there is an error, returns
+the error, otherwise returns false.
+
+=cut
+
+sub charge {
+  my $self = shift;
+  my ( $amount, $pkg, $comment, $taxclass, $additional, $classnum );
+  if ( ref( $_[0] ) ) {
+    $amount     = $_[0]->{amount};
+    $pkg        = exists($_[0]->{pkg}) ? $_[0]->{pkg} : 'One-time charge';
+    $comment    = exists($_[0]->{comment}) ? $_[0]->{comment}
+                                           : '$'. sprintf("%.2f",$amount);
+    $taxclass   = exists($_[0]->{taxclass}) ? $_[0]->{taxclass} : '';
+    $classnum   = exists($_[0]->{classnum}) ? $_[0]->{classnum} : '';
+    $additional = $_[0]->{additional};
+  }else{
+    $amount     = shift;
+    $pkg        = @_ ? shift : 'One-time charge';
+    $comment    = @_ ? shift : '$'. sprintf("%.2f",$amount);
+    $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 : '',
+    'taxclass' => $taxclass,
+  } );
+
+  my %options = ( ( map { ("additional_info$_" => $additional->[$_] ) }
+                        ( 0 .. @$additional - 1 )
+                  ),
+                  'additional_count' => scalar(@$additional),
+                  'setup_fee' => $amount,
+                );
+
+  my $error = $part_pkg->insert( options => \%options );
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  my $pkgpart = $part_pkg->pkgpart;
+  my %type_pkgs = ( 'typenum' => $self->agent->typenum, 'pkgpart' => $pkgpart );
+  unless ( qsearchs('type_pkgs', \%type_pkgs ) ) {
+    my $type_pkgs = new FS::type_pkgs \%type_pkgs;
+    $error = $type_pkgs->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  my $cust_pkg = new FS::cust_pkg ( {
+    'custnum' => $self->custnum,
+    'pkgpart' => $pkgpart,
+  } );
+
+  $error = $cust_pkg->insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+
+}
+
+=item cust_bill
+
+Returns all the invoices (see L<FS::cust_bill>) for this customer.
+
+=cut
+
+sub cust_bill {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch('cust_bill', { 'custnum' => $self->custnum, } )
+}
+
+=item open_cust_bill
+
+Returns all the open (owed > 0) invoices (see L<FS::cust_bill>) for this
+customer.
+
+=cut
+
+sub open_cust_bill {
+  my $self = shift;
+  grep { $_->owed > 0 } $self->cust_bill;
+}
+
+=item cust_credit
+
+Returns all the credits (see L<FS::cust_credit>) for this customer.
+
+=cut
+
+sub cust_credit {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_credit', { 'custnum' => $self->custnum } )
+}
+
+=item cust_pay
+
+Returns all the payments (see L<FS::cust_pay>) for this customer.
+
+=cut
+
+sub cust_pay {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_pay', { 'custnum' => $self->custnum } )
+}
+
+=item cust_pay_void
+
+Returns all voided payments (see L<FS::cust_pay_void>) for this customer.
+
+=cut
+
+sub cust_pay_void {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_pay_void', { 'custnum' => $self->custnum } )
+}
+
+=item cust_pay_batch
+
+Returns all batched payments (see L<FS::cust_pay_void>) for this customer.
+
+=cut
+
+sub cust_pay_batch {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_pay_batch', { 'custnum' => $self->custnum } )
+}
+
+=item cust_refund
+
+Returns all the refunds (see L<FS::cust_refund>) for this customer.
+
+=cut
+
+sub cust_refund {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_refund', { 'custnum' => $self->custnum } )
+}
+
+=item 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 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 country_full
+
+Returns this customer's full country name
+
+=cut
+
+sub country_full {
+  my $self = shift;
+  code2country($self->country);
+}
+
+=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 my %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 = ();
+
+  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;
+
+}
+
+=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
+no active recurring packages, but otherwise unsuspended/uncancelled).
+
+=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
+active recurring packages).
+
+=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 $recurring_sql = "
+  #  '0' != ( select freq from part_pkg
+  #             where cust_pkg.pkgpart = part_pkg.pkgpart )
+  #";
+
+  "
+    0 < ( $select_count_pkgs )
+    AND 0 = ( $select_count_pkgs AND $recurring_sql
+                  AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )
+            )
+  ";
+}
+
+=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_credited 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 - set to true to remove all customer comparison clauses, for totals
+
+=item where - WHERE clause hashref (elements "AND"ed together) (typically used with the total option)
+
+=item join - JOIN clause (typically used with the total option)
+
+=item 
+
+=back
+
+=cut
+
+sub balance_date_sql {
+  my( $class, $start, $end, %opt ) = @_;
+
+  my $owed         = FS::cust_bill->owed_sql;
+  my $unapp_refund = FS::cust_refund->unapplied_sql;
+  my $unapp_credit = FS::cust_credit->unapplied_sql;
+  my $unapp_pay    = FS::cust_pay->unapplied_sql;
+
+  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 _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 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> and/or I<company> 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
+  } elsif ( $search =~ /^\s*(\d+)\s*$/
+            || ( $conf->config('cust_main-agent_custid-format') eq 'ww?d+'
+                 && $search =~ /^\s*(\w\w?\d+)\s*$/
+               )
+          )
+  {
+
+    push @cust_main, qsearch( {
+      'table'     => 'cust_main',
+      'hashref'   => { 'custnum' => $1, %options },
+      'extra_sql' => " AND $agentnums_sql", #agent virtualization
+    } );
+
+    push @cust_main, qsearch( {
+      'table'     => 'cust_main',
+      'hashref'   => { 'agent_custid' => $1, %options },
+      'extra_sql' => " AND $agentnums_sql", #agent virtualization
+    } );
+
+  } 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
+
+    foreach my $prefix ( '', 'ship_' ) {
+      push @cust_main, qsearch( {
+        'table'     => 'cust_main',
+        'hashref'   => { $prefix.'first'   => $first,
+                         $prefix.'last'    => $last,
+                         $prefix.'company' => $company,
+                         %options,
+                       },
+        'extra_sql' => " AND $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
+              )";
+
+    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/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%" }, },
+        ;
+      }
+
+      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 );
+      }
+
+    }
+
+    #eliminate duplicates
+    my %saw = ();
+    @cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
+
+  }
+
+  @cust_main;
+
+}
+
+=item check_and_rebuild_fuzzyfiles
+
+=cut
+
+use vars qw(@fuzzyfields);
+@fuzzyfields = ( 'last', 'first', 'company' );
+
+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 LASTNAME COMPANY
+
+=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 (qw( first last company )) {
+    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_import
+
+=cut
+
+sub batch_import {
+  my $param = shift;
+  #warn join('-',keys %$param);
+  my $fh = $param->{filehandle};
+  my $agentnum = $param->{agentnum};
+
+  my $refnum = $param->{refnum};
+  my $pkgpart = $param->{pkgpart};
+
+  #my @fields = @{$param->{fields}};
+  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';
+  } else {
+    die "unknown format $format";
+  }
+
+  eval "use Text::CSV_XS;";
+  die $@ if $@;
+
+  my $csv = new Text::CSV_XS;
+  #warn $csv;
+  #warn $fh;
+
+  my $imported = 0;
+  #my $columns;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+  
+  #while ( $columns = $csv->getline($fh) ) {
+  my $line;
+  while ( defined($line=<$fh>) ) {
+
+    $csv->parse($line) or do {
+      $dbh->rollback if $oldAutoCommit;
+      return "can't parse: ". $csv->error_input();
+    };
+
+    my @columns = $csv->fields();
+    #warn join('-',@columns);
+
+    my %cust_main = (
+      agentnum => $agentnum,
+      refnum   => $refnum,
+      country  => $conf->config('countrydefault') || 'US',
+      payby    => $payby, #default
+      paydate  => '12/2037', #default
+    );
+    my $billtime = time;
+    my %cust_pkg = ( pkgpart => $pkgpart );
+    my %svc_acct = ();
+    foreach my $field ( @fields ) {
+
+      if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) {
+
+        #$cust_pkg{$1} = str2time( shift @$columns );
+        if ( $1 eq 'pkgpart' ) {
+          $cust_pkg{$1} = shift @columns;
+        } elsif ( $1 eq 'setup' ) {
+          $billtime = str2time(shift @columns);
+        } else {
+          $cust_pkg{$1} = str2time( shift @columns );
+        } 
+
+      } elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) {
+
+        $svc_acct{$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;
+        }
+
+        #$cust_main{$field} = shift @$columns; 
+        $cust_main{$field} = shift @columns; 
+      }
+    }
+
+    $cust_main{'payby'} = 'CARD' if 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_acct = ();
+      if ( $svc_acct{'username'} ) {
+        my $part_pkg = $cust_pkg->part_pkg;
+       unless ( $part_pkg ) {
+         $dbh->rollback if $oldAutoCommit;
+         return "unknown pkgpart: ". $cust_pkg{'pkgpart'};
+       } 
+        $svc_acct{svcpart} = $part_pkg->svcpart( 'svc_acct' );
+        push @svc_acct, new FS::svc_acct ( \%svc_acct )
+      }
+
+      $hash{$cust_pkg} = \@svc_acct;
+    }
+
+    my $error = $cust_main->insert( \%hash, $invoicing_list );
+
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "can't insert customer 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";
+      }
+
+    }
+
+    $imported++;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  return "Empty file!" unless $imported;
+
+  ''; #no error
+
+}
+
+=item batch_charge
+
+=cut
+
+sub batch_charge {
+  my $param = shift;
+  #warn join('-',keys %$param);
+  my $fh = $param->{filehandle};
+  my @fields = @{$param->{fields}};
+
+  eval "use 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 ($customer, $template, %options) = @_;
+
+  return unless $conf->exists($template);
+
+  my $from = $conf->config('invoice_from') if $conf->exists('invoice_from');
+  $from = $options{from} if exists($options{from});
+
+  my $to = join(',', $customer->invoicing_list_emailonly);
+  $to = $options{to} if exists($options{to});
+  
+  my $subject = "Notice from " . $conf->config('company_name')
+    if $conf->exists('company_name');
+  $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');
+  $FS::notify_template::_template::company_address =
+    join("\n", $conf->config('company_address') ). "\n";
+
+  my $paydate = $customer->paydate || '2037-12-31';
+  $FS::notify_template::_template::first = $customer->first;
+  $FS::notify_template::_template::last = $customer->last;
+  $FS::notify_template::_template::company = $customer->company;
+  $FS::notify_template::_template::payinfo = $customer->mask_payinfo;
+  my $payby = $customer->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') ) {
+      $letter_data{returnaddress} =
+        join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg,
+                          $conf->config('company_address')
+        );
+    } else {
+      $letter_data{returnaddress} = '~';
+    }
+  }
+
+  $letter_data{conf_dir} = "$FS::UID::conf_dir/conf.$FS::UID::datasrc";
+
+  $letter_data{company_name} = $conf->config('company_name');
+
+  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) ];
+}
+
+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 = '';
+  if ( driver_name =~ /^Pg/i ) {
+    $regexp = '~';
+  } elsif ( driver_name =~ /^mysql/i ) {
+    $regexp = 'REGEXP';
+  } else {
+    die "don't know how to use regular expressions in ". driver_name. " databases";
+  }
+
+  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_option AS peo_cust_bill_age
+          ON ( part_event.eventpart = peo_cust_bill_age.eventpart
+               AND peo_cust_bill_age.optionname = 'cust_bill_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 peo_cust_bill_age.optionname != 'cust_bill_age'
+           THEN -1
+          ELSE ". FS::part_event::Condition->age2seconds_sql('peo_cust_bill_age.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,
+      );
+}
+
+=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_Mixin.pm b/FS/FS/cust_main_Mixin.pm
new file mode 100644 (file)
index 0000000..ced0a1f
--- /dev/null
@@ -0,0 +1,269 @@
+package FS::cust_main_Mixin;
+
+use strict;
+use vars qw( $DEBUG );
+use FS::UID qw(dbh);
+use FS::cust_main;
+
+$DEBUG = 0;
+
+=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
+
+=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 cust_unlinked_msg { '(unlinked)'; }
+sub cust_linked { $_[0]->custnum; }
+
+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
+
+Given an object that contains fields from cust_main (say, from a JOINed
+search; see httemplate/search/ for examples), returns the equivalent of the
+corresponding FS::cust_main method, or "0" if this object is not linked to
+a customer.
+
+=cut
+
+foreach my $sub (qw( prospect active inactive suspended cancelled )) {
+  eval "
+    sub ${sub}_sql {
+      my \$self = shift;
+      \$self->cust_linked
+        ? FS::cust_main::${sub}_sql(\$self)
+        : '0';
+      }
+  ";
+  die $@ if $@;
+}
+
+=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 (file)
index 0000000..17f3460
--- /dev/null
@@ -0,0 +1,291 @@
+package FS::cust_main_county;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $conf
+             @cust_main_county %cust_main_county $countyflag );
+use Exporter;
+use FS::Record qw( qsearch );
+
+@ISA = qw( FS::Record );
+@EXPORT_OK = qw( regionselector );
+
+@cust_main_county = ();
+$countyflag = '';
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::cust_main_county'} = sub { 
+  $conf = new FS::Conf;
+};
+
+=head1 NAME
+
+FS::cust_main_county - Object methods for cust_main_county objects
+
+=head1 SYNOPSIS
+
+  use FS::cust_main_county;
+
+  $record = new FS::cust_main_county \%hash;
+  $record = new FS::cust_main_county { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  ($county_html, $state_html, $country_html) =
+    FS::cust_main_county::regionselector( $county, $state, $country );
+
+=head1 DESCRIPTION
+
+An FS::cust_main_county object represents a tax rate, defined by locale.
+FS::cust_main_county inherits from FS::Record.  The following fields are
+currently supported:
+
+=over 4
+
+=item taxnum - primary key (assigned automatically for new tax rates)
+
+=item state
+
+=item county
+
+=item country
+
+=item tax - percentage
+
+=item taxclass
+
+=item exempt_amount
+
+=item taxname - if defined, printed on invoices instead of "Tax"
+
+=item setuptax - if 'Y', this tax does not apply to setup fees
+
+=item recurtax - if 'Y', this tax does not apply to recurring fees
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new tax rate.  To add the tax rate to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_main_county'; }
+
+=item insert
+
+Adds this tax rate to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this tax rate from the database.  If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid tax rate.  If there is an error,
+returns the error, otherwise returns false.  Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  $self->exempt_amount(0) unless $self->exempt_amount;
+
+  $self->ut_numbern('taxnum')
+    || $self->ut_anything('state')
+    || $self->ut_textn('county')
+    || $self->ut_text('country')
+    || $self->ut_float('tax')
+    || $self->ut_textn('taxclass') # ...
+    || $self->ut_money('exempt_amount')
+    || $self->ut_textn('taxname')
+    || $self->ut_enum('setuptax', [ '', 'Y' ] )
+    || $self->ut_enum('recurtax', [ '', 'Y' ] )
+    || $self->SUPER::check
+    ;
+
+}
+
+sub taxname {
+  my $self = shift;
+  if ( $self->dbdef_table->column('taxname') ) {
+    return $self->setfield('taxname', $_[0]) if @_;
+    return $self->getfield('taxname');
+  }  
+  return '';
+}
+
+sub setuptax {
+  my $self = shift;
+  if ( $self->dbdef_table->column('setuptax') ) {
+    return $self->setfield('setuptax', $_[0]) if @_;
+    return $self->getfield('setuptax');
+  }  
+  return '';
+}
+
+sub recurtax {
+  my $self = shift;
+  if ( $self->dbdef_table->column('recurtax') ) {
+    return $self->setfield('recurtax', $_[0]) if @_;
+    return $self->getfield('recurtax');
+  }  
+  return '';
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item regionselector [ COUNTY STATE COUNTRY [ PREFIX [ ONCHANGE [ 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_invoice.pm b/FS/FS/cust_main_invoice.pm
new file mode 100644 (file)
index 0000000..71029d0
--- /dev/null
@@ -0,0 +1,173 @@
+package FS::cust_main_invoice;
+
+use strict;
+use vars qw(@ISA $conf);
+use Exporter;
+use FS::Record qw( qsearchs );
+use FS::Conf;
+use FS::cust_main;
+use FS::svc_acct;
+use FS::Msgcat qw(gettext);
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::cust_main_invoice - Object methods for cust_main_invoice records
+
+=head1 SYNOPSIS
+
+  use FS::cust_main_invoice;
+
+  $record = new FS::cust_main_invoice \%hash;
+  $record = new FS::cust_main_invoice { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  $email_address = $record->address;
+
+=head1 DESCRIPTION
+
+An FS::cust_main_invoice object represents an invoice destination.  FS::cust_main_invoice inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item destnum - primary key
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item dest - Invoice destination: If numeric, a svcnum (see L<FS::svc_acct>), if string, a literal email address, `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;
+
+  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 ( $self->dest =~ /^([\w\.\-\&\+]+)\@(([\w\.\-]+\.)+\w+)$/ ) {
+    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;
+  }
+}
+
+=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 (file)
index 0000000..4732d12
--- /dev/null
@@ -0,0 +1,131 @@
+package FS::cust_main_note;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=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 otaker - 
+
+=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_text('otaker')
+    || $self->ut_anything('comments')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=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 (file)
index 0000000..90a14ea
--- /dev/null
@@ -0,0 +1,882 @@
+package FS::cust_pay;
+
+use strict;
+use vars qw( @ISA $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_Mixin;
+use FS::cust_bill;
+use FS::cust_bill_pay;
+use FS::cust_pay_refund;
+use FS::cust_main;
+use FS::cust_pay_void;
+
+@ISA = qw(FS::Record FS::cust_main_Mixin FS::payinfo_Mixin  );
+
+$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 otaker - order taker (assigned automatically, see L<FS::UID>)
+
+=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'
+
+=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
+
+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.  An 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;
+    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;
+
+  #my $cust_main = $self->cust_main;
+  if ( $conf->exists('payment_receipt_email')
+       && grep { $_ !~ /^(POST|FAX)$/ } $cust_main->invoicing_list
+  ) {
+
+    $cust_bill ||= ($cust_main->cust_bill)[-1]; #rather inefficient though?
+
+    my $error;
+    if (    ( exists($options{'manual'}) && $options{'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;
+      $payinfo = $self->paymask if $payby eq 'CARD' || $payby eq 'CHEK';
+      $payby =~ s/^CHEK$/Electronic check/;
+
+      $error = send_email(
+        'from'    => $conf->config('invoice_from'), #??? well as good as any
+        'to'      => \@invoicing_list,
+        'subject' => 'Payment receipt',
+        'body'    => [ $receipt_template->fill_in( HASH => {
+                       'date'    => time2str("%a %B %o, %Y", $self->_date),
+                       'name'    => $cust_main->name,
+                       'paynum'  => $self->paynum,
+                       'paid'    => sprintf("%.2f", $self->paid),
+                       'payby'   => ucfirst(lc($payby)),
+                       'payinfo' => $payinfo,
+                       'balance' => $cust_main->balance,
+                     } ) ],
+      );
+
+    } else {
+
+      my $queue = new FS::queue {
+         'paynum' => $self->paynum,
+         'job'    => 'FS::cust_bill::queueable_email',
+      };
+      $error = $queue->insert(
+        'invnum' => $cust_bill->invnum,
+        'template' => 'statement',
+      );
+
+    }
+
+    if ( $error ) {
+      warn "can't send payment receipt/statement: $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->config('deletepayments') ne '' ) {
+
+    my $cust_main = $self->cust_main;
+
+    my $error = send_email(
+      'from'    => $conf->config('invoice_from'), #??? well as good as any
+      'to'      => $conf->config('deletepayments'),
+      'subject' => 'FREESIDE NOTIFICATION: Payment deleted',
+      'body'    => [
+        "This is an automatic message from your Freeside installation\n",
+        "informing you that the following payment has been deleted:\n",
+        "\n",
+        'paynum: '. $self->paynum. "\n",
+        'custnum: '. $self->custnum.
+          " (". $cust_main->last. ", ". $cust_main->first. ")\n",
+        'paid: $'. sprintf("%.2f", $self->paid). "\n",
+        'date: '. time2str("%a %b %e %T %Y", $self->_date). "\n",
+        'payby: '. $self->payby. "\n",
+        'payinfo: '. $self->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_alpha('otaker')
+    || $self->ut_textn('paybatch')
+    || $self->ut_textn('payunique')
+    || $self->ut_enum('closed', [ '', 'Y' ])
+    || $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->otaker(getotaker);
+
+  $self->SUPER::check;
+}
+
+=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 cust_bill_pay
+
+Returns all applications to invoices (see L<FS::cust_bill_pay>) for this
+payment.
+
+=cut
+
+sub cust_bill_pay {
+  my $self = shift;
+  sort {    $a->_date  <=> $b->_date
+         || $a->invnum <=> $b->invnum }
+    qsearch( 'cust_bill_pay', { 'paynum' => $self->paynum } )
+  ;
+}
+
+=item cust_pay_refund
+
+Returns all applications of refunds (see L<FS::cust_pay_refund>) to this
+payment.
+
+=cut
+
+sub cust_pay_refund {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_pay_refund', { 'paynum' => $self->paynum } )
+  ;
+}
+
+
+=item unapplied
+
+Returns the amount of this payment that is still unapplied; which is
+paid minus all payment applications (see L<FS::cust_bill_pay>) and refund
+applications (see L<FS::cust_pay_refund>).
+
+=cut
+
+sub unapplied {
+  my $self = shift;
+  my $amount = $self->paid;
+  $amount -= $_->amount foreach ( $self->cust_bill_pay );
+  $amount -= $_->amount foreach ( $self->cust_pay_refund );
+  sprintf("%.2f", $amount );
+}
+
+=item unrefunded
+
+Returns the amount of this payment that has not been refuned; which is
+paid minus all  refund applications (see L<FS::cust_pay_refund>).
+
+=cut
+
+sub unrefunded {
+  my $self = shift;
+  my $amount = $self->paid;
+  $amount -= $_->amount foreach ( $self->cust_pay_refund );
+  sprintf("%.2f", $amount );
+}
+
+
+=item cust_main
+
+Returns the parent customer object (see L<FS::cust_main>).
+
+=cut
+
+sub cust_main {
+  my $self = shift;
+  qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+}
+
+=item payby_name
+
+Returns a name for the payby field.
+
+=cut
+
+sub payby_name {
+  my $self = shift;
+  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 CLASS METHODS
+
+=over 4
+
+=item unapplied_sql
+
+Returns an SQL fragment to retreive the unapplied amount.
+
+=cut 
+
+sub unapplied_sql {
+  #my $class = shift;
+
+  "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
+                  )
+  ";
+
+}
+
+# _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 $count_sql =
+    "SELECT COUNT(*) FROM cust_pay WHERE otaker IS NULL OR otaker = ''";
+
+  my $sth = dbh->prepare($count_sql) or die dbh->errstr;
+  $sth->execute or die $sth->errstr;
+  my $total = $sth->fetchrow_arrayref->[0];
+
+  local($DEBUG) = 2 if $total > 1000; #could be a while, force progress info
+
+  my $count = 0;
+  my $lastprog = 0;
+  while (1) {
+
+    my $cust_pay = qsearchs( {
+      'table'     => 'cust_pay',
+      'hashref'   => {},
+      'extra_sql' => "WHERE otaker IS NULL OR otaker = ''",
+      'order_by'  => 'ORDER BY paynum LIMIT 1',
+    } );
+
+    return unless $cust_pay;
+
+    my $h_cust_pay = $cust_pay->h_search('insert');
+    if ( $h_cust_pay ) {
+      $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;
+    die $error if $error;
+    $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;
+    }
+
+  }
+
+}
+
+=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 (file)
index 0000000..9ef1e1c
--- /dev/null
@@ -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 (file)
index 0000000..ad39b10
--- /dev/null
@@ -0,0 +1,229 @@
+package FS::cust_pay_pending;
+
+use strict;
+use vars qw( @ISA  @encrypted_fields );
+use FS::Record qw( qsearch qsearchs );
+use FS::payby;
+use FS::payinfo_Mixin;
+use FS::cust_main;
+use FS::cust_pay;
+
+@ISA = qw(FS::Record FS::payinfo_Mixin);
+
+@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 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_foreign_keyn('paynum', 'cust_pay', 'paynum' )
+    || $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;
+}
+
+=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 (file)
index 0000000..cb9dbce
--- /dev/null
@@ -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 (file)
index 0000000..de05f71
--- /dev/null
@@ -0,0 +1,225 @@
+package FS::cust_pay_void; 
+use strict;
+use vars qw( @ISA @encrypted_fields );
+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;
+
+@ISA = qw( FS::Record FS::payinfo_Mixin );
+
+@encrypted_fields = ('payinfo');
+
+=head1 NAME
+
+FS::cust_pay_void - Object methods for cust_pay_void objects
+
+=head1 SYNOPSIS
+
+  use FS::cust_pay_void;
+
+  $record = new FS::cust_pay_void \%hash;
+  $record = new FS::cust_pay_void { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pay_void object represents a voided payment.  The following fields
+are currently supported:
+
+=over 4
+
+=item paynum - primary key (assigned automatically for new payments)
+
+=item custnum - customer (see L<FS::cust_main>)
+
+=item paid - Amount of this payment
+
+=item _date - specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=item payby - `CARD' (credit cards), `CHEK' (electronic check/ACH),
+`LECB' (phone bill billing), `BILL' (billing), `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 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!";
+}
+
+=item check
+
+Checks all fields to make sure this is a valid voided payment.  If there is an
+error, returns the error, otherwise returns false.  Called by the insert
+method.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error =
+    $self->ut_numbern('paynum')
+    || $self->ut_numbern('custnum')
+    || $self->ut_money('paid')
+    || $self->ut_number('_date')
+    || $self->ut_textn('paybatch')
+    || $self->ut_enum('closed', [ '', 'Y' ])
+    || $self->ut_numbern('void_date')
+    || $self->ut_textn('reason')
+  ;
+  return $error if $error;
+
+  return "paid must be > 0 " if $self->paid <= 0;
+
+  return "unknown cust_main.custnum: ". $self->custnum
+    unless $self->invnum
+           || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+  $self->void_date(time) unless $self->void_date;
+
+  $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP|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);
+
+  $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 } );
+}
+
+=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 (file)
index 0000000..d413596
--- /dev/null
@@ -0,0 +1,2091 @@
+package FS::cust_pkg;
+
+use strict;
+use vars qw(@ISA $disable_agentcheck $DEBUG);
+use List::Util qw(max);
+use Tie::IxHash;
+use FS::UID qw( getotaker dbh );
+use FS::Misc qw( send_email );
+use FS::Record qw( qsearch qsearchs );
+use FS::m2m_Common;
+use FS::cust_main_Mixin;
+use FS::cust_svc;
+use FS::part_pkg;
+use FS::cust_main;
+use FS::type_pkgs;
+use FS::pkg_svc;
+use FS::cust_bill_pkg;
+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::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;
+
+@ISA = qw( FS::m2m_Common FS::cust_main_Mixin FS::option_Common FS::Record );
+
+$DEBUG = 0;
+
+$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 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 otaker - order taker (assigned automatically if null, see L<FS::UID>)
+
+=item manual_flag - If this field is set to 1, disables the automatic
+unsuspension of this package when using the B<unsuspendauto> config file.
+
+=back
+
+Note: setup, bill, adjourn, susp, expire and cancel are specified as UNIX timestamps;
+see L<perlfunc/"time">.  Also see L<Time::Local> and L<Date::Parse> for
+conversion functions.
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new billing item.  To add the item to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_pkg'; }
+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: I<change>
+
+I<change>, if set true, supresses any referral credit to a referring customer.
+
+=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;
+  }
+
+  $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->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;
+  my $cust_main = $self->cust_main;
+  my $part_pkg = $self->part_pkg;
+  if ( $conf->exists('referral_credit')
+       && $cust_main->referral_custnum
+       && ! $options{'change'}
+       && $part_pkg->freq !~ /^0\D?$/
+     )
+  {
+    my $referring_cust_main = $cust_main->referring_cust_main;
+    if ( $referring_cust_main->status ne 'cancelled' ) {
+      my $error;
+      if ( $part_pkg->freq !~ /^\d+$/ ) {
+        warn 'WARNING: Not crediting customer '. $cust_main->referral_custnum.
+             ' for package '. $self->pkgnum.
+             ' ( customer '. $self->custnum. ')'.
+             ' - One-time referral credits not (yet) available for '.
+             ' packages with '. $part_pkg->freq_pretty. ' frequency';
+      } else {
+
+        my $amount = sprintf( "%.2f", $part_pkg->base_recur / $part_pkg->freq );
+        my $error =
+          $referring_cust_main->
+            credit( $amount,
+                    'Referral credit for '.$cust_main->name,
+                    'reason_type' => $conf->config('referral_credit_type')
+                  );
+        if ( $error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return "Error crediting customer ". $cust_main->referral_custnum.
+               " for referral: $error";
+        }
+
+      }
+
+    }
+  }
+
+  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
+
+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).
+
+Calls 
+
+=cut
+
+sub replace {
+  my( $new, $old, %options ) = @_;
+
+  # We absolutely have to have an old vs. new record to make this work.
+  if (!defined($old)) {
+    $old = qsearchs( 'cust_pkg', { 'pkgnum' => $new->pkgnum } );
+  }
+  #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,
+                                     );
+      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;
+
+  my $error = 
+    $self->ut_numbern('pkgnum')
+    || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
+    || $self->ut_numbern('pkgpart')
+    || $self->ut_numbern('setup')
+    || $self->ut_numbern('bill')
+    || $self->ut_numbern('susp')
+    || $self->ut_numbern('cancel')
+    || $self->ut_numbern('adjourn')
+    || $self->ut_numbern('expire')
+  ;
+  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 } );
+      my $pkgpart_href = $agent->pkgpart_hashref;
+      return "agent ". $agent->agentnum.
+             " can't purchase pkgpart ". $self->pkgpart
+        unless $pkgpart_href->{ $self->pkgpart };
+    }
+
+    $error = $self->ut_foreign_key('pkgpart', 'part_pkg', 'pkgpart' );
+    return $error if $error;
+
+  }
+
+  $self->otaker(getotaker) unless $self->otaker;
+  $self->otaker =~ /^(\w{1,32})$/ or return "Illegal otaker";
+  $self->otaker($1);
+
+  if ( $self->dbdef_table->column('manual_flag') ) {
+    $self->manual_flag('') if $self->manual_flag eq ' ';
+    $self->manual_flag =~ /^([01]?)$/
+      or return "Illegal manual_flag ". $self->manual_flag;
+    $self->manual_flag($1);
+  }
+
+  $self->SUPER::check;
+}
+
+=item cancel [ OPTION => VALUE ... ]
+
+Cancels and removes all services (see L<FS::cust_svc> and L<FS::part_svc>)
+in this package, then cancels the package itself (sets the cancel field to
+now).
+
+Available options are:
+
+=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.
+
+=back
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub cancel {
+  my( $self, %options ) = @_;
+
+  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 $cancel_time = $options{'time'} || time;
+
+  my $error;
+
+  if ( $options{'reason'} ) {
+    $error = $self->insert_reason( 'reason' => $options{'reason'} );
+    if ( $error ) {
+      dbh->rollback if $oldAutoCommit;
+      return "Error inserting cust_pkg_reason: $error";
+    }
+  }
+
+  my %svc;
+  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";
+    }
+  }
+
+  unless ( $self->getfield('cancel') ) {
+    # Add a credit for remaining service
+    my $remaining_value = $self->calc_remain(time=>$cancel_time);
+    if ( $remaining_value > 0 && !$options{'no_credit'} ) {
+      my $conf = new FS::Conf;
+      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;
+    $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;
+
+  my $conf = new FS::Conf;
+  my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $self->cust_main->invoicing_list;
+  if ( !$options{'quiet'} && $conf->exists('emailcancel') && @invoicing_list ) {
+    my $conf = new FS::Conf;
+    my $error = send_email(
+      'from'    => $conf->config('invoice_from'),
+      'to'      => \@invoicing_list,
+      'subject' => ( $conf->config('cancelsubject') || '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 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.
+
+=back
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub suspend {
+  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;
+
+  if ( $options{'reason'} ) {
+    $error = $self->insert_reason( 'reason' => $options{'reason'} );
+    if ( $error ) {
+      dbh->rollback if $oldAutoCommit;
+      return "Error inserting cust_pkg_reason: $error";
+    }
+  }
+
+  foreach my $cust_svc (
+    qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } )
+  ) {
+    my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $cust_svc->svcpart } );
+
+    $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
+      $dbh->rollback if $oldAutoCommit;
+      return "Illegal svcdb value in part_svc!";
+    };
+    my $svcdb = $1;
+    require "FS/$svcdb.pm";
+
+    my $svc = qsearchs( $svcdb, { 'svcnum' => $cust_svc->svcnum } );
+    if ($svc) {
+      $error = $svc->suspend;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    }
+
+  }
+
+  unless ( $self->getfield('susp') ) {
+    my %hash = $self->hash;
+    $hash{'susp'} = time;
+    my $new = new FS::cust_pkg ( \%hash );
+    $error = $new->replace( $self, 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: I<adjust_next_bill>.
+
+I<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?
+
+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;
+
+  foreach my $cust_svc (
+    qsearch('cust_svc',{'pkgnum'=> $self->pkgnum } )
+  ) {
+    my $part_svc = qsearchs( 'part_svc', { 'svcpart' => $cust_svc->svcpart } );
+
+    $part_svc->svcdb =~ /^([\w\-]+)$/ or do {
+      $dbh->rollback if $oldAutoCommit;
+      return "Illegal svcdb value in part_svc!";
+    };
+    my $svcdb = $1;
+    require "FS/$svcdb.pm";
+
+    my $svc = qsearchs( $svcdb, { 'svcnum' => $cust_svc->svcnum } );
+    if ($svc) {
+      $error = $svc->unsuspend;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    }
+
+  }
+
+  unless ( ! $self->getfield('susp') ) {
+    my %hash = $self->hash;
+    my $inactive = time - $hash{'susp'};
+
+    my $conf = new FS::Conf;
+
+    $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive
+      if ( $opt{'adjust_next_bill'}
+           || $conf->config('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 last_bill
+
+Returns the last bill date, or if there is no last bill date, the setup date.
+Useful for billing metered services.
+
+=cut
+
+sub last_bill {
+  my $self = shift;
+  if ( $self->dbdef_table->column('last_bill') ) {
+    return $self->setfield('last_bill', $_[0]) if @_;
+    return $self->getfield('last_bill') if $self->getfield('last_bill');
+  }    
+  my $cust_bill_pkg = qsearchs('cust_bill_pkg', { 'pkgnum' => $self->pkgnum,
+                                                  'edate'  => $self->bill,  } );
+  $cust_bill_pkg ? $cust_bill_pkg->sdate : $self->setup || 0;
+}
+
+=item last_reason
+
+Returns the most recent FS::reason associated with the package.
+
+=cut
+
+sub last_reason {
+  my $self = shift;
+  my $cust_pkg_reason = qsearchs( {
+                                    'table' => 'cust_pkg_reason',
+                                   'hashref' => { 'pkgnum' => $self->pkgnum, },
+                                   'extra_sql'=> 'ORDER BY date DESC LIMIT 1',
+                                 } );
+  qsearchs ( 'reason', { 'reasonnum' => $cust_pkg_reason->reasonnum } )
+    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;
+  #exists( $self->{'_pkgpart'} )
+  $self->{'_pkgpart'}
+    ? $self->{'_pkgpart'}
+    : 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_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;
+
+  if ( @_ ) {
+    return qsearch( 'cust_svc', { 'pkgnum'  => $self->pkgnum,
+                                  'svcpart' => shift,          } );
+  }
+
+  #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;
+  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 ) = @_;
+
+  map  { $_->[0] }
+  sort { $b->[1] cmp $a->[1]  or  $a->[2] <=> $b->[2] } 
+  map {
+        my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $self->pkgpart,
+                                             'svcpart' => $_->svcpart     } );
+        [ $_,
+          $pkg_svc ? $pkg_svc->primary_svc : '',
+          $pkg_svc ? $pkg_svc->quantity : 0,
+        ];
+      }
+  @$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;
+  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'} = [ $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'} = [ $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 = $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 = $pkgnum
+                             )",
+  } );
+}
+
+=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 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 h_labels_short END_TIMESTAMP [ START_TIMESTAMP ]
+
+Like h_labels, except returns a simple flat list, and shortens long 
+(currently >5) 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 {
+  my $self = shift;
+
+  my %labels;
+  #tie %labels, 'Tie::IxHash';
+  push @{ $labels{$_->[0]} }, $_->[1]
+    foreach $self->h_labels(@_);
+  my @labels;
+  foreach my $label ( keys %labels ) {
+    my @values = @{ $labels{$label} };
+    my $num = scalar(@values);
+    if ( $num > 5 ) {
+      push @labels, "$label ($num)";
+    } 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 } );
+}
+
+=item seconds_since TIMESTAMP
+
+Returns the number of seconds all accounts (see L<FS::svc_acct>) in this
+package have been online since TIMESTAMP, according to the session monitor.
+
+TIMESTAMP is specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
+L<Time::Local> and L<Date::Parse> for conversion functions.
+
+=cut
+
+sub seconds_since {
+  my($self, $since) = @_;
+  my $seconds = 0;
+
+  foreach my $cust_svc (
+    grep { $_->part_svc->svcdb eq 'svc_acct' } $self->cust_svc
+  ) {
+    $seconds += $cust_svc->seconds_since($since);
+  }
+
+  $seconds;
+
+}
+
+=item seconds_since_sqlradacct TIMESTAMP_START TIMESTAMP_END
+
+Returns the numbers of seconds all accounts (see L<FS::svc_acct>) in this
+package have been online between TIMESTAMP_START (inclusive) and TIMESTAMP_END
+(exclusive).
+
+TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
+L<perlfunc/"time">.  Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+
+=cut
+
+sub seconds_since_sqlradacct {
+  my($self, $start, $end) = @_;
+
+  my $seconds = 0;
+
+  foreach my $cust_svc (
+    grep {
+      my $part_svc = $_->part_svc;
+      $part_svc->svcdb eq 'svc_acct'
+        && scalar($part_svc->part_export('sqlradius'));
+    } $self->cust_svc
+  ) {
+    $seconds += $cust_svc->seconds_since_sqlradacct($start, $end);
+  }
+
+  $seconds;
+
+}
+
+=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
+
+Returns the sum of the given attribute for all accounts (see L<FS::svc_acct>)
+in this package for sessions ending between TIMESTAMP_START (inclusive) and
+TIMESTAMP_END
+(exclusive).
+
+TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
+L<perlfunc/"time">.  Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+=cut
+
+sub attribute_since_sqlradacct {
+  my($self, $start, $end, $attrib) = @_;
+
+  my $sum = 0;
+
+  foreach my $cust_svc (
+    grep {
+      my $part_svc = $_->part_svc;
+      $part_svc->svcdb eq 'svc_acct'
+        && scalar($part_svc->part_export('sqlradius'));
+    } $self->cust_svc
+  ) {
+    $sum += $cust_svc->attribute_since_sqlradacct($start, $end, $attrib);
+  }
+
+  $sum;
+
+}
+
+=item transfer DEST_PKGNUM | DEST_CUST_PKG, [ OPTION => VALUE ... ]
+
+Transfers as many services as possible from this package to another package.
+
+The destination package can be specified by pkgnum by passing an FS::cust_pkg
+object.  The destination package must already exist.
+
+Services are moved only if the destination allows services with the correct
+I<svcpart> (not svcdb), unless the B<change_svcpart> option is set true.  Use
+this option with caution!  No provision is made for export differences
+between the old and new service definitions.  Probably only should be used
+when your exports for all service definitions of a given svcdb are identical.
+(attempt a transfer without it first, to move all possible svcpart-matching
+services)
+
+Any services that can't be moved remain in the original package.
+
+Returns an error, if there is one; otherwise, returns the number of services 
+that couldn't be moved.
+
+=cut
+
+sub transfer {
+  my ($self, $dest_pkgnum, %opt) = @_;
+
+  my $remaining = 0;
+  my $dest;
+  my %target;
+
+  if (ref ($dest_pkgnum) eq 'FS::cust_pkg') {
+    $dest = $dest_pkgnum;
+    $dest_pkgnum = $dest->pkgnum;
+  } else {
+    $dest = qsearchs('cust_pkg', { pkgnum => $dest_pkgnum });
+  }
+
+  return ('Package does not exist: '.$dest_pkgnum) unless $dest;
+
+  foreach my $pkg_svc ( $dest->part_pkg->pkg_svc ) {
+    $target{$pkg_svc->svcpart} = $pkg_svc->quantity;
+  }
+
+  foreach my $cust_svc ($dest->cust_svc) {
+    $target{$cust_svc->svcpart}--;
+  }
+
+  my %svcpart2svcparts = ();
+  if ( exists $opt{'change_svcpart'} && $opt{'change_svcpart'} ) {
+    warn "change_svcpart option received, creating alternates list\n" if $DEBUG;
+    foreach my $svcpart ( map { $_->svcpart } $self->cust_svc ) {
+      next if exists $svcpart2svcparts{$svcpart};
+      my $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+      $svcpart2svcparts{$svcpart} = [
+        map  { $_->[0] }
+        sort { $b->[1] cmp $a->[1]  or  $a->[2] <=> $b->[2] } 
+        map {
+              my $pkg_svc = qsearchs( 'pkg_svc', { 'pkgpart' => $dest->pkgpart,
+                                                   'svcpart' => $_          } );
+              [ $_,
+                $pkg_svc ? $pkg_svc->primary_svc : '',
+                $pkg_svc ? $pkg_svc->quantity : 0,
+              ];
+            }
+
+        grep { $_ != $svcpart }
+        map  { $_->svcpart }
+        qsearch('part_svc', { 'svcdb' => $part_svc->svcdb } )
+      ];
+      warn "alternates for svcpart $svcpart: ".
+           join(', ', @{$svcpart2svcparts{$svcpart}}). "\n"
+        if $DEBUG;
+    }
+  }
+
+  foreach my $cust_svc ($self->cust_svc) {
+    if($target{$cust_svc->svcpart} > 0) {
+      $target{$cust_svc->svcpart}--;
+      my $new = new FS::cust_svc { $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;
+  '';
+
+}
+
+=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.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.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_sql HREF
+
+Returns a qsearch hash expression to search for parameters specified in HREF.
+Valid parameters are
+
+=over 4
+=item agentnum
+=item magic - /^(active|inactive|suspended|cancell?ed)$/
+=item status - /^(active|inactive|suspended|one-time charge|inactive|cancell?ed)$/
+=item classnum
+=item pkgpart - list specified how?
+=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/APKG_pkgnum)$/
+=item cust_fields - a value suited to passing to FS::UI::Web::cust_header
+=item CurrentUser - specifies the user for agent virtualization
+=back
+
+=cut
+
+sub search_sql { 
+  my ($class, $params) = @_;
+  my @where = ();
+
+  ##
+  # parse agent
+  ##
+
+  if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) {
+    push @where,
+      "cust_main.agentnum = $1";
+  }
+
+  ##
+  # parse status
+  ##
+
+  if (    $params->{'magic'}  eq 'active'
+       || $params->{'status'} eq 'active' ) {
+
+    push @where, FS::cust_pkg->active_sql();
+
+  } elsif (    $params->{'magic'}  eq 'inactive'
+            || $params->{'status'} eq '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();
+
+  } elsif ( $params->{'status'} =~ /^(one-time charge|inactive)$/ ) {
+
+    push @where, FS::cust_pkg->inactive_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, "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, "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 part_pkg
+  ###
+
+  my $pkgpart = join (' OR pkgpart=',
+                      grep {$_} map { /^(\d+)$/; } ($params->{'pkgpart'}));
+  push @where,  '(pkgpart=' . $pkgpart . ')' if $pkgpart;
+
+  ###
+  # 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'       => {},
+    ''                => {},
+  );
+
+  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 USING ( 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,
+  };
+
+}
+
+=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;
+
+  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 ) {
+
+    my $time = time;
+
+    #$hash{$_} = $old_cust_pkg[0]->$_() foreach qw( last_bill bill );
+    
+    #$hash{$_} = $old_cust_pkg[0]->$_() foreach qw( setup );
+    $hash{'setup'} = $time if $old_cust_pkg[0]->setup;
+
+    $hash{'change_date'} = $time;
+    $hash{"change_$_"}  = $old_cust_pkg[0]->$_() foreach qw( pkgnum pkgpart );
+  }
+
+  # Create the new packages.
+  foreach my $pkgpart (@$pkgparts) {
+    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) {
+
+    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 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 date
+
+=back
+
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+=item bulk_change PKGPARTS_ARYREF, REMOVE_PKGNUMS_ARYREF [ RETURN_CUST_PKG_ARRAYREF ]
+
+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 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;
+  '';
+}
+
+sub insert_reason {
+  my ($self, %options) = @_;
+
+  my $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,
+                             'date'      => $options{'date'}
+                                              ? $options{'date'}
+                                              : time,
+                           });
+
+  $cust_pkg_reason->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) = @_;
+
+  foreach my $cust_svc ($self->cust_svc){
+    my $svc_x = $cust_svc->svc_x;
+    $svc_x->set_usage($valueref)
+      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");
+  }
+}
+
+=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_option.pm b/FS/FS/cust_pkg_option.pm
new file mode 100644 (file)
index 0000000..43a1530
--- /dev/null
@@ -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 (file)
index 0000000..2f92740
--- /dev/null
@@ -0,0 +1,122 @@
+package FS::cust_pkg_reason;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=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 otaker - 
+
+=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_text('otaker')
+    || $self->ut_numbern('date')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=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_refund.pm b/FS/FS/cust_refund.pm
new file mode 100644 (file)
index 0000000..53c6bac
--- /dev/null
@@ -0,0 +1,352 @@
+package FS::cust_refund;
+
+use strict;
+use vars qw( @ISA @encrypted_fields );
+use Business::CreditCard;
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::UID qw(getotaker);
+use FS::cust_credit;
+use FS::cust_credit_refund;
+use FS::cust_pay_refund;
+use FS::cust_main;
+use FS::payinfo_Mixin;
+
+@ISA = qw( FS::Record FS::payinfo_Mixin );
+
+@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 otaker - order taker (assigned automatically, see L<FS::UID>)
+
+=item closed - books closed flag, empty or `Y'
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new refund.  To add the refund to the database, see L<"insert">.
+
+=cut
+
+sub table { 'cust_refund'; }
+
+=item insert
+
+Adds this refund to the database.
+
+For backwards-compatibility and convenience, if the additional field crednum is
+defined, an FS::cust_credit_refund record for the full amount of the refund
+will be created.  Or (this time for convenience and consistancy), if the
+additional field paynum is defined, an FS::cust_pay_refund record for the full
+amount of the refund will be created.  In both cases, custnum is optional.
+
+=cut
+
+sub insert {
+  my $self = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  if ( $self->crednum ) {
+    my $cust_credit = qsearchs('cust_credit', { 'crednum' => $self->crednum } )
+      or do {
+        $dbh->rollback if $oldAutoCommit;
+        return "Unknown cust_credit.crednum: ". $self->crednum;
+      };
+    $self->custnum($cust_credit->custnum);
+  } elsif ( $self->paynum ) {
+    my $cust_pay = qsearchs('cust_pay', { 'paynum' => $self->paynum } )
+      or do {
+        $dbh->rollback if $oldAutoCommit;
+        return "Unknown cust_pay.paynum: ". $self->paynum;
+      };
+    $self->custnum($cust_pay->custnum);
+  }
+
+  my $error = $self->check;
+  return $error if $error;
+
+  $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  if ( $self->crednum ) {
+    my $cust_credit_refund = new FS::cust_credit_refund {
+      'crednum'   => $self->crednum,
+      'refundnum' => $self->refundnum,
+      'amount'    => $self->refund,
+      '_date'     => $self->_date,
+    };
+    $error = $cust_credit_refund->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+    #$self->custnum($cust_credit_refund->cust_credit->custnum);
+  } elsif ( $self->paynum ) {
+    my $cust_pay_refund = new FS::cust_pay_refund {
+      'paynum'    => $self->paynum,
+      'refundnum' => $self->refundnum,
+      'amount'    => $self->refund,
+      '_date'     => $self->_date,
+    };
+    $error = $cust_pay_refund->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
+=item delete
+
+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
+
+Currently unimplemented (accounting reasons).
+
+=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;
+
+  my $error =
+    $self->ut_numbern('refundnum')
+    || $self->ut_numbern('custnum')
+    || $self->ut_money('refund')
+    || $self->ut_text('reason')
+    || $self->ut_numbern('_date')
+    || $self->ut_textn('paybatch')
+    || $self->ut_enum('closed', [ '', 'Y' ])
+  ;
+  return $error if $error;
+
+  return "refund must be > 0 " if $self->refund <= 0;
+
+  $self->_date(time) unless $self->_date;
+
+  return "unknown cust_main.custnum: ". $self->custnum
+    unless $self->crednum 
+           || qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
+
+  $error = $self->payinfo_check;
+  return $error if $error;
+
+  $self->otaker(getotaker);
+
+  $self->SUPER::check;
+}
+
+=item cust_credit_refund
+
+Returns all applications to credits (see L<FS::cust_credit_refund>) for this
+refund.
+
+=cut
+
+sub cust_credit_refund {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_credit_refund', { 'refundnum' => $self->refundnum } )
+  ;
+}
+
+=item cust_pay_refund
+
+Returns all applications to payments (see L<FS::cust_pay_refund>) for this
+refund.
+
+=cut
+
+sub cust_pay_refund {
+  my $self = shift;
+  sort { $a->_date <=> $b->_date }
+    qsearch( 'cust_pay_refund', { 'refundnum' => $self->refundnum } )
+  ;
+}
+
+=item unapplied
+
+Returns the amount of this refund that is still unapplied; which is
+amount minus all credit applications (see L<FS::cust_credit_refund>) and
+payment applications (see L<FS::cust_pay_refund>).
+
+=cut
+
+sub unapplied {
+  my $self = shift;
+  my $amount = $self->refund;
+  $amount -= $_->amount foreach ( $self->cust_credit_refund );
+  $amount -= $_->amount foreach ( $self->cust_pay_refund );
+  sprintf("%.2f", $amount );
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item unapplied_sql
+
+Returns an SQL fragment to retreive the unapplied amount.
+
+=cut 
+
+sub unapplied_sql {
+  #my $class = shift;
+
+  "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
+              )
+  ";
+
+}
+
+=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_svc.pm b/FS/FS/cust_svc.pm
new file mode 100644 (file)
index 0000000..8eecda3
--- /dev/null
@@ -0,0 +1,705 @@
+package FS::cust_svc;
+
+use strict;
+use vars qw( @ISA $DEBUG $me $ignore_quantity );
+use Carp;
+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::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, $old ) = ( shift, shift );
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  $old = $new->replace_old unless defined($old);
+  
+  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;
+    }
+  }
+
+  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;
+
+=cut
+
+sub label {
+  my $self = shift;
+  carp "FS::cust_svc::label called on $self" if $DEBUG;
+  my $svc_x = $self->svc_x
+    or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum;
+
+  $self->_svc_label($svc_x);
+}
+
+sub _svc_label {
+  my( $self, $svc_x ) = ( shift, shift );
+
+  (
+    $self->part_svc->svc,
+    $svc_x->label(@_),
+    $self->part_svc->svcdb,
+    $self->svcnum
+  );
+
+}
+
+=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 $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');
+
+    my $dbh = DBI->connect( map { $part_export->option($_) }
+                            qw(datasrc username password)    )
+      or die "can't connect to sqlradius database: ". $DBI::errstr;
+
+    #select a unix time conversion function based on database type
+    my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+    
+    my $username = $part_export->export_username($svc_x);
+
+    my $query;
+  
+    #find closed sessions completely within the given range
+    my $sth = $dbh->prepare("SELECT SUM(acctsessiontime)
+                               FROM radacct
+                               WHERE UserName = ?
+                                 AND $str2time AcctStartTime) >= ?
+                                 AND $str2time AcctStopTime ) <  ?
+                                 AND $str2time AcctStopTime ) > 0
+                                 AND AcctStopTime IS NOT NULL"
+    ) or die $dbh->errstr;
+    $sth->execute($username, $start, $end) or die $sth->errstr;
+    my $regular = $sth->fetchrow_arrayref->[0];
+  
+    #find open sessions which start in the range, count session start->range end
+    $query = "SELECT SUM( ? - $str2time AcctStartTime ) )
+                FROM radacct
+                WHERE UserName = ?
+                  AND $str2time AcctStartTime ) >= ?
+                  AND $str2time AcctStartTime ) <  ?
+                  AND ( ? - $str2time AcctStartTime ) ) < 86400
+                  AND (    $str2time AcctStopTime ) = 0
+                                    OR AcctStopTime IS NULL )";
+    $sth = $dbh->prepare($query) or die $dbh->errstr;
+    $sth->execute($end, $username, $start, $end, $end)
+      or die $sth->errstr. " executing query $query";
+    my $start_during = $sth->fetchrow_arrayref->[0];
+  
+    #find closed sessions which start before the range but stop during,
+    #count range start->session end
+    $sth = $dbh->prepare("SELECT SUM( $str2time AcctStopTime ) - ? ) 
+                            FROM radacct
+                            WHERE UserName = ?
+                              AND $str2time AcctStartTime ) < ?
+                              AND $str2time AcctStopTime  ) >= ?
+                              AND $str2time AcctStopTime  ) <  ?
+                              AND $str2time AcctStopTime ) > 0
+                              AND AcctStopTime IS NOT NULL"
+    ) or die $dbh->errstr;
+    $sth->execute($start, $username, $start, $start, $end ) or die $sth->errstr;
+    my $end_during = $sth->fetchrow_arrayref->[0];
+  
+    #find closed (not anymore - or open) sessions which start before the range
+    # but stop after, or are still open, count range start->range end
+    # don't count open sessions (probably missing stop record)
+    $sth = $dbh->prepare("SELECT COUNT(*)
+                            FROM radacct
+                            WHERE UserName = ?
+                              AND $str2time AcctStartTime ) < ?
+                              AND ( $str2time AcctStopTime ) >= ?
+                                                                  )"
+                              #      OR AcctStopTime =  0
+                              #      OR AcctStopTime IS NULL       )"
+    ) or die $dbh->errstr;
+    $sth->execute($username, $start, $end ) or die $sth->errstr;
+    my $entire_range = ($end-$start) * $sth->fetchrow_arrayref->[0];
+
+    $seconds += $regular + $end_during + $start_during + $entire_range;
+
+  }
+
+  $seconds;
+
+}
+
+=item attribute_since_sqlradacct TIMESTAMP_START TIMESTAMP_END ATTRIBUTE
+
+See L<FS::svc_acct/attribute_since_sqlradacct>.  Equivalent to
+$cust_svc->svc_x->attribute_since_sqlradacct, but more efficient.  Meaningless
+for records where B<svcdb> is not "svc_acct".
+
+=cut
+
+#note: implementation here, POD in FS::svc_acct
+#(false laziness w/seconds_since_sqlradacct above)
+sub attribute_since_sqlradacct {
+  my($self, $start, $end, $attrib) = @_;
+
+  my $svc_x = $self->svc_x;
+
+  my @part_export = $self->part_svc->part_export_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');
+
+    my $dbh = DBI->connect( map { $part_export->option($_) }
+                            qw(datasrc username password)    )
+      or die "can't connect to sqlradius database: ". $DBI::errstr;
+
+    #select a unix time conversion function based on database type
+    my $str2time = str2time_sql( $dbh->{Driver}->{Name} );
+
+    my $username = $part_export->export_username($svc_x);
+
+    my $sth = $dbh->prepare("SELECT SUM($attrib)
+                               FROM radacct
+                               WHERE UserName = ?
+                                 AND $str2time AcctStopTime ) >= ?
+                                 AND $str2time AcctStopTime ) <  ?
+                                 AND AcctStopTime IS NOT NULL"
+    ) or die $dbh->errstr;
+    $sth->execute($username, $start, $end) or die $sth->errstr;
+
+    $sum += $sth->fetchrow_arrayref->[0];
+
+  }
+
+  $sum;
+
+}
+
+=item get_session_history 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;
+
+}
+
+=item get_cdrs_for_update
+
+Returns (and SELECTs "FOR UPDATE") all unprocessed (freesidestatus NULL) CDR
+objects (see L<FS::cdr>) associated with this service.
+
+CDRs are associated with svc_phone services via svc_phone.phonenum
+
+=cut
+
+sub get_cdrs_for_update {
+  my($self, %options) = @_;
+
+  my $default_prefix = $options{'default_prefix'};
+
+  #CDRs are now associated with svc_phone services via svc_phone.phonenum
+  #return () unless $self->svc_x->isa('FS::svc_phone');
+  return () unless $self->part_svc->svcdb eq 'svc_phone';
+  my $number = $self->svc_x->phonenum;
+
+  my @cdrs = 
+    qsearch( {
+      'table'      => 'cdr',
+      'hashref'    => { 'freesidestatus' => '',
+                        'charged_party'  => $number
+                      },
+      'extra_sql'  => 'FOR UPDATE',
+    } );
+
+  if ( length($default_prefix) ) {
+    push @cdrs,
+      qsearch( {
+        'table'      => 'cdr',
+        'hashref'    => { 'freesidestatus' => '',
+                          'charged_party'  => "$default_prefix$number",
+                        },
+        'extra_sql'  => 'FOR UPDATE',
+      } );
+  }
+
+  #astricon hack?  config option?
+  push @cdrs,
+    qsearch( {
+      'table'        => 'cdr',
+      'hashref'      => { 'freesidestatus' => '',
+                          'src'            => $number,
+                       },
+      'extra_sql'    => 'FOR UPDATE',
+     } );
+
+  if ( length($default_prefix) ) {
+    push @cdrs,
+      qsearch( {
+        'table'        => 'cdr',
+        'hashref'      => { 'freesidestatus' => '',
+                            'src'            => "$default_prefix$number",
+                       },
+        'extra_sql'    => 'FOR UPDATE',
+       } );
+  }
+
+  @cdrs;
+}
+
+=back
+
+=head1 BUGS
+
+Behaviour of changing the svcpart of cust_svc records is undefined and should
+possibly be prohibited, and pkg_svc records are not checked.
+
+pkg_svc records are not checked in general (here).
+
+Deleting this record doesn't check or delete the svc_* record associated
+with this record.
+
+In seconds_since_sqlradacct, specifying a DATASRC/USERNAME/PASSWORD instead of
+a DBI database handle is not yet implemented.
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::cust_pkg>, L<FS::part_svc>, L<FS::pkg_svc>, 
+schema.html from the base documentation
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_tax_exempt.pm b/FS/FS/cust_tax_exempt.pm
new file mode 100644 (file)
index 0000000..3e39887
--- /dev/null
@@ -0,0 +1,151 @@
+package FS::cust_tax_exempt;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main;
+use FS::cust_main_county;
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_tax_exempt - Object methods for cust_tax_exempt records
+
+=head1 SYNOPSIS
+
+  use FS::cust_tax_exempt;
+
+  $record = new FS::cust_tax_exempt \%hash;
+  $record = new FS::cust_tax_exempt { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_tax_exempt object represents a 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 (file)
index 0000000..128921b
--- /dev/null
@@ -0,0 +1,136 @@
+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;
+
+@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_number('year') #check better
+    || $self->ut_number('month') #check better
+    || $self->ut_money('amount')
+    || $self->SUPER::check
+  ;
+}
+
+=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/domain_record.pm b/FS/FS/domain_record.pm
new file mode 100644 (file)
index 0000000..6513abf
--- /dev/null
@@ -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_svc.pm b/FS/FS/export_svc.pm
new file mode 100644 (file)
index 0000000..0370f5f
--- /dev/null
@@ -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 (file)
index 0000000..ca13e1b
--- /dev/null
@@ -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 (file)
index 0000000..7a3d811
--- /dev/null
@@ -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 (file)
index 0000000..1425a26
--- /dev/null
@@ -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 (file)
index 0000000..6434b3f
--- /dev/null
@@ -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_svc.pm b/FS/FS/h_cust_svc.pm
new file mode 100644 (file)
index 0000000..921be3a
--- /dev/null
@@ -0,0 +1,161 @@
+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 {
+  my $self = shift;
+  carp "FS::h_cust_svc::label called on $self" 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->_svc_label($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 (file)
index 0000000..9d2318b
--- /dev/null
@@ -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 (file)
index 0000000..0ab974f
--- /dev/null
@@ -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 (file)
index 0000000..247d20c
--- /dev/null
@@ -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 (file)
index 0000000..d6038fb
--- /dev/null
@@ -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 (file)
index 0000000..60d54f7
--- /dev/null
@@ -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 (file)
index 0000000..5eb7064
--- /dev/null
@@ -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 (file)
index 0000000..25b2039
--- /dev/null
@@ -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_phone.pm b/FS/FS/h_svc_phone.pm
new file mode 100644 (file)
index 0000000..95898c7
--- /dev/null
@@ -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 (file)
index 0000000..2a3b6dc
--- /dev/null
@@ -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 (file)
index 0000000..508889b
--- /dev/null
@@ -0,0 +1,164 @@
+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 {
+  shift->num_sql('( svcnum IS NULL OR svcnum = 0 )');
+}
+
+sub num_sql {
+  my( $self, $sql ) = @_;
+  $sql = "AND $sql" if length($sql);
+  my $statement =
+    "SELECT COUNT(*) FROM inventory_item WHERE classnum = ? $sql";
+  my $sth = dbh->prepare($statement) or die dbh->errstr. " preparing $statement";
+  $sth->execute($self->classnum) or die $sth->errstr. " executing $statement";
+  $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 {
+  shift->num_sql("svcnum IS NOT NULL AND svcnum > 0 ");
+}
+
+=item num_total
+
+Returns the total number of inventory items of this class (see
+L<FS::inventory_class>).
+
+=cut
+
+sub num_total {
+  shift->num_sql('');
+}
+
+=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 (file)
index 0000000..7fa350f
--- /dev/null
@@ -0,0 +1,204 @@
+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_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 } );
+}
+
+=back
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item batch_import
+
+=cut
+
+sub batch_import {
+  my $param = shift;
+
+  my $fh = $param->{filehandle};
+
+  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>) ) {
+
+    chomp $line;
+
+    my $inventory_item = new FS::inventory_item {
+      'classnum' => $param->{'classnum'},
+      'item'     => $line,
+    };
+
+    my $error = $inventory_item->insert;
+
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+
+      #or just skip?
+      #next;
+    }
+
+    $imported++;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  #might want to disable this if we skip records for any reason...
+  return "Empty file!" unless $imported;
+
+  '';
+
+}
+
+=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/m2m_Common.pm b/FS/FS/m2m_Common.pm
new file mode 100644 (file)
index 0000000..5dc2a8e
--- /dev/null
@@ -0,0 +1,144 @@
+package FS::m2m_Common;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+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;
+
+=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).
+
+Note: It is currently assumed that the link table contains two fields
+named the same as the primary keys of ths base and target tables.
+
+=head1 METHODS
+
+=over 4
+
+=item process_m2m OPTION => VALUE, ...
+
+Available options:
+
+link_table (required) - 
+
+target_table (required) - 
+
+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.
+
+=cut
+
+sub process_m2m {
+  my( $self, %opt ) = @_;
+
+  my $self_pkey = $self->dbdef_table->primary_key;
+  my %hash = ( $self_pkey => $self->$self_pkey() );
+
+  my $link_table = $self->_load_table($opt{'link_table'});
+
+  my $target_table = $self->_load_table($opt{'target_table'});
+  my $target_pkey = 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_pkey();
+           (    ! $opt{'params'}->{$targetnum}
+             && ! $opt{'params'}->{"$target_pkey$targetnum"}
+           );
+         }
+         qsearch( $link_table, \%hash )
+  ) {
+    my $error = $del_obj->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  foreach my $add_targetnum (
+    grep { ! qsearchs( $link_table, { %hash, $target_pkey => $_ } ) }
+    map  { /^($target_pkey)?(\d+)$/; $2; }
+    grep { /^($target_pkey)?(\d+)$/ }
+    grep { $opt{'params'}->{$_} }
+    keys %{ $opt{'params'} }
+  ) {
+
+    my $add_obj = "FS::$link_table"->new( {
+      %hash, 
+      $target_pkey => $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 (file)
index 0000000..e9dcee9
--- /dev/null
@@ -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/msgcat.pm b/FS/FS/msgcat.pm
new file mode 100644 (file)
index 0000000..cbdc1d6
--- /dev/null
@@ -0,0 +1,133 @@
+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
+}
+
+=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 (file)
index 0000000..97b0ea1
--- /dev/null
@@ -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/option_Common.pm b/FS/FS/option_Common.pm
new file mode 100644 (file)
index 0000000..441e798
--- /dev/null
@@ -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 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/part_bill_event.pm b/FS/FS/part_bill_event.pm
new file mode 100644 (file)
index 0000000..1d48af9
--- /dev/null
@@ -0,0 +1,363 @@
+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;
+
+@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 { $_->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_event.pm b/FS/FS/part_event.pm
new file mode 100644 (file)
index 0000000..d0ab65e
--- /dev/null
@@ -0,0 +1,428 @@
+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)
+
+=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', [ 'cust_bill', 'cust_main', 'cust_pkg' ] )
+    || $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',
+  ;
+
+  \%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 %hash = (
+    'cust_main'      => 'cust_main.custnum',
+    'cust_bill'      => 'cust_bill.invnum',
+    'cust_pkg'       => 'cust_pkg.pkgnum',
+    'cust_pay_batch' => 'cust_pay_batch.paybatchnum',
+  );
+
+  \%hash;
+}
+
+
+=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 (file)
index 0000000..bdb9df6
--- /dev/null
@@ -0,0 +1,224 @@
+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 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:
+
+=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;
+}
+
+=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/addpost.pm b/FS/FS/part_event/Action/addpost.pm
new file mode 100644 (file)
index 0000000..e0e3fa8
--- /dev/null
@@ -0,0 +1,24 @@
+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 (file)
index 0000000..f91c604
--- /dev/null
@@ -0,0 +1,28 @@
+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 (file)
index 0000000..fec025f
--- /dev/null
@@ -0,0 +1,30 @@
+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 (file)
index 0000000..94f3146
--- /dev/null
@@ -0,0 +1,35 @@
+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 (file)
index 0000000..fa94b7d
--- /dev/null
@@ -0,0 +1,30 @@
+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 (file)
index 0000000..aec0925
--- /dev/null
@@ -0,0 +1,31 @@
+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 (file)
index 0000000..636a66d
--- /dev/null
@@ -0,0 +1,34 @@
+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_fee_percent.pm b/FS/FS/part_event/Action/cust_bill_fee_percent.pm
new file mode 100644 (file)
index 0000000..100fc8b
--- /dev/null
@@ -0,0 +1,40 @@
+package FS::part_event::Action::cust_bill_fee_percent;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+  'Late fee (percentage of invoice)';
+}
+
+sub eventtable_hashref {
+  { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+  ( 
+    'percent' => { label=>'Percent', size=>2, },
+    'reason'  => 'Reason',
+  );
+}
+
+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 $error = $cust_main->charge(
+    sprintf('%.2f', $cust_bill->owed * $self->option('percent') / 100 ),
+    $self->option('reason')
+  );
+  die $error if $error;
+
+  '';
+}
+
+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 (file)
index 0000000..471c946
--- /dev/null
@@ -0,0 +1,32 @@
+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 (file)
index 0000000..9a52830
--- /dev/null
@@ -0,0 +1,32 @@
+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 (file)
index 0000000..db091da
--- /dev/null
@@ -0,0 +1,32 @@
+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 (file)
index 0000000..9330c61
--- /dev/null
@@ -0,0 +1,27 @@
+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 ) = @_;
+
+  #my $cust_main = $self->cust_main($cust_bill);
+  my $cust_main = $cust_bill->cust_main;
+
+  $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 (file)
index 0000000..fcf0007
--- /dev/null
@@ -0,0 +1,44 @@
+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 (file)
index 0000000..6afb89a
--- /dev/null
@@ -0,0 +1,35 @@
+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 (file)
index 0000000..db3554e
--- /dev/null
@@ -0,0 +1,56 @@
+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 (file)
index 0000000..916983e
--- /dev/null
@@ -0,0 +1,40 @@
+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_spool_csv.pm b/FS/FS/part_event/Action/cust_bill_spool_csv.pm
new file mode 100644 (file)
index 0000000..4300b61
--- /dev/null
@@ -0,0 +1,64 @@
+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',
+                           },
+  );
+}
+
+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 (file)
index 0000000..6559949
--- /dev/null
@@ -0,0 +1,48 @@
+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/fee.pm b/FS/FS/part_event/Action/fee.pm
new file mode 100644 (file)
index 0000000..81a8449
--- /dev/null
@@ -0,0 +1,33 @@
+package FS::part_event::Action::fee;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+  'Late fee (flat)';
+}
+
+sub option_fields {
+  ( 
+    'charge' => { label=>'Amount', type=>'money', }, # size=>7, },
+    'reason' => 'Reason',
+  );
+};
+
+sub default_weight {
+  10;
+}
+
+sub do_action {
+  my( $self, $cust_object ) = @_;
+
+  my $cust_main = $self->cust_main($cust_object);
+
+  my $error = $cust_main->charge( $self->option('charge'), $self->option('reason') );
+
+  die $error if $error;
+
+  '';
+}
+
+1;
diff --git a/FS/FS/part_event/Action/suspend.pm b/FS/FS/part_event/Action/suspend.pm
new file mode 100644 (file)
index 0000000..ec440ff
--- /dev/null
@@ -0,0 +1,36 @@
+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 (file)
index 0000000..9bdc9be
--- /dev/null
@@ -0,0 +1,42 @@
+package FS::part_event::Action::suspend_if_pkgpart;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+  'Suspend packages';
+}
+
+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 (file)
index 0000000..f9bf1e8
--- /dev/null
@@ -0,0 +1,42 @@
+package FS::part_event::Action::suspend_unless_pkgpart;
+
+use strict;
+use base qw( FS::part_event::Action );
+
+sub description {
+  'Suspend packages except';
+}
+
+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/Condition.pm b/FS/FS/part_event/Condition.pm
new file mode 100644 (file)
index 0000000..2b71fbb
--- /dev/null
@@ -0,0 +1,412 @@
+package FS::part_event::Condition;
+
+use strict;
+use base qw( FS::part_event_condition );
+
+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,
+    };
+  }
+
+=cut
+
+#fallback
+sub eventtable_hashref {
+    { 'cust_main'      => 1,
+      'cust_bill'      => 1,
+      'cust_pkg'       => 1,
+      'cust_pay_batch' => 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 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 (file)
index 0000000..da428c1
--- /dev/null
@@ -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 (file)
index 0000000..54c8932
--- /dev/null
@@ -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 (file)
index 0000000..2639413
--- /dev/null
@@ -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 > $over";
+
+}
+
+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 (file)
index 0000000..ec3624a
--- /dev/null
@@ -0,0 +1,77 @@
+package FS::part_event::Condition::balance_age;
+
+require 5.006;
+use strict;
+use Time::Local qw(timelocal_nocheck);
+
+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);
+
+  #false laziness w/cust_bill_age
+  my $time = $opt{'time'};
+  my $age = $self->option('age');
+  $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";
+  }
+  my $age_date = timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year);
+
+  $cust_main->balance_date($age_date) > $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 > $over";
+}
+
+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 (file)
index 0000000..5e19034
--- /dev/null
@@ -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 <= $under";
+
+}
+
+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 (file)
index 0000000..5c1e468
--- /dev/null
@@ -0,0 +1,75 @@
+package FS::part_event::Condition::cust_bill_age;
+
+require 5.006;
+use strict;
+use Time::Local qw(timelocal_nocheck);
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+  'Invoice age';
+}
+
+sub eventtable_hashref {
+    { 'cust_main' => 0,
+      'cust_bill' => 1,
+      'cust_pkg'  => 0,
+    };
+}
+
+#something like this
+sub option_fields {
+  (
+    #'days' => { label=>'Days', size=>3, },
+    'age' => { label=>'Age', type=>'freq', },
+  );
+}
+
+sub condition {
+  my( $self, $cust_bill, %opt ) = @_;
+
+  #false laziness w/balance_age
+  my $time = $opt{'time'};
+  my $age = $self->option('age');
+  $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";
+  }
+  my $age_date = timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year);
+
+  $cust_bill->_date <= $age_date;
+
+}
+
+#                            and seconds <= $time - cust_bill._date
+
+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_service.pm b/FS/FS/part_event/Condition/cust_bill_has_service.pm
new file mode 100644 (file)
index 0000000..be7ea2b
--- /dev/null
@@ -0,0 +1,54 @@
+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 == $_->svcnum } 
+  map { $_->cust_pkg->cust_svc }
+  $cust_bill->cust_bill_pkg ;
+}
+
+sub condition_sql {
+  my( $class, $table ) = @_;
+  
+  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 = $servicenum
+  )
+  |;
+  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 (file)
index 0000000..5e582ef
--- /dev/null
@@ -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 > $over";
+}
+
+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 (file)
index 0000000..460e6a4
--- /dev/null
@@ -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 <= $under";
+}
+
+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 (file)
index 0000000..b3a8d70
--- /dev/null
@@ -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_status.pm b/FS/FS/part_event/Condition/cust_status.pm
new file mode 100644 (file)
index 0000000..fbdff25
--- /dev/null
@@ -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/every.pm b/FS/FS/part_event/Condition/every.pm
new file mode 100644 (file)
index 0000000..3408b0a
--- /dev/null
@@ -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/once.pm b/FS/FS/part_event/Condition/once.pm
new file mode 100644 (file)
index 0000000..5a9161f
--- /dev/null
@@ -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 sucessfully"; }
+
+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/payby.pm b/FS/FS/part_event/Condition/payby.pm
new file mode 100644 (file)
index 0000000..d931568
--- /dev/null
@@ -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_class.pm b/FS/FS/part_event/Condition/pkg_class.pm
new file mode 100644 (file)
index 0000000..8c9031c
--- /dev/null
@@ -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_status.pm b/FS/FS/part_event/Condition/pkg_status.pm
new file mode 100644 (file)
index 0000000..6c1c9cc
--- /dev/null
@@ -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.pm b/FS/FS/part_event_condition.pm
new file mode 100644 (file)
index 0000000..d13e849
--- /dev/null
@@ -0,0 +1,352 @@
+package FS::part_event_condition;
+
+use strict;
+use vars qw( @ISA $DEBUG @SKIP_CONDITION_SQL );
+use FS::UID qw(dbh);
+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 );
+          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 (file)
index 0000000..3256dc0
--- /dev/null
@@ -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 (file)
index 0000000..7396c22
--- /dev/null
@@ -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 (file)
index 0000000..43e1da9
--- /dev/null
@@ -0,0 +1,213 @@
+package FS::part_event_option;
+
+use strict;
+use vars qw( @ISA );
+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 (file)
index 0000000..6adcab9
--- /dev/null
@@ -0,0 +1,461 @@
+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 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 );
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item export_info [ SVCDB ]
+
+Returns a hash reference of the exports for the given I<svcdb>, or if no
+I<svcdb> is specified, for all exports.  The keys of the hash are
+I<exporttype>s and the values are again hash references containing information
+on the export:
+
+  'desc'     => 'Description',
+  'options'  => {
+                  'option'  => { label=>'Option Label' },
+                  'option2' => { label=>'Another label' },
+                },
+  'nodomain' => 'Y', #or ''
+  'notes'    => 'Additional notes',
+
+=cut
+
+sub export_info {
+  #warn $_[0];
+  return $exports{$_[0]} || {} if @_;
+  #{ map { %{$exports{$_}} } keys %exports };
+  my $r = { map { %{$exports{$_}} } keys %exports };
+}
+
+#=item exporttype2svcdb EXPORTTYPE
+#
+#Returns the applicable I<svcdb> for an I<exporttype>.
+#
+#=cut
+#
+#sub exporttype2svcdb {
+#  my $exporttype = $_[0];
+#  foreach my $svcdb ( keys %exports ) {
+#    return $svcdb if grep { $exporttype eq $_ } keys %{$exports{$svcdb}};
+#  }
+#  '';
+#}
+
+foreach my $INC ( @INC ) {
+  foreach my $file ( glob("$INC/FS/part_export/*.pm") ) {
+    warn "attempting to load export info from $file\n" if $DEBUG;
+    $file =~ /\/(\w+)\.pm$/ or do {
+      warn "unrecognized file in $INC/FS/part_export/: $file\n";
+      next;
+    };
+    my $mod = $1;
+    my $info = eval "use FS::part_export::$mod; ".
+                    "\\%FS::part_export::$mod\::info;";
+    if ( $@ ) {
+      die "error using FS::part_export::$mod (skipping): $@\n" if $@;
+      next;
+    }
+    unless ( keys %$info ) {
+      warn "no %info hash found in FS::part_export::$mod, skipping\n"
+        unless $mod =~ /^(passwdfile|null)$/; #hack but what the heck
+      next;
+    }
+    warn "got export info from FS::part_export::$mod: $info\n" if $DEBUG;
+    no strict 'refs';
+    foreach my $svc (
+      ref($info->{'svc'}) ? @{$info->{'svc'}} : $info->{'svc'}
+    ) {
+      unless ( $svc ) {
+        warn "blank svc for FS::part_export::$mod (skipping)\n";
+        next;
+      }
+      $exports{$svc}->{$mod} = $info;
+    }
+  }
+}
+
+=back
+
+=head1 NEW EXPORT CLASSES
+
+A module should be added in FS/FS/part_export/ (an example may be found in
+eg/export_template.pm)
+
+=head1 BUGS
+
+Hmm... cust_export class (not necessarily a database table...) ... ?
+
+deprecated column...
+
+=head1 SEE ALSO
+
+L<FS::part_export_option>, L<FS::export_svc>, L<FS::svc_acct>,
+L<FS::svc_domain>,
+L<FS::svc_forward>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_export/acct_plesk.pm b/FS/FS/part_export/acct_plesk.pm
new file mode 100644 (file)
index 0000000..1be820a
--- /dev/null
@@ -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.
+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 (file)
index 0000000..9f1ae7b
--- /dev/null
@@ -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/apache.pm b/FS/FS/part_export/apache.pm
new file mode 100644 (file)
index 0000000..35b00cc
--- /dev/null
@@ -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 (file)
index 0000000..c006db9
--- /dev/null
@@ -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 (file)
index 0000000..1ef7b65
--- /dev/null
@@ -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 (file)
index 0000000..c89325f
--- /dev/null
@@ -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 (file)
index 0000000..7b5feb2
--- /dev/null
@@ -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 (file)
index 0000000..ecb3780
--- /dev/null
@@ -0,0 +1,178 @@
+package FS::part_export::communigate_pro;
+
+use vars qw(@ISA %info %options);
+use Tie::IxHash;
+use FS::part_export;
+use FS::queue;
+
+@ISA = qw(FS::part_export);
+
+tie %options, 'Tie::IxHash',
+  'port'     => { label=>'Port number', default=>'106', },
+  'login'    => { label=>'The administrator account name.  The name can contain a domain part.', },
+  'password' => { label=>'The administrator account password.', },
+  'accountType' => { label=>'Type for newly-created accounts',
+                     type=>'select',
+                     options=>[qw( MultiMailbox TextMailbox MailDirMailbox )],
+                     default=>'MultiMailbox',
+                   },
+  'externalFlag' => { label=> 'Create accounts with an external (visible for legacy mailers) INBOX.',
+                      type=>'checkbox',
+                    },
+  'AccessModes' => { label=>'Access modes',
+                     default=>'Mail POP IMAP PWD WebMail WebSite',
+                   },
+;
+
+%info = (
+  'svc'     => 'svc_acct',
+  'desc'    => 'Real-time export to a CommuniGate Pro mail server',
+  'options' => \%options,
+  'notes'   => <<'END'
+Real time export to a
+<a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a>
+mail server.  The
+<a href="http://www.stalker.com/CGPerl/">CommuniGate Pro Perl Interface</a>
+must be installed as CGP::CLI.
+END
+);
+
+sub rebless { shift; }
+
+sub export_username {
+  my($self, $svc_acct) = (shift, shift);
+  $svc_acct->email;
+}
+
+sub _export_insert {
+  my( $self, $svc_acct ) = (shift, shift);
+  my @options = ( $svc_acct->svcnum, 'CreateAccount',
+    'accountName'    => $self->export_username($svc_acct),
+    'accountType'    => $self->option('accountType'),
+    'AccessModes'    => $self->option('AccessModes'),
+    'RealName'       => $svc_acct->finger,
+    'Password'       => $svc_acct->_password,
+  );
+  push @options, 'MaxAccountSize' => $svc_acct->quota if $svc_acct->quota;
+  push @options, 'externalFlag'   => $self->option('externalFlag')
+    if $self->option('externalFlag');
+
+  $self->communigate_pro_queue( @options );
+}
+
+sub _export_replace {
+  my( $self, $new, $old ) = (shift, shift, shift);
+  return "can't (yet) change username with CommuniGate Pro"
+    if $old->username ne $new->username;
+  return "can't (yet) change domain with CommuniGate Pro"
+    if $self->export_username($old) ne $self->export_username($new);
+  return "can't (yet) change GECOS with CommuniGate Pro"
+    if $old->finger ne $new->finger;
+  return "can't (yet) change quota with CommuniGate Pro"
+    if $old->quota ne $new->quota;
+  return '' unless $old->username ne $new->username
+                || $old->_password ne $new->_password
+                || $old->finger ne $new->finger
+                || $old->quota ne $new->quota;
+
+  return '' if '*SUSPENDED* '. $old->_password eq $new->_password;
+
+  #my $err_or_queue = $self->communigate_pro_queue( $new->svcnum,'RenameAccount',
+  #  $old->email, $new->email );
+  #return $err_or_queue unless ref($err_or_queue);
+  #my $jobnum = $err_or_queue->jobnum;
+
+  $self->communigate_pro_queue( $new->svcnum, 'SetAccountPassword',
+                                $self->export_username($new), $new->_password        )
+    if $new->_password ne $old->_password;
+
+}
+
+sub _export_delete {
+  my( $self, $svc_acct ) = (shift, shift);
+  $self->communigate_pro_queue( $svc_acct->svcnum, 'DeleteAccount',
+    $self->export_username($svc_acct),
+  );
+}
+
+sub _export_suspend {
+  my( $self, $svc_acct ) = (shift, shift);
+  $self->communigate_pro_queue( $svc_acct->svcnum, 'UpdateAccountSettings',
+    'accountName' => $self->export_username($svc_acct),
+    'AccessModes' => 'Mail',
+  );
+}
+
+sub _export_unsuspend {
+  my( $self, $svc_acct ) = (shift, shift);
+  $self->communigate_pro_queue( $svc_acct->svcnum, 'UpdateAccountSettings',
+    'accountName' => $self->export_username($svc_acct),
+    'AccessModes' => $self->option('AccessModes'),
+  );
+}
+
+sub communigate_pro_queue {
+  my( $self, $svcnum, $method ) = (shift, shift, shift);
+  my @kludge_methods = qw(CreateAccount UpdateAccountSettings);
+  my $sub = 'communigate_pro_command';
+  $sub = $method if grep { $method eq $_ } @kludge_methods;
+  my $queue = new FS::queue {
+    'svcnum' => $svcnum,
+    'job'    => "FS::part_export::communigate_pro::$sub",
+  };
+  $queue->insert(
+    $self->machine,
+    $self->option('port'),
+    $self->option('login'),
+    $self->option('password'),
+    $method,
+    @_,
+  );
+
+}
+
+sub CreateAccount {
+  my( $machine, $port, $login, $password, $method, %args ) = @_;
+  my $accountName  = delete $args{'accountName'};
+  my $accountType  = delete $args{'accountType'};
+  my $externalFlag = delete $args{'externalFlag'};
+  $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
+  my @args = ( accountName => $accountName,
+               accountType  => $accountType,
+               settings     => \%args,
+             );
+               #externalFlag => $externalFlag,
+  push @args, externalFlag => $externalFlag if $externalFlag;
+
+  communigate_pro_command( $machine, $port, $login, $password, $method, @args );
+
+}
+
+sub UpdateAccountSettings {
+  my( $machine, $port, $login, $password, $method, %args ) = @_;
+  my $accountName  = delete $args{'accountName'};
+  $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
+  @args = ( $accountName, \%args );
+  communigate_pro_command( $machine, $port, $login, $password, $method, @args );
+}
+
+sub communigate_pro_command { #subroutine, not method
+  my( $machine, $port, $login, $password, $method, @args ) = @_;
+
+  eval "use CGP::CLI";
+
+  my $cli = new CGP::CLI( {
+    'PeerAddr' => $machine,
+    'PeerPort' => $port,
+    'login'    => $login,
+    'password' => $password,
+  } ) or die "Can't login to CGPro: $CGP::ERR_STRING\n";
+
+  $cli->$method(@args) or die "CGPro error: ". $cli->getErrMessage;
+
+  $cli->Logout; # or die "Can't logout of CGPro: $CGP::ERR_STRING\n";
+
+}
+
+1;
+
diff --git a/FS/FS/part_export/communigate_pro_singledomain.pm b/FS/FS/part_export/communigate_pro_singledomain.pm
new file mode 100644 (file)
index 0000000..e25043f
--- /dev/null
@@ -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 (file)
index 0000000..96fa437
--- /dev/null
@@ -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 (file)
index 0000000..0ad00df
--- /dev/null
@@ -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 (file)
index 0000000..84c9e5a
--- /dev/null
@@ -0,0 +1,120 @@
+package FS::part_export::cyrus;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+  'server'   => { label=>'IMAP server' },
+  'username' => { label=>'Admin username' },
+  'password' => { label=>'Admin password' },
+;
+
+%info = ( 
+  'svc'      => 'svc_acct',
+  'desc'     => 'Real-time export to Cyrus IMAP server',
+  'options'  => \%options,
+  'nodomain' => 'Y',
+  'notes'    => <<'END'
+Integration with
+<a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a>.
+Cyrus::IMAP::Admin should be installed locally and the connection to the
+server secured.  <B>svc_acct.quota</B>, if available, is used to set the
+Cyrus quota.
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+  my($self, $svc_acct) = (shift, shift);
+  $self->cyrus_queue( $svc_acct->svcnum, 'insert',
+    $svc_acct->username, $svc_acct->quota );
+}
+
+sub _export_replace {
+  my( $self, $new, $old ) = (shift, shift, shift);
+  return "can't change username using Cyrus"
+    if $old->username ne $new->username;
+  return '';
+#  #return '' unless $old->_password ne $new->_password;
+#  $self->cyrus_queue( $new->svcnum,
+#    'replace', $new->username, $new->_password );
+}
+
+sub _export_delete {
+  my( $self, $svc_acct ) = (shift, shift);
+  $self->cyrus_queue( $svc_acct->svcnum, 'delete',
+    $svc_acct->username );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub cyrus_queue {
+  my( $self, $svcnum, $method ) = (shift, shift, shift);
+  my $queue = new FS::queue {
+    'svcnum' => $svcnum,
+    'job'    => "FS::part_export::cyrus::cyrus_$method",
+  };
+  $queue->insert(
+    $self->option('server'),
+    $self->option('username'),
+    $self->option('password'),
+    @_
+  );
+}
+
+sub cyrus_insert { #subroutine, not method
+  my $client = cyrus_connect(shift, shift, shift);
+  my( $username, $quota ) = @_;
+  my $rc = $client->create("user.$username");
+  my $error = $client->error;
+  die "creating user.$username: $error" if $error;
+
+  $rc = $client->setacl("user.$username", $username => 'all' );
+  $error = $client->error;
+  die "setacl user.$username: $error" if $error;
+
+  if ( $quota ) {
+    $rc = $client->setquota("user.$username", 'STORAGE' => $quota );
+    $error = $client->error;
+    die "setquota user.$username: $error" if $error;
+  }
+
+}
+
+sub cyrus_delete { #subroutine, not method
+  my ( $server, $admin_username, $password_username, $username ) = @_;
+  my $client = cyrus_connect($server, $admin_username, $password_username);
+
+  my $rc = $client->setacl("user.$username", $admin_username => 'all' );
+  my $error = $client->error;
+  die $error if $error;
+
+  $rc = $client->delete("user.$username");
+  $error = $client->error;
+  die $error if $error;
+}
+
+sub cyrus_connect {
+
+  my( $server, $admin_username, $admin_password ) = @_;
+
+  eval "use Cyrus::IMAP::Admin;";
+
+  my $client = Cyrus::IMAP::Admin->new($server);
+  $client->authenticate(
+    -user      => $admin_username,
+    -mechanism => "login",       
+    -password  => $admin_password,
+  );
+  $client;
+
+}
+
+#sub cyrus_replace { #subroutine, not method
+#}
+
+1;
+
diff --git a/FS/FS/part_export/domain_shellcommands.pm b/FS/FS/part_export/domain_shellcommands.pm
new file mode 100644 (file)
index 0000000..994c113
--- /dev/null
@@ -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="../docs/ssh.html">setup SSH for unattended operation</a>.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+  <LI>
+    <INPUT TYPE="button" VALUE="qmail catchall .qmail-domain-default maintenance" onClick='
+      this.form.useradd.value = "[ \"$uid\" -a \"$gid\" -a \"$dir\" -a \"$qdomain\" ] && [ -e $dir/.qmail-$qdomain-default ] || { touch $dir/.qmail-$qdomain-default; chown $uid:$gid $dir/.qmail-$qdomain-default; }";
+      this.form.userdel.value = "";
+      this.form.usermod.value = "";
+    '>
+  <LI>
+    <INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
+      this.form.useradd.value = "/usr/local/ispman/bin/ispman.addDomain -d $domain changeme";
+      this.form.userdel.value = "/usr/local/ispman/bin/ispman.deleteDomain -d $domain";
+      this.form.usermod.value = "";
+    '>
+</UL>
+The following variables are available for interpolation (prefixed with <code>new_</code> or <code>old_</code> for replace operations):
+<UL>
+  <LI><code>$domain</code>
+  <LI><code>$qdomain</code> - domain with periods replaced by colons
+  <LI><code>$uid</code> - of catchall account
+  <LI><code>$gid</code> - of catchall account
+  <LI><code>$dir</code> - home directory of catchall account
+  <LI>All other fields in
+    <a href="../docs/schema.html#svc_domain">svc_domain</a> are also available.
+</UL>
+END
+);
+
+sub rebless { shift; }
+
+sub _export_insert {
+  my($self) = shift;
+  $self->_export_command('useradd', @_);
+}
+
+sub _export_delete {
+  my($self) = shift;
+  $self->_export_command('userdel', @_);
+}
+
+sub _export_command {
+  my ( $self, $action, $svc_domain) = (shift, shift, shift);
+  my $command = $self->option($action);
+  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 (file)
index 0000000..0ce1b16
--- /dev/null
@@ -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/everyone_net.pm b/FS/FS/part_export/everyone_net.pm
new file mode 100644 (file)
index 0000000..e04318e
--- /dev/null
@@ -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 (file)
index 0000000..cee24e4
--- /dev/null
@@ -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="../docs/ssh.html">setup SSH for unattended operation</a>.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+  <LI>
+    <INPUT TYPE="button" VALUE="text vpopmail maintenance" onClick='
+      this.form.useradd.value = "[ -d /home/vpopmail/domains/$domain/$username ] && { echo \"$destination\" > /home/vpopmail/domains/$domain/$username/.qmail; chown vpopmail:vchkpw /home/vpopmail/domains/$domain/$username/.qmail; }";
+      this.form.userdel.value = "rm /home/vpopmail/domains/$domain/$username/.qmail";
+      this.form.usermod.value = "mv /home/vpopmail/domains/$old_domain/$old_username/.qmail /home/vpopmail/domains/$new_domain/$new_username; [ \"$old_destination\" != \"$new_destination\" ] && { echo \"$new_destination\" > /home/vpopmail/domains/$new_domain/$new_username/.qmail; chown vpopmail:vchkpw /home/vpopmail/domains/$new_domain/$new_username/.qmail; }";
+    '>
+  <LI>
+    <INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
+      this.form.useradd.value = "";
+      this.form.userdel.value = "";
+      this.form.usermod.value = "";
+    '>
+</UL>
+The following variables are available for interpolation (prefixed with
+<code>new_</code> or <code>old_</code> for replace operations):
+<UL>
+  <LI><code>$username</code> - 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/http.pm b/FS/FS/part_export/http.pm
new file mode 100644 (file)
index 0000000..55d8329
--- /dev/null
@@ -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/infostreet.pm b/FS/FS/part_export/infostreet.pm
new file mode 100644 (file)
index 0000000..ef16c7c
--- /dev/null
@@ -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/ldap.pm b/FS/FS/part_export/ldap.pm
new file mode 100644 (file)
index 0000000..823d99d
--- /dev/null
@@ -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 (file)
index 0000000..2499ba3
--- /dev/null
@@ -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/null.pm b/FS/FS/part_export/null.pm
new file mode 100644 (file)
index 0000000..0145af3
--- /dev/null
@@ -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 (file)
index 0000000..2978d25
--- /dev/null
@@ -0,0 +1,18 @@
+package FS::part_export::passwdfile;
+
+use strict;
+use vars qw(@ISA %options);
+use Tie::IxHash;
+use FS::part_export::null;
+
+@ISA = qw(FS::part_export::null);
+
+tie %options, 'Tie::IxHash',
+  'crypt' => { label=>'Password encryption',
+               type=>'select', options=>[qw(crypt md5)],
+               default=>'crypt',
+             },
+;
+
+1;
+
diff --git a/FS/FS/part_export/postfix.pm b/FS/FS/part_export/postfix.pm
new file mode 100644 (file)
index 0000000..4fd19ee
--- /dev/null
@@ -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 (file)
index 0000000..3ba1b27
--- /dev/null
@@ -0,0 +1,532 @@
+package FS::part_export::prizm;
+
+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 => '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 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 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 );
+
+  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;
+  }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 "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;
+#    }
+#  }
+
+  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),
+                                      $svc->cust_svc->cust_pkg->part_pkg->pkg,
+                                      $svc->vlan_profile,
+                                      ($self->option('ems') ? 1 : 0 ),
+                                     );
+  return $err_or_som
+    unless ref($err_or_som);
+
+  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);
+
+  $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
+                                     [ $element ],
+                                     $svc->vlan_profile,
+                                     0,
+                                     1,
+                                    );
+  return $err_or_som
+    unless ref($err_or_som);
+
+  $err_or_som = $self->prizm_command('NetworkIfService', 'setElementConfigSet',
+                                     [ $element ],
+                                     $svc->cust_svc->cust_pkg->part_pkg->pkg,
+                                     0,
+                                     1,
+                                    );
+  return $err_or_som
+    unless ref($err_or_som);
+
+  $err_or_som = $self->prizm_command('NetworkIfService',
+                                     'activateNetworkElements',
+                                     [ $element ],
+                                     1,
+                                     ( $self->option('ems') ? 1 : 0 ),
+                                    );
+
+  return $err_or_som
+    unless ref($err_or_som);
+
+  $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);
+
+  '';
+}
+
+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);
+
+  '';
+
+}
+
+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 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 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 (file)
index 0000000..2ac3edb
--- /dev/null
@@ -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 (file)
index 0000000..42aa51c
--- /dev/null
@@ -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 (file)
index 0000000..29e0a57
--- /dev/null
@@ -0,0 +1,399 @@
+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_stdin' => { label=>'Insert command STDIN',
+                       type =>'textarea',
+                       default=>'',
+                     },
+  'userdel' => { label=>'Delete command',
+                 default=>'userdel -r $username',
+                 #default=>'rm -rf $dir',
+               },
+  'userdel_stdin' => { label=>'Delete command STDIN',
+                       type =>'textarea',
+                       default=>'',
+                     },
+  'usermod' => { label=>'Modify command',
+                 default=>'usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -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_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_stdin' => { label=>'Suspension command STDIN',
+                       default=>'',
+                     },
+  'unsuspend' => { label=>'Unsuspension command',
+                   default=>'usermod -U $username',
+                 },
+  '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',
+                         },
+;
+
+%info = (
+  'svc'      => 'svc_acct',
+  'desc'     =>
+    'Real-time export via remote SSH (i.e. useradd, userdel, etc.)',
+  'options'  => \%options,
+  'nodomain' => 'Y',
+  'notes' => <<'END'
+Run remote commands via SSH.  Usernames are considered unique (also see
+shellcommands_withdomain).  You probably want this if the commands you are
+running will not accept a domain as a parameter.  You will need to
+<a href="../docs/ssh.html">setup SSH for unattended operation</a>.
+
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+  <LI>
+    <INPUT TYPE="button" VALUE="Linux" onClick='
+      this.form.useradd.value = "useradd -c $finger -d $dir -m -s $shell -u $uid -p $crypt_password $username";
+      this.form.useradd_stdin.value = "";
+      this.form.userdel.value = "userdel -r $username";
+      this.form.userdel_stdin.value="";
+      this.form.usermod.value = "usermod -c $new_finger -d $new_dir -m -l $new_username -s $new_shell -u $new_uid -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>All other fields in <a href="../docs/schema.html#svc_acct">svc_acct</a> are also available.
+</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 ) {
+    $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) ) {
+    $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 = '';
+  }
+
+  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;
+
+  my $command_string = eval(qq("$command"));
+
+  $self->shellcommands_queue( $svc_acct->svcnum,
+    user         => $self->option('user')||'root',
+    host         => $self->machine,
+    command      => $command_string,
+    stdin_string => $stdin_string,
+  );
+}
+
+sub _export_replace {
+  my($self, $new, $old ) = (shift, shift, shift);
+  my $command = $self->option('usermod');
+  my $stdin = $self->option('usermod_stdin');
+  no strict 'vars';
+  {
+    no strict 'refs';
+    ${"old_$_"} = $old->getfield($_) foreach $old->fields;
+    ${"new_$_"} = $new->getfield($_) foreach $new->fields;
+  }
+  $new_finger =~ /^(.*)\s+(\S+)$/ or $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;
+
+  my $command_string = eval(qq("$command"));
+
+  $self->shellcommands_queue( $new->svcnum,
+    user         => $self->option('user')||'root',
+    host         => $self->machine,
+    command      => $command_string,
+    stdin_string => $stdin_string,
+  );
+}
+
+#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 (file)
index 0000000..7c5d904
--- /dev/null
@@ -0,0 +1,112 @@
+package FS::part_export::shellcommands_withdomain;
+
+use vars qw(@ISA %info);
+use Tie::IxHash;
+use FS::part_export::shellcommands;
+
+@ISA = qw(FS::part_export::shellcommands);
+
+tie my %options, 'Tie::IxHash',
+  'user' => { label=>'Remote username', default=>'root' },
+  'useradd' => { label=>'Insert command',
+                 #default=>''
+               },
+  'useradd_stdin' => { label=>'Insert command STDIN',
+                       type =>'textarea',
+                       #default=>"$_password\n$_password\n",
+                     },
+  'userdel' => { label=>'Delete command',
+                 #default=>'',
+               },
+  'userdel_stdin' => { label=>'Delete command STDIN',
+                       type =>'textarea',
+                       #default=>'',
+                     },
+  'usermod' => { label=>'Modify command',
+                 default=>'',
+               },
+  'usermod_stdin' => { label=>'Modify command STDIN',
+                       type =>'textarea',
+                       #default=>"$_password\n$_password\n",
+                     },
+  'usermod_pwonly' => { label=>'Disallow username, 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=>'',
+                     },
+  'unsuspend' => { label=>'Unsuspension command',
+                   default=>'',
+                 },
+  'unsuspend_stdin' => { label=>'Unsuspension command STDIN',
+                         default=>'',
+                       },
+  '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="../docs/ssh.html">setup SSH for unattended operation</a>.
+
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+  <LI><INPUT TYPE="button" VALUE="vpopmail" onClick='
+    this.form.useradd.value = "/home/vpopmail/bin/vadduser $username\\\@$domain $quoted_password";
+    this.form.useradd_stdin.value = "";
+    this.form.userdel.value = "/home/vpopmail/bin/vdeluser $username\\\@$domain";
+    this.form.userdel_stdin.value="";
+    this.form.usermod.value = "/home/vpopmail/bin/vpasswd $new_username\\\@$new_domain $new_quoted_password";
+    this.form.usermod_stdin.value = "";
+    this.form.usermod_pwonly.checked = true;
+  '>
+  <LI><INPUT TYPE="button" VALUE="ISPMan CLI" onClick='
+    this.form.useradd.value = "/usr/local/ispman/bin/ispman.addUser -d $domain -f $first -l $last -q $quota -p $quoted_password $username";
+    this.form.useradd_stdin.value = "";
+    this.form.userdel.value = "/usr/local/ispman/bin/ispman.delUser -d $domain $username";
+    this.form.userdel_stdin.value="";
+    this.form.usermod.value = "/usr/local/ispman/bin/ispman.passwd.user $new_username\\\@$new_domain $new_quoted_password";
+    this.form.usermod_stdin.value = "";
+    this.form.usermod_pwonly.checked = true;
+  '>
+</UL>
+
+The following variables are available for interpolation (prefixed with
+<code>new_</code> or <code>old_</code> for replace operations):
+<UL>
+  <LI><code>$username</code>
+  <LI><code>$domain</code>
+  <LI><code>$_password</code>
+  <LI><code>$quoted_password</code> - unencrypted password, 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 (file)
index 0000000..81b3c7e
--- /dev/null
@@ -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/sqlmail.pm b/FS/FS/part_export/sqlmail.pm
new file mode 100644 (file)
index 0000000..cbdaf7f
--- /dev/null
@@ -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 (file)
index 0000000..5e63e10
--- /dev/null
@@ -0,0 +1,722 @@
+package FS::part_export::sqlradius;
+
+use vars qw(@ISA $DEBUG %info %options $notes1 $notes2);
+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);
+
+$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', } ,
+  '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="../docs/man/bin/freeside-sqlradius-reset">freeside-sqlradius-reset</a>
+to delete the entire RADIUS database and repopulate the tables from the
+Freeside database.  See the
+<a href="http://search.cpan.org/dist/DBI/DBI.pm#connect">DBI documentation</a>
+and the
+<a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">documentation for your DBD</a>
+for the exact syntax of a DBI data source.
+<ul>
+  <li>Using FreeRADIUS 0.9.0 with the PostgreSQL backend, the db_postgresql.sql schema and postgresql.conf queries contain incompatible changes.  This is fixed in 0.9.1.  Only new installs with 0.9.0 and PostgreSQL are affected - upgrades and other database backends and versions are unaffected.
+  <li>Using ICRADIUS, add a dummy "op" column to your database:
+    <blockquote><code>
+      ALTER&nbsp;TABLE&nbsp;radcheck&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
+      ALTER&nbsp;TABLE&nbsp;radreply&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
+      ALTER&nbsp;TABLE&nbsp;radgroupcheck&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='<br>
+      ALTER&nbsp;TABLE&nbsp;radgroupreply&nbsp;ADD&nbsp;COLUMN&nbsp;op&nbsp;VARCHAR(2)&nbsp;NOT&nbsp;NULL&nbsp;DEFAULT&nbsp;'=='
+    </code></blockquote>
+  <li>Using Radiator, see the
+    <a href="http://www.open.com.au/radiator/faq.html#38">Radiator FAQ</a>
+    for configuration information.
+</ul>
+END
+
+%info = (
+  'svc'      => 'svc_acct',
+  'desc'     => 'Real-time export to SQL-backed RADIUS (FreeRADIUS, ICRADIUS)',
+  '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_acct) = (shift, shift);
+
+  foreach my $table (qw(reply check)) {
+    my $method = "radius_$table";
+    my %attrib = $svc_acct->$method();
+    next unless keys %attrib;
+    my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'insert',
+      $table, $self->export_username($svc_acct), %attrib );
+    return $err_or_queue unless ref($err_or_queue);
+  }
+  my @groups = $svc_acct->radius_groups;
+  if ( @groups ) {
+    cluck localtime(). ": queuing usergroup_insert for ". $svc_acct->svcnum.
+          " (". $self->export_username($svc_acct). " with ". join(", ", @groups)
+      if $DEBUG;
+    my $err_or_queue = $self->sqlradius_queue(
+      $svc_acct->svcnum, 'usergroup_insert',
+      $self->export_username($svc_acct), @groups );
+    return $err_or_queue unless ref($err_or_queue);
+  }
+  '';
+}
+
+sub _export_replace {
+  my( $self, $new, $old ) = (shift, shift, shift);
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $jobnum = '';
+  if ( $self->export_username($old) ne $self->export_username($new) ) {
+    my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'rename',
+      $self->export_username($new), $self->export_username($old) );
+    unless ( ref($err_or_queue) ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $err_or_queue;
+    }
+    $jobnum = $err_or_queue->jobnum;
+  }
+
+  foreach my $table (qw(reply check)) {
+    my $method = "radius_$table";
+    my %new = $new->$method();
+    my %old = $old->$method();
+    if ( grep { !exists $old{$_} #new attributes
+                || $new{$_} ne $old{$_} #changed
+              } keys %new
+    ) {
+      my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'insert',
+        $table, $self->export_username($new), %new );
+      unless ( ref($err_or_queue) ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $err_or_queue;
+      }
+      if ( $jobnum ) {
+        my $error = $err_or_queue->depend_insert( $jobnum );
+        if ( $error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return $error;
+        }
+      }
+    }
+
+    my @del = grep { !exists $new{$_} } keys %old;
+    if ( @del ) {
+      my $err_or_queue = $self->sqlradius_queue( $new->svcnum, 'attrib_delete',
+        $table, $self->export_username($new), @del );
+      unless ( ref($err_or_queue) ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $err_or_queue;
+      }
+      if ( $jobnum ) {
+        my $error = $err_or_queue->depend_insert( $jobnum );
+        if ( $error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return $error;
+        }
+      }
+    }
+  }
+
+  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_acct ) = (shift, shift);
+  my $err_or_queue = $self->sqlradius_queue( $svc_acct->svcnum, 'delete',
+    $self->export_username($svc_acct) );
+  ref($err_or_queue) ? '' : $err_or_queue;
+}
+
+sub sqlradius_queue {
+  my( $self, $svcnum, $method ) = (shift, shift, shift);
+  my $queue = new FS::queue {
+    'svcnum' => $svcnum,
+    'job'    => "FS::part_export::sqlradius::sqlradius_$method",
+  };
+  $queue->insert(
+    $self->option('datasrc'),
+    $self->option('username'),
+    $self->option('password'),
+    @_,
+  ) or $queue;
+}
+
+sub 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;
+  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 =~ /Password/i ? '==' : ':=' ),
+        $attributes{$attribute},
+      ) or die $i_sth->errstr;
+
+    }
+
+  }
+  $dbh->disconnect;
+}
+
+sub sqlradius_usergroup_insert { #subroutine, not method
+  my $dbh = sqlradius_connect(shift, shift, shift);
+  my( $username, @groups ) = @_;
+
+  my $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 TIMESTAMP_START TIMESTAMP_END [ SVC_ACCT [ IP [ PREFIX [ SQL_SELECT ] ] ] ]
+
+TIMESTAMP_START and TIMESTAMP_END are specified as UNIX timestamps; see
+L<perlfunc/"time">.  Also see L<Time::Local> and L<Date::Parse> for conversion
+functions.
+
+SVC_ACCT, if specified, limits the results to the specified account.
+
+IP, if specified, limits the results to the specified IP address.
+
+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, $start, $end ) = splice(@_, 0, 3);
+  my $svc_acct = @_ ? shift : '';
+  my $ip = @_ ? shift : '';
+  my $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 =~ /^([^@]+)\@([^@]+)$/ ) {
+      $where = '( UserName = ? OR ( UserName = ? AND Realm = ? ) ) AND';
+      push @param, $username, $1, $2;
+    } else {
+      $where = 'UserName = ? AND';
+      push @param, $username;
+    }
+  }
+
+  if ( length($ip) ) {
+    $where .= ' FramedIPAddress = ? AND';
+    push @param, $ip;
+  }
+
+  if ( length($prefix) ) {
+    #assume sip: for now, else things get ugly trying to match /^\w+:$prefix/
+    $where .= " CalledStationID LIKE 'sip:$prefix\%' AND";
+  }
+
+  push @param, $start, $end;
+
+  my $sth = $dbh->prepare('SELECT '. join(', ', @fields).
+                          "  FROM radacct
+                             WHERE $where
+                                   $str2time AcctStopTime ) >= ?
+                               AND $str2time AcctStopTime ) <=  ?
+                               ORDER BY AcctStartTime DESC
+  ") or die $dbh->errstr;                                 
+  $sth->execute(@param) or die $sth->errstr;
+
+  [ map { { %$_ } } @{ $sth->fetchall_arrayref({}) } ];
+
+}
+
+=item update_svc_acct
+
+=cut
+
+sub update_svc_acct {
+  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 {
+      warn "found svc_acct ". $svc_acct[0]->svcnum. " $errinfo\n" if $DEBUG;
+      $svc_acct[0]->last_login($AcctStartTime);
+      $svc_acct[0]->last_logout($AcctStopTime);
+      my @stati;
+      push @stati, _try_decrement($svc_acct[0], 'seconds', $AcctSessionTime);
+      push @stati, _try_decrement($svc_acct[0], 'upbytes', $AcctInputOctets);
+      push @stati, _try_decrement($svc_acct[0], 'downbytes', $AcctOutputOctets);
+      push @stati, _try_decrement($svc_acct[0], 'totalbytes', $AcctInputOctets + 
+                     $AcctOutputOctets);
+      $status=join(' ', @stati);
+    }
+
+    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';
+}
+
+1;
+
diff --git a/FS/FS/part_export/sqlradius_withdomain.pm b/FS/FS/part_export/sqlradius_withdomain.pm
new file mode 100644 (file)
index 0000000..e5a7151
--- /dev/null
@@ -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 (file)
index 0000000..244c3bf
--- /dev/null
@@ -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 (file)
index 0000000..3cd7039
--- /dev/null
@@ -0,0 +1,191 @@
+package FS::part_export::textradius;
+
+use vars qw(@ISA %info $prefix);
+use Fcntl qw(:flock);
+use Tie::IxHash;
+use FS::UID qw(datasrc);
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+  'user' => { label=>'Remote username', default=>'root' },
+  'users' => { label=>'users file location', default=>'/etc/raddb/users' },
+;
+
+%info = (
+  'svc'     => 'svc_acct',
+  'desc'    =>
+    'Real-time export to a text /etc/raddb/users file (Livingston, Cistron)',
+  'options' => \%options,
+  'notes'   => <<'END'
+This will edit a text RADIUS users file in place on a remote server.
+Requires installation of
+<a href="http://search.cpan.org/dist/RADIUS-UserFile">RADIUS::UserFile</a>
+from CPAN.  If using RADIUS::UserFile 1.01, make sure to apply
+<a href="http://rt.cpan.org/NoAuth/Bug.html?id=1210">this patch</a>.  Also
+make sure <a href="http://rsync.samba.org/">rsync</a> is installed on the
+remote machine, and <a href="../docs/ssh.html">SSH is setup for unattended
+operation</a>.
+END
+);
+
+$prefix = "%%%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/trango.pm b/FS/FS/part_export/trango.pm
new file mode 100644 (file)
index 0000000..e7f1126
--- /dev/null
@@ -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/vpopmail.pm b/FS/FS/part_export/vpopmail.pm
new file mode 100644 (file)
index 0000000..4cda657
--- /dev/null
@@ -0,0 +1,254 @@
+package FS::part_export::vpopmail;
+
+use vars qw(@ISA %info @saltset $exportdir);
+use Fcntl qw(:flock);
+use Tie::IxHash;
+use File::Path;
+use FS::UID qw( datasrc );
+use FS::part_export;
+
+@ISA = qw(FS::part_export);
+
+tie my %options, 'Tie::IxHash',
+  #'machine' => { label=>'vpopmail machine', },
+  'dir'     => { label=>'directory', }, # ?more info? default?
+  'uid'     => { label=>'vpopmail uid' },
+  'gid'     => { label=>'vpopmail gid' },
+  'restart' => { label=> 'vpopmail restart command',
+                 default=> 'cd /home/vpopmail/domains; for domain in *; do /home/vpopmail/bin/vmkpasswd $domain; done; /var/qmail/bin/qmail-newu; killall -HUP qmail-send',
+               },
+;
+
+%info = (
+  'svc'     => 'svc_acct',
+  'desc'    => 'Real-time export to vpopmail text files',
+  'options' => \%options,
+  'notes'   => <<'END'
+This export is currently unmaintained.  See shellcommands_withdomain for an
+export that uses vpopmail CLI commands instead.<BR>
+<BR>
+Real time export to <a href="http://inter7.com/vpopmail/">vpopmail</a> text
+files.  <a href="http://search.cpan.org/dist/File-Rsync">File::Rsync</a>
+must be installed, and you will need to
+<a href="../docs/ssh.html">setup SSH for unattended operation</a>
+to <b>vpopmail</b>@<i>export.host</i>. 
+END
+);
+
+@saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+sub rebless { shift; }
+
+sub _export_insert {
+  my($self, $svc_acct) = (shift, shift);
+  $self->vpopmail_queue( $svc_acct->svcnum, 'insert',
+    $svc_acct->username,
+    crypt($svc_acct->_password,$saltset[int(rand(64))].$saltset[int(rand(64))]),
+    $svc_acct->domain,
+    $svc_acct->quota,
+    $svc_acct->finger,
+  );
+}
+
+sub _export_replace {
+  my( $self, $new, $old ) = (shift, shift, shift);
+
+  my $cpassword = crypt(
+    $new->_password, $saltset[int(rand(64))].$saltset[int(rand(64))]
+  );
+
+  return "can't change username with vpopmail"
+    if $old->username ne $new->username;
+
+  #no.... if mail can't be preserved, better to disallow username changes
+  #if ($old->username ne $new->username || $old->domain ne $new->domain ) {
+  #  vpopmail_queue( $svc_acct->svcnum, 'delete', 
+  #    $old->username, $old->domain
+  #  );
+  #  vpopmail_queue( $svc_acct->svcnum, 'insert', 
+  #    $new->username,
+  #    $cpassword,
+  #    $new->domain,
+  #  );
+
+  return '' unless $old->_password ne $new->_password;
+
+  $self->vpopmail_queue( $new->svcnum, 'replace',
+    $new->username, $cpassword, $new->domain, $new->quota, $new->finger );
+}
+
+sub _export_delete {
+  my( $self, $svc_acct ) = (shift, shift);
+  $self->vpopmail_queue( $svc_acct->svcnum, 'delete',
+    $svc_acct->username, $svc_acct->domain );
+}
+
+#a good idea to queue anything that could fail or take any time
+sub vpopmail_queue {
+  my( $self, $svcnum, $method ) = (shift, shift, shift);
+
+  my $exportdir = "%%%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 (file)
index 0000000..82d5557
--- /dev/null
@@ -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.
+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 (file)
index 0000000..7e4be9c
--- /dev/null
@@ -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="../docs/ssh.html">setup SSH for unattended operation</a>.
+<BR><BR>Use these buttons for some useful presets:
+<UL>
+  <LI>
+    <INPUT TYPE="button" VALUE="Maintain directories" onClick='
+      this.form.user.value = "root";
+      this.form.useradd.value = "mkdir $homedir/$zone; chown $username $homedir/$zone; ln -s $homedir/$zone /var/www/$zone";
+      this.form.userdel.value = "[ -n \"$zone\" ] && rm -rf /var/www/$zone; rm -rf $homedir/$zone";
+      this.form.usermod.value = "[ -n \"$old_zone\" ] && rm /var/www/$old_zone; [ \"$old_zone\" != \"$new_zone\" -a -n \"$new_zone\" ] && ( mv $old_homedir/$old_zone $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone ); [ \"$old_username\" != \"$new_username\" ] && chown -R $new_username $new_homedir/$new_zone; ln -sf $new_homedir/$new_zone /var/www/$new_zone";
+      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 (file)
index 0000000..e759404
--- /dev/null
@@ -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 (file)
index 0000000..84502b7
--- /dev/null
@@ -0,0 +1,896 @@
+package FS::part_pkg;
+
+use strict;
+use vars qw( @ISA %plans $DEBUG );
+use Carp qw(carp cluck confess);
+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;
+
+@ISA = qw( FS::m2m_Common FS::Record ); # FS::option_Common ); # this can use option_Common
+                                                # when all the plandata bs is
+                                                # gone
+
+$DEBUG = 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 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>)
+
+=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 `(CUSTOM) ' is prepended
+to the comment field.  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{'comment'} = "(CUSTOM) ". $hash{'comment'}
+    unless $hash{'comment'} =~ /^\(CUSTOM\) /;
+  #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<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 "  saving legacy plandata" if $DEBUG;
+  my $plandata = $self->get('plandata');
+  $self->set('plandata', '');
+
+  warn "  inserting part_pkg record" if $DEBUG;
+  my $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  if ( $plandata ) {
+
+    warn "  inserting part_pkg_option records for 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'     => $self->pkgpart,
+              'optionname'  => $1,
+              'optionvalue' => $2,
+            };
+          }
+      split("\n", $plandata)
+    ) {
+      my $error = $part_pkg_option->insert;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    }
+
+  } elsif ( $options{'options'} ) {
+
+    warn "  inserting part_pkg_option records for options hashref" if $DEBUG;
+    foreach my $optionname ( keys %{$options{'options'}} ) {
+
+      my $part_pkg_option =
+        new FS::part_pkg_option {
+          'pkgpart'     => $self->pkgpart,
+          'optionname'  => $optionname,
+          'optionvalue' => $options{'options'}->{$optionname},
+        };
+
+      my $error = $part_pkg_option->insert;
+      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 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> and I<primary_svc>
+
+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 replace.
+
+If I<primary_svc> is set to the svcpart of the primary service, the appropriate
+FS::pkg_svc record will be updated.
+
+=cut
+
+sub replace {
+  my( $new, $old ) = ( shift, shift );
+  my %options = @_;
+
+  # We absolutely have to have an old vs. new record to make this work.
+  if (!defined($old)) {
+    $old = qsearchs( 'part_pkg', { 'pkgpart' => $new->pkgpart } );
+  }
+
+  warn "FS::part_pkg::replace called on $new to replace $old ".
+       "with options %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 "  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);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  warn "  inserting part_pkg_option records for 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 = $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"
+      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 $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_floatn('pay_weight')
+    || $self->ut_floatn('credit_weight')
+    || $self->ut_agentnum_acl('agentnum', 'Edit global package definitions')
+    || $self->SUPER::check
+  ;
+  return $error if $error;
+
+  if ( $self->classnum !~ /^$/ ) {
+    my $error = $self->ut_foreign_key('classnum', 'pkg_class', 'classnum');
+    return $error if $error;
+  } else {
+    $self->classnum('');
+  }
+
+  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_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 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 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
+
+Returns all FS::pkg_svc objects (see L<FS::pkg_svc>) for this package
+definition (with non-zero quantity).
+
+=cut
+
+sub pkg_svc {
+  my $self = shift;
+  #sort { $b->primary cmp $a->primary } 
+    grep { $_->quantity }
+      qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
+}
+
+=item svcpart [ SVCDB ]
+
+Returns the svcpart of the primary service definition (see L<FS::part_svc>)
+associated with this 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, 
+
+=cut
+
+sub svcpart {
+  my $self = shift;
+  my $svcdb = scalar(@_) ? shift : '';
+  my @svcdb_pkg_svc =
+    grep { ( $svcdb eq $_->part_svc->svcdb || !$svcdb ) } $self->pkg_svc;
+  my @pkg_svc = ();
+  @pkg_svc = grep { $_->primary_svc =~ /^Y/i } @svcdb_pkg_svc
+    if dbdef->table('pkg_svc')->column('primary_svc');
+  @pkg_svc = grep {$_->quantity == 1 } @svcdb_pkg_svc
+    unless @pkg_svc;
+  return '' if scalar(@pkg_svc) != 1;
+  $pkg_svc[0]->svcpart;
+}
+
+=item payby
+
+Returns a list of the acceptable payment types for this package.  Eventually
+this should come out of a database table and be editable, but currently has the
+following logic instead:
+
+If the package 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 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 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
+
+Returns the option value for the given name, or the empty string.
+
+=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 _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 ) {
+    confess "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; }
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item plan_info
+
+=cut
+
+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"
+        unless $mod =~ /^(passwdfile|null)$/; #hack but what the heck
+      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;
+}
+
+=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
+
+=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
+
+=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/base_delayed.pm b/FS/FS/part_pkg/base_delayed.pm
new file mode 100644 (file)
index 0000000..ddd4caf
--- /dev/null
@@ -0,0 +1,51 @@
+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)',
+  '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' => 50,
+);
+
+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 (file)
index 0000000..04896e0
--- /dev/null
@@ -0,0 +1,93 @@
+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)',
+  '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' => 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++ );
+  }
+
+  $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
+              || ! $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 * ( $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 (file)
index 0000000..44645b7
--- /dev/null
@@ -0,0 +1,96 @@
+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 = 0;
+$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' => 55,
+);
+
+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;
+
+  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_svc ( $cust_pkg->h_cust_svc( $$sdate, $last_bill ) ) {
+
+    my @label = $h_svc->label( $$sdate, $last_bill );
+    die "fatal: no historical label found, wtf?" unless scalar(@label); #?
+    #my $svc_details = $label[0].': '. $label[1]. ': ';
+    my $svc_details = $label[1]. ': ';
+
+    my $svc_charge = 0;
+
+    my $svc_start = $h_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_svc->date_deleted;
+    $svc_end = ( !$svc_end || $svc_end > $$sdate ) ? $$sdate : $svc_end;
+
+    $svc_charge = $self->option('svc_recur_fee') * ( $svc_end - $svc_start )
+                                                 / ( $$sdate  - $last_bill );
+
+    $svc_details .= $money_char. sprintf('%.2f', $svc_charge ).
+                    ' ('.  time2str('%x', $svc_start).
+                    ' - '. time2str('%x', $svc_end  ). ')'
+      if $self->option('svc_recur_fee');
+
+    push @$details, $svc_details;
+    $total_svc_charge += $svc_charge;
+
+  }
+
+  sprintf("%.2f", $self->base_recur($cust_pkg) + $total_svc_charge );
+}
+
+sub is_free_options {
+  qw( setup_fee recur_fee svc_setup_fee svc_recur_fee );
+}
+
+1;
+
diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm
new file mode 100644 (file)
index 0000000..92e72cf
--- /dev/null
@@ -0,0 +1,168 @@
+package FS::part_pkg::flat;
+
+use strict;
+use vars qw(@ISA %info);
+#use FS::Record qw(qsearch);
+use FS::UI::bytecount;
+use FS::part_pkg;
+
+@ISA = qw(FS::part_pkg);
+
+%info = (
+  'name' => 'Flat rate (anniversary billing)',
+  'fields' => {
+    'setup_fee'     => { 'name' => 'Setup fee for this package',
+                         'default' => 0,
+                       },
+    'recur_fee'     => { 'name' => 'Recurring fee for this package',
+                         'default' => 0,
+                       },
+    'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+                                   ' of service at cancellation',
+                         'type' => 'checkbox',
+                       },
+    'externalid' => { 'name'   => 'Optional External ID',
+                      'default' => '',
+                    },
+    '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',
+                        },
+  },
+  'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 
+                    'seconds', 'upbytes', 'downbytes', 'totalbytes',
+                    'recharge_amount', 'recharge_seconds', 'recharge_upbytes',
+                    'recharge_downbytes', 'recharge_totalbytes',
+                    'usage_rollover', 'recharge_reset', '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++ );
+  }
+
+  $self->option('setup_fee');
+}
+
+sub calc_recur {
+  my($self, $cust_pkg) = @_;
+  $self->base_recur($cust_pkg);
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('recur_fee', 1) || 0;
+}
+
+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;
+  return 0 if    ! $self->base_recur
+              || ! $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 * ( $next_bill - $time ) / $freq_sec );
+
+}
+
+sub is_free_options {
+  qw( setup_fee recur_fee );
+}
+
+sub is_prepaid {
+  0; #no, we're postpaid
+}
+
+sub reset_usage {
+  my($self, $cust_pkg) = @_;
+  my %values = map { $_, $self->option($_) } 
+    grep { $self->option($_, 'hush') } 
+    qw(seconds upbytes downbytes totalbytes);
+  if ($self->option('usage_rollover', 1)) {
+    $cust_pkg->recharge(\%values);
+  }else{
+    $cust_pkg->set_usage(\%values);
+  }
+}
+
+1;
diff --git a/FS/FS/part_pkg/flat_comission.pm b/FS/FS/part_pkg/flat_comission.pm
new file mode 100644 (file)
index 0000000..4592bed
--- /dev/null
@@ -0,0 +1,66 @@
+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',
+  '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');
+}
+
+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 (file)
index 0000000..82e5111
--- /dev/null
@@ -0,0 +1,64 @@
+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',
+  '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');
+}
+
+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 (file)
index 0000000..07c3d1b
--- /dev/null
@@ -0,0 +1,57 @@
+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',
+  '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');
+}
+
+1;
diff --git a/FS/FS/part_pkg/flat_delayed.pm b/FS/FS/part_pkg/flat_delayed.pm
new file mode 100644 (file)
index 0000000..8ac1682
--- /dev/null
@@ -0,0 +1,68 @@
+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)',
+  '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' => 50,
+);
+
+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
+              || ! $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 (file)
index 0000000..c92ba97
--- /dev/null
@@ -0,0 +1,67 @@
+package FS::part_pkg::flat_introrate;
+
+use strict;
+use vars qw(@ISA %info $DEBUG $DEBUG_PRE);
+#use FS::Record qw(qsearch qsearchs);
+use FS::part_pkg::flat;
+
+use Date::Manip qw(DateCalc UnixDate ParseDate);
+
+@ISA = qw(FS::part_pkg::flat);
+$DEBUG = 0;
+$DEBUG_PRE = '[' . __PACKAGE__ . ']: ';
+
+%info = (
+  'name' => 'Introductory price for X months, then flat rate,'.
+            'relative to setup date (anniversary billing)',
+  'fields' =>  {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'intro_fee' => { 'name' => 'Introductory recurring free for this package',
+                     'default' => 0,
+                   },
+    'intro_duration' => { 'name' => 'Duration of the introductory period, ' .
+                                    'in number of months',
+                          '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',
+                       },
+  },
+  'fieldorder' => [ 'setup_fee', 'intro_duration', 'intro_fee', 'recur_fee', 'unused_credit' ],
+  'weight' => 150,
+);
+
+sub calc_recur {
+  my($self, $cust_pkg, $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 $DEBUG_PRE . "\$duration = ${duration}" if $DEBUG;
+  warn $DEBUG_PRE . "\$intro_end = ${intro_end}" if $DEBUG;
+  warn $DEBUG_PRE . "$$time < " . &UnixDate($intro_end, '%s') if $DEBUG;
+
+  if ($$time < &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 (file)
index 0000000..8740547
--- /dev/null
@@ -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 (file)
index 0000000..d309d45
--- /dev/null
@@ -0,0 +1,38 @@
+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',
+;
+
+%info = (
+  'name' => 'Prepaid, flat rate',
+  '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,
+                     },
+  },
+  'fieldorder' => [ 'setup_fee', 'recur_fee', 'recur_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 (file)
index 0000000..45bbf01
--- /dev/null
@@ -0,0 +1,122 @@
+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)',
+  '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', 'upbyte', '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 ) = @_;
+  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;
+
+  $permonth * ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) );
+}
+
+1;
diff --git a/FS/FS/part_pkg/prorate_delayed.pm b/FS/FS/part_pkg/prorate_delayed.pm
new file mode 100644 (file)
index 0000000..ee66432
--- /dev/null
@@ -0,0 +1,61 @@
+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)',
+  '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,
+                    },
+    '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' => 50,
+);
+
+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
+              || ! $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/sesmon_hour.pm b/FS/FS/part_pkg/sesmon_hour.pm
new file mode 100644 (file)
index 0000000..9843edb
--- /dev/null
@@ -0,0 +1,56 @@
+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',
+  'fields' => {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'recur_flat' => { '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_flat', '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_flat.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_flat') + $hours * $self->option('recur_hourly_charge');
+
+}
+
+sub is_free_options {
+  qw( setup_fee recur_fee recur_hourly_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('recur_flat');
+}
+
+1;
diff --git a/FS/FS/part_pkg/sesmon_minute.pm b/FS/FS/part_pkg/sesmon_minute.pm
new file mode 100644 (file)
index 0000000..39516f8
--- /dev/null
@@ -0,0 +1,55 @@
+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',
+  'fields' => {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'recur_flat' => { '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_flat', '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_flat.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_flat') + $min * $self->option('recur_minly_charge');
+}
+
+sub is_free_options {
+  qw( setup_fee recur_fee recur_minly_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('recur_flat');
+}
+
+1;
diff --git a/FS/FS/part_pkg/sql_external.pm b/FS/FS/part_pkg/sql_external.pm
new file mode 100644 (file)
index 0000000..ca58c4e
--- /dev/null
@@ -0,0 +1,76 @@
+package FS::part_pkg::sql_external;
+
+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 additional fees for external services from a configurable SQL query',
+  'fields' => {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'recur_flat' => { 'name' => 'Base recurring fee for this package',
+                      'default' => 0,
+                    },
+    'unused_credit' => { 'name' => 'Credit the customer for the unused portion'.
+                                   ' of service at cancellation',
+                         'type' => 'checkbox',
+                       },
+    'datasrc' => { 'name' => 'DBI data source',
+                   'default' => '',
+                 },
+    'db_username' => { 'name' => 'Database username',
+                       'default' => '',
+                     },
+    'db_password' => { 'name' => 'Database password',
+                       'default' => '',
+                     },
+    'query' => { 'name' => 'SQL query',
+                 'default' => '',
+               },
+  },
+  'fieldorder' => [qw( setup_fee recur_flat unused_credit datasrc db_username db_password query )],
+  #'setup' => 'what.setup_fee.value',
+  #'recur' => q!'my $dbh = DBI->connect("' + what.datasrc.value + '", "' + what.db_username.value + '", "' + what.db_password.value + '" ) or die $DBI::errstr; my $sth = $dbh->prepare("' + what.query.value + '") or die $dbh->errstr; my $price = ' + what.recur_flat.value + '; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_external" } $cust_pkg->cust_svc ){ my $id = $cust_svc->svc_x->id; $sth->execute($id) or die $sth->errstr; $price += $sth->fetchrow_arrayref->[0]; } $price;'!,
+  'weight' => '72',
+);
+
+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 $price = $self->option('recur_flat');
+
+  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 is_free {
+  0;
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('recur_flat');
+}
+
+1;
diff --git a/FS/FS/part_pkg/sql_generic.pm b/FS/FS/part_pkg/sql_generic.pm
new file mode 100644 (file)
index 0000000..0e6ab7c
--- /dev/null
@@ -0,0 +1,87 @@
+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',
+  'fields' => {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'recur_flat' => { '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_flat 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_flat.value + \' + $units * \' + what.recur_unit_charge.value + \';\'',
+  #'recur' => '\'my $dbh = DBI->connect("\' + what.datasrc.value + \'", "\' + what.db_username.value + \'", "\' what.db_password.value + \'" ) or die $DBI::errstr; my $sth = $dbh->prepare("\' + what.query.value + \'") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_domain" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge + \';\'',
+  'weight' => '70',
+);
+
+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_flat') + $units * $self->option('recur_unit_charge');
+}
+
+sub is_free_options {
+  qw( setup_fee recur_flat recur_unit_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('recur_flat');
+}
+
+1;
diff --git a/FS/FS/part_pkg/sqlradacct_hour.pm b/FS/FS/part_pkg/sqlradacct_hour.pm
new file mode 100644 (file)
index 0000000..e54a8a5
--- /dev/null
@@ -0,0 +1,170 @@
+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',
+  'fields' => {
+    'setup_fee' => { 'name' => 'Setup fee for this package',
+                     'default' => 0,
+                   },
+    'recur_flat' => { '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_flat 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_flat.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_hours_cap')
+    if $self->option('recur_hours_cap')
+    && $hourscharge > $self->option('recur_hours_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_flat') + $charges;
+}
+
+sub is_free_options {
+  qw( setup_fee recur_flat recur_hourly_charge
+      recur_input_charge recur_output_charge recur_total_charge );
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('recur_flat');
+}
+
+1;
diff --git a/FS/FS/part_pkg/subscription.pm b/FS/FS/part_pkg/subscription.pm
new file mode 100644 (file)
index 0000000..c9c472c
--- /dev/null
@@ -0,0 +1,108 @@
+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)',
+  '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 ) = @_;
+  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);
+
+  $self->option('recur_fee');
+}
+
+1;
diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm
new file mode 100644 (file)
index 0000000..7cf1779
--- /dev/null
@@ -0,0 +1,357 @@
+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::flat;
+#use FS::rate;
+#use FS::rate_prefix;
+
+@ISA = qw(FS::part_pkg::flat);
+
+$DEBUG = 1;
+
+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.',
+;
+
+#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',
+#;
+
+%info = (
+  'name' => 'VoIP rating by plan of CDR records in an internal (or external?) SQL table',
+  'fields' => {
+    'setup_fee'     => { 'name' => 'Setup fee for this package',
+                         'default' => 0,
+                       },
+    'recur_flat'     => { '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',
+                   },
+    'rating_method' => { 'name' => 'Region rating method',
+                         'type' => 'select',
+                         'select_options' => \%rating_method,
+                       },
+
+    'default_prefix' => { 'name'    => 'Default prefix optionally prepended to customer DID numbers when searching for CDR records',
+                          'default' => '+1',
+                        },
+
+    #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_flat unused_credit ratenum rating_method default_prefix )],
+  'weight' => 40,
+);
+
+sub calc_setup {
+  my($self, $cust_pkg ) = @_;
+  $self->option('setup_fee');
+}
+
+#false laziness w/voip_sqlradacct... resolve it if that one ever gets used again
+sub calc_recur {
+  my($self, $cust_pkg, $sdate, $details, $param ) = @_;
+
+  my $last_bill = $cust_pkg->last_bill;
+
+  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 = '';
+
+  foreach my $cust_svc (
+    grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc
+  ) {
+
+    foreach my $cdr (
+      $cust_svc->get_cdrs_for_update()  # $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 = 0;
+      my @call_details = ();
+      if ( $self->option('rating_method') eq 'prefix'
+           || ! $self->option('rating_method')
+         )
+      {
+
+        ###
+        # look up rate details based on called station id
+        # (or calling station id for toll free calls)
+        ###
+
+        my( $to_or_from, $number );
+        if ( $cdr->dst =~ /^(\+?1)?8([02-8])\1/ ) { #tollfree call
+          $to_or_from = 'from';
+          $number = $cdr->src;
+        } else { #regular call
+          $to_or_from = 'to';
+          $number = $cdr->dst;
+        }
+  
+        #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 =~ /^011(((\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 = '1';
+          $number =~ s/^1//;# if length($number) > 10;
+        }
+  
+        warn "rating call $to_or_from +$countrycode $number\n" if $DEBUG;
+        $pretty_destnum = "+$countrycode $number";
+  
+        #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($number, 0, $len) }
+            'npa'         => substr($number, 0, $len),
+          } ) and last;
+        }
+        $rate_prefix ||= qsearchs('rate_prefix', {
+          'countrycode' => $countrycode,
+          'npa'         => '',
+        });
+
+        #
+        die "Can't find rate for call $to_or_from +$countrycode $\numbern"
+          unless $rate_prefix;
+  
+        $regionnum = $rate_prefix->regionnum;
+        $rate_detail = qsearchs('rate_detail', {
+          'ratenum'        => $ratenum,
+          'dest_regionnum' => $regionnum,
+        } );
+  
+        $rate_region = $rate_prefix->rate_region;
+
+        warn "  found rate for regionnum $regionnum ".
+             "and rate detail $rate_detail\n"
+          if $DEBUG;
+
+      } elsif ( $self->option('rating_method') eq 'upstream' ) {
+
+        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);
+  
+          @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,
+          );
+
+        }
+
+      } else {
+        die "don't know how to rate CDRs using method: ".
+            $self->option('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 NULL,
+      # don't call downstream_csv or something on it...
+      # but DO emit a warning...
+      if ( ! $rate_detail && ! scalar(@call_details) ) {
+  
+        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 ) {
+    
+          $included_min{$regionnum} = $rate_detail->min_included
+            unless exists $included_min{$regionnum};
+      
+          my $granularity = $rate_detail->sec_granularity;
+          my $seconds = $cdr->billsec; # |ength($cdr->billsec) ? $cdr->billsec : $cdr->duration;
+          $seconds += $granularity - ( $seconds % $granularity )
+            if $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;
+      
+          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;
+          }
+      
+          # this is why we need regionnum/rate_region....
+          warn "  (rate region $rate_region)\n" if $DEBUG;
+      
+          @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
+            $granularity ? $minutes.'m' : $minutes.' call',
+            '$'.$charge,
+            $pretty_destnum,
+            $rate_region->regionname,
+          );
+
+        }
+    
+        warn "  adding details on charge to invoice: ".
+             join(' - ', @call_details )
+          if $DEBUG;
+    
+        push @$details, join(' - ', @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' => 'convergent' )
+          if $spool_cdr;
+  
+        my $error = $cdr->set_status_and_rated_price('done', $charge);
+        die $error if $error;
+  
+      }
+  
+    } # $cdr
+
+  } # $cust_svc
+
+  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) )
+
+  $self->option('recur_flat') + $charges;
+
+}
+
+sub is_free {
+  0;
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('recur_flat');
+}
+
+1;
+
diff --git a/FS/FS/part_pkg/voip_sqlradacct.pm b/FS/FS/part_pkg/voip_sqlradacct.pm
new file mode 100644 (file)
index 0000000..bf18003
--- /dev/null
@@ -0,0 +1,192 @@
+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 = (
+  'name' => 'VoIP rating by plan of CDR records in an SQL RADIUS radacct table',
+  'fields' => {
+    'setup_fee'     => { 'name' => 'Setup fee for this package',
+                         'default' => 0,
+                       },
+    'recur_flat'     => { '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_flat 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_flat') + $charges;
+
+}
+
+sub is_free {
+  0;
+}
+
+sub base_recur {
+  my($self, $cust_pkg) = @_;
+  $self->option('recur_flat');
+}
+
+1;
+
diff --git a/FS/FS/part_pkg_option.pm b/FS/FS/part_pkg_option.pm
new file mode 100644 (file)
index 0000000..c2f609e
--- /dev/null
@@ -0,0 +1,131 @@
+package FS::part_pkg_option;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+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
+
+=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_taxclass.pm b/FS/FS/part_pkg_taxclass.pm
new file mode 100644 (file)
index 0000000..fda200e
--- /dev/null
@@ -0,0 +1,158 @@
+package FS::part_pkg_taxclass;
+
+use strict;
+use vars qw( @ISA );
+use FS::UID qw(dbh);
+use FS::Record qw( qsearch qsearchs );
+
+@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
+
+=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
+
+# 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 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')
+  ;
+  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_pop_local.pm b/FS/FS/part_pop_local.pm
new file mode 100644 (file)
index 0000000..01c59df
--- /dev/null
@@ -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 (file)
index 0000000..87bc87c
--- /dev/null
@@ -0,0 +1,204 @@
+package FS::part_referral;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::agent;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::part_referral - Object methods for part_referral objects
+
+=head1 SYNOPSIS
+
+  use FS::part_referral;
+
+  $record = new FS::part_referral \%hash
+  $record = new FS::part_referral { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_referral represents a advertising source - where a customer heard
+of your services.  This can be used to track the effectiveness of a particular
+piece of advertising, for example.  FS::part_referral inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item refnum - primary key (assigned automatically for new referrals)
+
+=item referral - Text name of this advertising source
+
+=item disabled - Disabled flag, empty or 'Y'
+
+=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')
+    || $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 (file)
index 0000000..4fae457
--- /dev/null
@@ -0,0 +1,825 @@
+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') )
+         } (fields($svcdb), @fields)
+  ) {
+    my $part_svc_column = $self->part_svc_column($field);
+    my $previous = qsearchs('part_svc_column', {
+      'svcpart'    => $self->svcpart,
+      'columnname' => $field,
+    } );
+
+    my $flag = $self->getfield($svcdb.'__'.$field.'_flag');
+    #if ( uc($flag) =~ /^([DFMAX])$/ ) {
+    if ( uc($flag) =~ /^([A-Z])$/ ) { #part_svc_column will test it
+      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))
+      );
+      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') )
+           } (fields($svcdb),@fields)
+    ) {
+      my $part_svc_column = $new->part_svc_column($field);
+      my $previous = qsearchs('part_svc_column', {
+        'svcpart'    => $new->svcpart,
+        'columnname' => $field,
+      } );
+
+      my $flag = $new->getfield($svcdb.'__'.$field.'_flag');
+      #if ( uc($flag) =~ /^([DFMAX])$/ ) {
+      if ( uc($flag) =~ /^([A-Z])$/ ) { #part_svc_column will test it
+        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($new->getfield($svcdb.'__'.$field))
+        );
+        if ( $previous ) {
+          $error = $part_svc_column->replace($previous);
+        } else {
+          $error = $part_svc_column->insert;
+        }
+      } else {
+        $error = $previous ? $previous->delete : '';
+      }
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    }
+
+    # maintain export_svc records
+
+    if ( $exportnums ) {
+
+      #false laziness w/ edit/process/agent_type.cgi
+      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 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'};
+  
+  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 {
+                    if ( $param->{ $svcdb.'__'.$_.'_flag' } =~ /^[MA]$/ ) {
+                      $param->{ $svcdb.'__'.$_ } =
+                        delete( $param->{ $svcdb.'__'.$_.'_classnum' } );
+                    }
+                   if ( $param->{ $svcdb.'__'.$_.'_flag' } =~ /^S$/ ) {
+                      $param->{ $svcdb.'__'.$_} =
+                        ref($param->{ $svcdb.'__'.$_})
+                          ? join(',', @{$param->{ $svcdb.'__'.$_ }} )
+                          : $param->{ $svcdb.'__'.$_ };
+                   }
+                    ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' );
+                  }
+                  @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',
+                            [ '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 (file)
index 0000000..d2b8fd9
--- /dev/null
@@ -0,0 +1,120 @@
+package FS::part_svc_column;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( fields );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::part_svc_column - Object methods for part_svc_column objects
+
+=head1 SYNOPSIS
+
+  use FS::part_svc_column;
+
+  $record = new FS::part_svc_column \%hash
+  $record = new FS::part_svc_column { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_svc_column record represents a service definition column
+constraint.  FS::part_svc_column inherits from FS::Record.  The following
+fields are currently supported:
+
+=over 4
+
+=item columnnum - primary key (assigned automatcially for new records)
+
+=item svcpart - service definition (see L<FS::part_svc>)
+
+=item columnname - column name in part_svc.svcdb table
+
+=item columnvalue - default or fixed value for the column
+
+=item columnflag - null 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_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 (executable)
index 0000000..df04cc9
--- /dev/null
@@ -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 (executable)
index 0000000..ea973ba
--- /dev/null
@@ -0,0 +1,301 @@
+package FS::part_virtual_field;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch );
+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 (file)
index 0000000..5448b03
--- /dev/null
@@ -0,0 +1,538 @@
+package FS::pay_batch;
+
+use strict;
+use vars qw( @ISA );
+use Time::Local;
+use Text::CSV_XS;
+use FS::Record qw( dbh qsearch qsearchs );
+use FS::cust_pay;
+
+@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();
+}
+
+=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 $filetype;      # CSV, Fixed80, Fixed264
+  my @fields;
+  my $formatre;      # for Fixed.+
+  my @values;
+  my $begin_condition;
+  my $end_condition;
+  my $end_hook;
+  my $hook;
+  my $approved_condition;
+  my $declined_condition;
+
+  if ( $format eq 'csv-td_canada_trust-merchant_pc_batch' ) {
+
+    $filetype = "CSV";
+
+    @fields = (
+      'paybatchnum', # Reference#:  Invoice number of the transaction
+      'paid',        # Amount:  Amount of the transaction.  Dollars and cents
+                     #          with no decimal entered.
+      '',            # Card Type:  0 - MCrd, 1 - Visa, 2 - AMEX, 3 - Discover,
+                     #             4 - Insignia, 5 - Diners/EnRoute, 6 - JCB
+      '_date',       # Transaction Date:  Date the Transaction was processed
+      'time',        # Transaction Time:  Time the transaction was processed
+      'payinfo',     # Card Number:  Card number for the transaction
+      '',            # Expiry Date:  Expiry date of the card
+      '',            # Auth#:  Authorization number entered for force post
+                     #         transaction
+      'type',        # Transaction Type:  0 - purchase, 40 - refund,
+                     #                    20 - force post
+      'result',      # Processing Result: 3 - Approval,
+                     #                    4 - Declined/Amount over limit,
+                     #                    5 - Invalid/Expired/stolen card,
+                     #                    6 - Comm Error
+      '',            # Terminal ID: Terminal ID used to process the transaction
+    );
+
+    $end_condition = sub {
+      my $hash = shift;
+      $hash->{'type'} eq '0BC';
+    };
+
+    $end_hook = sub {
+      my( $hash, $total) = @_;
+      $total = sprintf("%.2f", $total);
+      my $batch_total = sprintf("%.2f", $hash->{'paybatchnum'} / 100 );
+      return "Our total $total does not match bank total $batch_total!"
+        if $total != $batch_total;
+      '';
+    };
+
+    $hook = sub {
+      my $hash = shift;
+      $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 );
+      $hash->{'_date'} = timelocal( substr($hash->{'time'},  4, 2),
+                                    substr($hash->{'time'},  2, 2),
+                                    substr($hash->{'time'},  0, 2),
+                                    substr($hash->{'_date'}, 6, 2),
+                                    substr($hash->{'_date'}, 4, 2)-1,
+                                    substr($hash->{'_date'}, 0, 4)-1900, );
+    };
+
+    $approved_condition = sub {
+      my $hash = shift;
+      $hash->{'type'} eq '0' && $hash->{'result'} == 3;
+    };
+
+    $declined_condition = sub {
+      my $hash = shift;
+      $hash->{'type'} eq '0' && (    $hash->{'result'} == 4
+                                  || $hash->{'result'} == 5 );
+    };
+
+
+  }elsif ( $format eq 'csv-chase_canada-E-xactBatch' ) {
+
+    $filetype = "CSV";
+
+    @fields = (
+      '',            # Internal(bank) id of the transaction
+      '',            # Transaction Type:  00 - purchase,      01 - preauth,
+                     #                    02 - completion,    03 - forcepost,
+                     #                    04 - refund,        05 - auth,
+                     #                    06 - purchase corr, 07 - refund corr,
+                     #                    08 - void           09 - void return
+      '',            # gateway used to process this transaction
+      'paid',        # Amount:  Amount of the transaction.  Dollars and cents
+                     #          with decimal entered.
+      'auth',        # Auth#:  Authorization number (if approved)
+      'payinfo',     # Card Number:  Card number for the transaction
+      '',            # Expiry Date:  Expiry date of the card
+      '',            # Cardholder Name
+      'bankcode',    # Bank response code (3 alphanumeric)
+      'bankmess',    # Bank response message
+      'etgcode',     # ETG response code (2 alphanumeric)
+      'etgmess',     # ETG response message
+      '',            # Returned customer number for the transaction
+      'paybatchnum', # Reference#:  paybatch number of the transaction
+      '',            # Reference#:  Invoice number of the transaction
+      'result',      # Processing Result: Approved of Declined
+    );
+
+    $end_condition = sub {
+      '';
+    };
+
+    $hook = sub {
+      my $hash = shift;
+      my $cpb = shift;
+      $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'}); #hmmmm
+      $hash->{'_date'} = time;  # got a better one?
+      $hash->{'payinfo'} = $cpb->{'payinfo'}
+        if( substr($hash->{'payinfo'}, -4) eq substr($cpb->{'payinfo'}, -4) );
+    };
+
+    $approved_condition = sub {
+      my $hash = shift;
+      $hash->{'etgcode'} eq '00' && $hash->{'result'} eq "Approved";
+    };
+
+    $declined_condition = sub {
+      my $hash = shift;
+      $hash->{'etgcode'} ne '00' # internal processing error
+        || ( $hash->{'result'} eq "Declined" );
+    };
+
+
+  }elsif ( $format eq 'PAP' ) {
+
+    $filetype = "Fixed264";
+
+    @fields = (
+      'recordtype',  # We are interested in the 'D' or debit records
+      'batchnum',    # Record#:  batch number we used when sending the file
+      'datacenter',  # Where in the bowels of the bank the data was processed
+      'paid',        # Amount:  Amount of the transaction.  Dollars and cents
+                     #          with no decimal entered.
+      '_date',       # Transaction Date:  Date the Transaction was processed
+      'bank',        # Routing information
+      'payinfo',     # Account number for the transaction
+      'paybatchnum', # Reference#:  Invoice number of the transaction
+    );
+
+    $formatre = '^(.).{19}(.{4})(.{3})(.{10})(.{6})(.{9})(.{12}).{110}(.{19}).{71}$'; 
+
+    $end_condition = sub {
+      my $hash = shift;
+      $hash->{'recordtype'} eq 'W';
+    };
+
+    $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;
+      '';
+    };
+
+    $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_condition = sub {
+      1;
+    };
+
+    $declined_condition = sub {
+      0;
+    };
+
+  }elsif ( $format eq 'ach-spiritone' ) {
+
+    $filetype = "CSV";
+
+    @fields = (
+      '',            # Name
+      'paybatchnum', # ID: Number of the transaction
+      'aba',         # ABA Number for the transaction
+      'payinfo',     # Bank Account Number for the transaction
+      '',            # Transaction Type:  27 - debit
+      'paid',        # Amount:  Amount of the transaction.  Dollars and cents
+                     #          with decimal entered.
+      '',            # Default Transaction Type
+      '',            # Default Amount:  Dollars and cents with decimal entered.
+    );
+
+    $end_condition = sub {
+      '';
+    };
+
+    $hook = sub {
+      my $hash = shift;
+      $hash->{'_date'} = time;  # got a better one?
+      $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'aba'};
+    };
+
+    $approved_condition = sub {
+      1;
+    };
+
+    $declined_condition = sub {
+      0;
+    };
+
+
+  } else {
+    return "Unknown format $format";
+  }
+
+  my $csv = new Text::CSV_XS;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $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;
+  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();
+      };
+      @values = $csv->fields();
+    }elsif ($filetype eq "Fixed80" || $filetype eq "Fixed264"){
+      @values = $line =~ /$formatre/;
+      unless (@values) {
+        $dbh->rollback if $oldAutoCommit;
+        return "can't parse: ". $line;
+      };
+    }else{
+      $dbh->rollback if $oldAutoCommit;
+      return "Unknown file type $filetype";
+    }
+
+    my %hash;
+    foreach my $field ( @fields ) {
+      my $value = shift @values;
+      next unless $field;
+      $hash{$field} = $value;
+    }
+
+    if ( &{$end_condition}(\%hash) ) {
+      my $error = &{$end_hook}(\%hash, $total);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+      last;
+    }
+
+    my $cust_pay_batch =
+      qsearchs('cust_pay_batch', { 'paybatchnum' => $hash{'paybatchnum'}+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;
+  '';
+
+}
+
+=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/payby.pm b/FS/FS/payby.pm
new file mode 100644 (file)
index 0000000..6684c95
--- /dev/null
@@ -0,0 +1,185 @@
+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)',
+  },
+  'DCRD' => {
+    tinyname  => 'card',
+    shortname => 'Credit card',
+    longname  => 'Credit card (on-demand)',
+    cust_pay  => 'CARD', #this is a customer type only, payments are CARD...
+  },
+  'CHEK' => {
+    tinyname  => 'check',
+    shortname => 'Electronic check',
+    longname  => 'Electronic check (automatic)',
+  },
+  'DCHK' => {
+    tinyname  => 'check',
+    shortname => 'Electronic check',
+    longname  => 'Electronic check (on-demand)',
+    cust_pay  => 'CHEK', #this is a customer type only, payments are CHEK...
+  },
+  'LECB' => {
+    tinyname  => 'phone bill',
+    shortname => 'Phone bill billing',
+    longname  => 'Phone bill billing',
+  },
+  'BILL' => {
+    tinyname  => 'billing',
+    shortname => 'Billing',
+    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 eq 'cust_pay_batch' || $table eq 'cust_refund';
+  return 0 if exists( $hash{$payby}->{$table} );
+
+  return 1;
+}
+
+sub payby2longname {
+  my $self = shift;
+  map { $_ => $hash{$_}->{longname} } $self->payby;
+}
+
+sub shortname {
+  my( $self, $payby ) = @_;
+  $hash{$payby}->{shortname};
+}
+
+sub longname {
+  my( $self, $payby ) = @_;
+  $hash{$payby}->{longname};
+}
+
+%payby2bop = (
+  'CARD' => 'CC',
+  'CHEK' => 'ECHECK',
+);
+
+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 (file)
index 0000000..15c4e39
--- /dev/null
@@ -0,0 +1,249 @@
+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)
+Encryption - In the Future (Pull from Record.pm)
+Bad Card Stuff - In the Future (Integrate Banned Pay)
+Currency - In the Future
+
+=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;
+}
+
+=cut
+
+sub _mask_payinfo {
+  my $self = shift;
+
+=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' ) {
+    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 {
+    my $error = $self->ut_textn('payinfo');
+    return $error if $error;
+  }
+}
+
+=head1 BUGS
+
+Have to add the future items...
+
+=head1 SEE ALSO
+
+L<FS::payby>, L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/payment_gateway.pm b/FS/FS/payment_gateway.pm
new file mode 100644 (file)
index 0000000..35b4f08
--- /dev/null
@@ -0,0 +1,200 @@
+package FS::payment_gateway;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::option_Common;
+use FS::agent_payment_gateway;
+
+@ISA = qw( FS::option_Common );
+
+=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_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_textn('gateway_username')
+    || $self->ut_anything('gateway_password')
+    || $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');
+  }
+
+  $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;
+  '';
+
+}
+
+=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 (file)
index 0000000..0576022
--- /dev/null
@@ -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/pkg_class.pm b/FS/FS/pkg_class.pm
new file mode 100644 (file)
index 0000000..bab6e5e
--- /dev/null
@@ -0,0 +1,113 @@
+package FS::pkg_class;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch );
+use FS::part_pkg;
+
+@ISA = qw( FS::Record );
+
+=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 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'; }
+
+=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.
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  return "Can't delete an pkg_class with part_pkg records!"
+    if qsearch( 'part_pkg', { 'classnum' => $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 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;
+
+  $self->ut_numbern('classnum')
+  or $self->ut_text('classname')
+  or $self->SUPER::check;
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::part_pkg>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/pkg_referral.pm b/FS/FS/pkg_referral.pm
new file mode 100644 (file)
index 0000000..333c2bf
--- /dev/null
@@ -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 (file)
index 0000000..9f3a4a1
--- /dev/null
@@ -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 (file)
index 0000000..c26ca85
--- /dev/null
@@ -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 (file)
index 0000000..302ba37
--- /dev/null
@@ -0,0 +1,202 @@
+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 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, $hashref ) = @_;
+
+  my @codeset = ();
+  push @codeset, ( 'A'..'Z' ) if $type =~ /alpha/;
+  push @codeset, ( '1'..'9' ) if $type =~ /numeric/;
+
+  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)], (0..7) ) );
+
+    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/queue.pm b/FS/FS/queue.pm
new file mode 100644 (file)
index 0000000..5f8bf11
--- /dev/null
@@ -0,0 +1,465 @@
+package FS::queue;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK $DEBUG $conf $jobnums);
+use Exporter;
+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;
+
+@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
+
+=item statustext - freeform text status message
+
+=item _date - UNIX timestamp
+
+=item svcnum - optional link to service (see L<FS::cust_svc>)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new job.  To add the 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 = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  foreach my $arg ( @_ ) {
+    my $queue_arg = new FS::queue_arg ( {
+      'jobnum' => $self->jobnum,
+      'arg'    => $arg,
+    } );
+    $error = $queue_arg->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  if ( $jobnums ) {
+    warn "jobnums global is active: $jobnums\n" if $DEBUG;
+    push @$jobnums, $self->jobnum;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
+=item delete
+
+Delete this record from the database.  Any corresponding queue_arg records are
+deleted as well
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
+  push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } );
+
+  my $error = $self->SUPER::delete;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  foreach my $del ( @del ) {
+    $error = $del->delete;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid job.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+  my $error =
+    $self->ut_numbern('jobnum')
+    || $self->ut_anything('job')
+    || $self->ut_numbern('_date')
+    || $self->ut_enum('status',['', qw( new locked failed )])
+    || $self->ut_anything('statustext')
+    || $self->ut_numbern('svcnum')
+  ;
+  return $error if $error;
+
+  $error = $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum');
+  $self->svcnum('') if $error;
+
+  $self->status('new') unless $self->status;
+  $self->_date(time) unless $self->_date;
+
+  $self->SUPER::check;
+}
+
+=item args
+
+Returns a list of the arguments associated with this job.
+
+=cut
+
+sub args {
+  my $self = shift;
+  map $_->arg, qsearch( 'queue_arg',
+                        { 'jobnum' => $self->jobnum },
+                        '',
+                        'ORDER BY argnum'
+                      );
+}
+
+=item cust_svc
+
+Returns the FS::cust_svc object associated with this job, if any.
+
+=cut
+
+sub cust_svc {
+  my $self = shift;
+  qsearchs('cust_svc', { 'svcnum' => $self->svcnum } );
+}
+
+=item queue_depend
+
+Returns the FS::queue_depend objects associated with this job, if any.
+(Dependancies that must complete before this job can be run).
+
+=cut
+
+sub queue_depend {
+  my $self = shift;
+  qsearch('queue_depend', { 'jobnum' => $self->jobnum } );
+}
+
+=item depend_insert OTHER_JOBNUM
+
+Inserts a dependancy for this job - it will not be run until the other job
+specified completes.  If there is an error, returns the error, otherwise
+returns false.
+
+When using job dependancies, you should wrap the insertion of all relevant jobs
+in a database transaction.  
+
+=cut
+
+sub depend_insert {
+  my($self, $other_jobnum) = @_;
+  my $queue_depend = new FS::queue_depend ( {
+    'jobnum'        => $self->jobnum,
+    'depend_jobnum' => $other_jobnum,
+  } );
+  $queue_depend->insert;
+}
+
+=item queue_depended
+
+Returns the FS::queue_depend objects that associate other jobs with this job,
+if any.  (The jobs that are waiting for this job to complete before they can
+run).
+
+=cut
+
+sub queue_depended {
+  my $self = shift;
+  qsearch('queue_depend', { 'depend_jobnum' => $self->jobnum } );
+}
+
+=item depended_delete
+
+Deletes the other queued jobs (FS::queue objects) that are waiting for this
+job, if any.  If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub depended_delete {
+  my $self = shift;
+  my $error;
+  foreach my $job (
+    map { qsearchs('queue', { 'jobnum' => $_->jobnum } ) } $self->queue_depended
+  ) {
+    $error = $job->depended_delete;
+    return $error if $error;
+    $error = $job->delete;
+    return $error if $error
+  }
+}
+
+=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 (file)
index 0000000..c96ff12
--- /dev/null
@@ -0,0 +1,117 @@
+package FS::queue_arg;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::queue_arg - Object methods for queue_arg records
+
+=head1 SYNOPSIS
+
+  use FS::queue_arg;
+
+  $record = new FS::queue_arg \%hash;
+  $record = new FS::queue_arg { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::queue_arg object represents job argument.  FS::queue_arg inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item argnum - primary key
+
+=item jobnum - see L<FS::queue>
+
+=item arg - argument
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new argument.  To add the 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_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 (file)
index 0000000..99a22c5
--- /dev/null
@@ -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 (file)
index 0000000..506b325
--- /dev/null
@@ -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 (file)
index 0000000..9bba057
--- /dev/null
@@ -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 (file)
index 0000000..c50ca04
--- /dev/null
@@ -0,0 +1,379 @@
+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
+
+Returns the rate detail (see L<FS::rate_detail>) for this rate to the
+specificed destination.
+
+=cut
+
+sub dest_detail {
+  my $self = shift;
+  my $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 )
+      };
+
+    } else {
+
+      new FS::rate_detail {
+        'dest_regionnum'  => $regionnum,
+        'min_included'    => 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;
+    $error = $rate->replace( $old,
+                             'rate_detail' => \@rate_detail,
+                             'job'         => $job,
+                           );
+  } 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 (file)
index 0000000..ad41b40
--- /dev/null
@@ -0,0 +1,202 @@
+package FS::rate_detail;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+use FS::rate;
+use FS::rate_region;
+
+@ISA = qw(FS::Record);
+
+=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
+
+=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')
+  ;
+  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;
+}
+
+
+=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 (file)
index 0000000..42b004f
--- /dev/null
@@ -0,0 +1,139 @@
+package FS::rate_prefix;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+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 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 (file)
index 0000000..65dfd2a
--- /dev/null
@@ -0,0 +1,313 @@
+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, 3 ) if length($npa) > 3;
+    } else {
+      $out .= $rate_prefix->npa;
+    }
+    $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 (file)
index 0000000..5311ec5
--- /dev/null
@@ -0,0 +1,184 @@
+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 } );
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+#
+#
+
+sub _upgrade_data {  # class method
+  my ($self, %opts) = @_;
+  my $dbh = dbh;
+
+  warn "$me upgrading $self\n" if $DEBUG;
+
+  my $column = dbdef->table($self->table)->column('reason');
+  unless ($column->type eq 'text') { # assume history matches main table
+
+    # ideally this would be supported in DBIx-DBSchema and friends
+    warn "$me Shifting reason column to type 'text'\n" if $DEBUG;
+    foreach my $table ( $self->table, 'h_'. $self->table ) {
+      my @sql = ();
+
+      $column = dbdef->table($self->table)->column('reason');
+      my $columndef = $column->line($dbh);
+      $columndef =~ s/varchar\(\d+\)/text/i;
+
+      if ( $dbh->{Driver}->{Name} eq 'Pg' ) {
+
+        my $notnull = $columndef =~ s/not null//i;
+        push @sql,"ALTER TABLE $table RENAME reason TO freeside_upgrade_reason";
+        push @sql,"ALTER TABLE $table ADD $columndef";
+        push @sql,"UPDATE $table SET reason = freeside_upgrade_reason";
+        push @sql,"ALTER TABLE $table ALTER reason SET NOT NULL"
+          if $notnull;
+        push @sql,"ALTER TABLE $table DROP freeside_upgrade_reason";
+
+      } elsif ( $dbh->{Driver}->{Name} =~ /^mysql/i ){
+
+        #crap, this isn't working
+        #push @sql,"ALTER TABLE $table MODIFY reason ". $column->line($dbh);
+        warn "WARNING: reason table upgrade not yet supported for mysql, sorry";
+
+      } else {
+        die "watchu talkin' 'bout, Willis? (unsupported database type)";
+      }
+
+      foreach (@sql) {
+        my $sth = $dbh->prepare($_) or die $dbh->errstr;
+        $sth->execute or die $sth->errstr;
+      }
+    }
+  }
+
+ '';
+
+}
+=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 (file)
index 0000000..482ea34
--- /dev/null
@@ -0,0 +1,211 @@
+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;
+#                                      # or clause for 1.7.x
+    $conf->set($_, $object->typenum)
+      or die "failed setting config";
+  }
+
+  '';
+
+}
+
+# _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 (file)
index 0000000..f48ccf0
--- /dev/null
@@ -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 (file)
index 0000000..837b755
--- /dev/null
@@ -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 (file)
index 0000000..cf5dc49
--- /dev/null
@@ -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 (executable)
index 0000000..88ba990
--- /dev/null
@@ -0,0 +1,140 @@
+package FS::router;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearchs qsearch );
+use FS::addr_block;
+
+@ISA = qw( FS::Record );
+
+=head1 NAME
+
+FS::router - Object methods for router records
+
+=head1 SYNOPSIS
+
+  use FS::router;
+
+  $record = new FS::router \%hash;
+  $record = new FS::router { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::router record describes a broadband router, such as a DSLAM or a wireless
+ access point.  FS::router inherits from FS::Record.  The following 
+fields are currently supported:
+
+=over 4
+
+=item routernum - primary key
+
+=item routername - descriptive name for the router
+
+=item svcnum - svcnum of the owning FS::svc_broadband, if appropriate
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Create a new record.  To add the record to the database, see "insert".
+
+=cut
+
+sub table { 'router'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Deletes this record from the database.  If there is an error, returns the
+error, otherwise returns false.
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid record.  If there is an error,
+returns the error, otherwise returns false.  Called by the insert and replace
+methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error =
+    $self->ut_numbern('routernum')
+    || $self->ut_text('routername');
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item addr_block
+
+Returns a list of FS::addr_block objects (address blocks) associated
+with this object.
+
+=cut
+
+sub addr_block {
+  my $self = shift;
+  return qsearch('addr_block', { routernum => $self->routernum });
+}
+
+=item part_svc_router
+
+Returns a list of FS::part_svc_router objects associated with this 
+object.  This is unlikely to be useful for any purpose other than retrieving 
+the associated FS::part_svc objects.  See below.
+
+=cut
+
+sub part_svc_router {
+  my $self = shift;
+  return qsearch('part_svc_router', { routernum => $self->routernum });
+}
+
+=item part_svc
+
+Returns a list of FS::part_svc objects associated with this object.
+
+=cut
+
+sub part_svc {
+  my $self = shift;
+  return map { qsearchs('part_svc', { svcpart => $_->svcpart }) }
+      $self->part_svc_router;
+}
+
+=back
+
+=head1 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 (file)
index 0000000..615c8ae
--- /dev/null
@@ -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_Common.pm b/FS/FS/svc_Common.pm
new file mode 100644 (file)
index 0000000..787acee
--- /dev/null
@@ -0,0 +1,815 @@
+package FS::svc_Common;
+
+use strict;
+use vars qw( @ISA $noexport_hack $DEBUG $me );
+use Carp qw( cluck carp croak ); #specify cluck have to specify them all..
+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;
+
+=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.
+
+=cut
+
+sub search_sql_field {
+  my( $class, $field, $string ) = @_;
+  my $table = $class->table;
+  my $q_string = dbh->quote($string);
+  "$table.$field = $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;
+}
+
+=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);
+  my $error;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  $error = $self->check;
+  return $error if $error;
+
+  my $svcnum = $self->svcnum;
+  my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : '';
+  #unless ( $svcnum ) {
+  if ( !$svcnum or !$cust_svc ) {
+    $cust_svc = new FS::cust_svc ( {
+      #hua?# 'svcnum'  => $svcnum,
+      'svcnum'  => $self->svcnum,
+      'pkgnum'  => $self->pkgnum,
+      'svcpart' => $self->svcpart,
+    } );
+    $error = $cust_svc->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+    $svcnum = $self->svcnum($cust_svc->svcnum);
+  } else {
+    #$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
+    unless ( $cust_svc ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "no cust_svc record found for svcnum ". $self->svcnum;
+    }
+    $self->pkgnum($cust_svc->pkgnum);
+    $self->svcpart($cust_svc->svcpart);
+  }
+
+  $error = $self->set_auto_inventory;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $error = $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;
+
+  '';
+}
+
+=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
+
+Replaces OLD_RECORD with this one.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub replace {
+  my ($new, $old) = (shift, shift);
+  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;
+
+  # We absolutely have to have an old vs. new record to make this work.
+  $old = $new->replace_old unless defined($old);
+
+  my $error = $new->set_auto_inventory;
+  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 "Unkonwn 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 set_auto_inventory
+
+Sets any fields which auto-populate from inventory (see L<FS::part_svc>).
+If there is an error, returns the error, otherwise returns false.
+
+=cut
+
+sub set_auto_inventory {
+  my $self = 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);
+    if ( $part_svc_column->columnflag eq 'A' && $self->$field() eq '' ) {
+
+      my $classnum = $part_svc_column->columnvalue;
+      my $inventory_item = qsearchs({
+        'table'     => 'inventory_item',
+        'hashref'   => { 'classnum' => $classnum, 
+                         'svcnum'   => '',
+                       },
+        'extra_sql' => '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
+      }
+
+      $inventory_item->svcnum( $self->svcnum );
+      my $ierror = $inventory_item->replace();
+      if ( $ierror ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "Error provisioning inventory: $ierror";
+        
+      }
+
+      $self->setfield( $field, $inventory_item->item );
+
+    }
+  }
+
+ $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 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(@_);
+}
+
+=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_External_Common.pm b/FS/FS/svc_External_Common.pm
new file mode 100644 (file)
index 0000000..a5805aa
--- /dev/null
@@ -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 (file)
index 0000000..4501baf
--- /dev/null
@@ -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 (file)
index 0000000..4343df5
--- /dev/null
@@ -0,0 +1,2664 @@
+package FS::svc_acct;
+
+use strict;
+use vars qw( @ISA $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
+             $password_noampersand $password_noexclamation
+             $warning_template $warning_from $warning_subject $warning_mimetype
+             $warning_cc
+             $smtpmachine
+             $radius_password $radius_ip
+             $dirhash
+             @saltset @pw_set );
+use Carp;
+use Fcntl qw(:flock);
+use Date::Format;
+use Crypt::PasswdMD5 1.2;
+use Data::Dumper;
+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::svc_Common;
+use FS::cust_svc;
+use FS::part_svc;
+use FS::svc_acct_pop;
+use FS::cust_main_invoice;
+use FS::svc_domain;
+use FS::raddb;
+use FS::queue;
+use FS::radius_usergroup;
+use FS::export_svc;
+use FS::part_export;
+use FS::svc_forward;
+use FS::svc_www;
+use FS::cdr;
+
+@ISA = qw( FS::svc_Common );
+
+$DEBUG = 0;
+$me = '[FS::svc_acct]';
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::svc_acct'} = sub { 
+  $conf = new FS::Conf;
+  $dir_prefix = $conf->config('home');
+  @shells = $conf->config('shells');
+  $usernamemin = $conf->config('usernamemin') || 2;
+  $usernamemax = $conf->config('usernamemax');
+  $passwordmin = $conf->config('passwordmin') || 6;
+  $passwordmax = $conf->config('passwordmax') || 8;
+  $username_letter = $conf->exists('username-letter');
+  $username_letterfirst = $conf->exists('username-letterfirst');
+  $username_noperiod = $conf->exists('username-noperiod');
+  $username_nounderscore = $conf->exists('username-nounderscore');
+  $username_nodash = $conf->exists('username-nodash');
+  $username_uppercase = $conf->exists('username-uppercase');
+  $username_ampersand = $conf->exists('username-ampersand');
+  $username_percent = $conf->exists('username-percent');
+  $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 - (unimplementd)
+
+=item slipip - IP address
+
+=item seconds - 
+
+=item upbytes - 
+
+=item downbytes - 
+
+=item totalbytes - 
+
+=item domsvc - svcnum from svc_domain
+
+=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_label => 'UID (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,
+                       },
+        'quota'     => { 
+                         label => 'Quota',
+                         type => 'text',
+                         disable_inventory => 1,
+                         disable_select => 1,
+                       },
+        '_password' => 'Password',
+        'gid'       => {
+                         label     => 'GID',
+                        def_label => 'GID (when blank, defaults to UID)',
+                        type      => 'text',
+                      },
+        'shell'     => {
+                         #desc =>'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file, set to blank for no shell tracking)',
+                        label    => 'Shell',
+                         def_label=> 'Shell (set to blank for no shell tracking)',
+                         type     =>'select',
+                         select_list => [ $conf->config('shells') ],
+                         disable_inventory => 1,
+                         disable_select => 1,
+                       },
+        'finger'    => 'Real name (GECOS)',
+        'domsvc'    => {
+                         label     => 'Domain',
+                         #def_label => 'svcnum from svc_domain',
+                         type      => 'select',
+                         select_table => 'svc_domain',
+                         select_key   => 'svcnum',
+                         select_label => 'domain',
+                         disable_inventory => 1,
+
+                       },
+        '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,
+                       },
+        'upbytes'   => { label => 'Upload',
+                         type  => 'text',
+                         disable_inventory => 1,
+                         disable_select => 1,
+                         'format' => \&FS::UI::bytecount::display_bytecount,
+                         'parse' => \&FS::UI::bytecount::parse_bytecount,
+                       },
+        'downbytes' => { label => 'Download',
+                         type  => 'text',
+                         disable_inventory => 1,
+                         disable_select => 1,
+                         'format' => \&FS::UI::bytecount::display_bytecount,
+                         'parse' => \&FS::UI::bytecount::parse_bytecount,
+                       },
+        '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,
+                       },
+        'seconds_threshold'   => { label => 'Seconds threshold',
+                                   type  => 'text',
+                                   disable_inventory => 1,
+                                   disable_select => 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,
+                                 },
+        '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,
+                                 },
+        '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,
+                                 },
+        'last_login'=>           {
+                                   label     => 'Last login',
+                                   type      => 'disabled',
+                                 },
+        'last_logout'=>          {
+                                   label     => 'Last logout',
+                                   type      => 'disabled',
+                                 },
+    },
+  };
+}
+
+sub table { 'svc_acct'; }
+
+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(@_);
+}
+
+=cut
+
+=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 $error = $self->check;
+  return $error if $error;
+
+  if ( $self->svcnum && qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) ) {
+    my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum});
+    unless ( $cust_svc ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "no cust_svc record found for svcnum ". $self->svcnum;
+    }
+    $self->pkgnum($cust_svc->pkgnum);
+    $self->svcpart($cust_svc->svcpart);
+  }
+
+  $error = $self->_check_duplicate;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  my @jobnums;
+  $error = $self->SUPER::insert(
+    'jobnums'       => \@jobnums,
+    'child_objects' => $self->child_objects,
+    %options,
+  );
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  if ( $self->usergroup ) {
+    foreach my $groupname ( @{$self->usergroup} ) {
+      my $radius_usergroup = new FS::radius_usergroup ( {
+        svcnum    => $self->svcnum,
+        groupname => $groupname,
+      } );
+      my $error = $radius_usergroup->insert;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      }
+    }
+  }
+
+  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
+}
+
+=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, $old ) = ( shift, shift );
+  my $error;
+  warn "$me replacing $old with $new\n" if $DEBUG;
+
+  # We absolutely have to have an old vs. new record to make this work.
+  if (!defined($old)) {
+    $old = qsearchs( 'svc_acct', { 'svcnum' => $new->svcnum } );
+  }
+
+  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";
+      }
+    }
+
+  }
+
+  if ( $old->username ne $new->username || $old->domsvc != $new->domsvc ) {
+    $new->svcpart( $new->cust_svc->svcpart ) unless $new->svcpart;
+    $error = $new->_check_duplicate;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $error = $new->SUPER::replace($old, @_);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error if $error;
+  }
+
+  if ( $new->username ne $old->username && ! $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_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 ) ]
+                               )
+  ;
+  return $error if $error;
+
+  my $ulen = $usernamemax || $self->dbdef_table->column('username')->length;
+  if ( $username_uppercase ) {
+    $recref->{username} =~ /^([a-z0-9_\-\.\&\%]{$usernamemin,$ulen})$/i
+      or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
+    $recref->{username} = $1;
+  } else {
+    $recref->{username} =~ /^([a-z0-9_\-\.\&\%]{$usernamemin,$ulen})$/
+      or return gettext('illegal_username'). " ($usernamemin-$ulen): ". $recref->{username};
+    $recref->{username} = $1;
+  }
+
+  if ( $username_letterfirst ) {
+    $recref->{username} =~ /^[a-z]/ or return gettext('illegal_username');
+  } elsif ( $username_letter ) {
+    $recref->{username} =~ /[a-z]/ or return gettext('illegal_username');
+  }
+  if ( $username_noperiod ) {
+    $recref->{username} =~ /\./ and return gettext('illegal_username');
+  }
+  if ( $username_nounderscore ) {
+    $recref->{username} =~ /_/ and return gettext('illegal_username');
+  }
+  if ( $username_nodash ) {
+    $recref->{username} =~ /\-/ and return gettext('illegal_username');
+  }
+  unless ( $username_ampersand ) {
+    $recref->{username} =~ /\&/ and return gettext('illegal_username');
+  }
+  unless ( $username_percent ) {
+    $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);
+
+  $recref->{quota} =~ /^(\w*)$/ or return "Illegal quota";
+  $recref->{quota} = $1;
+
+  unless ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) {
+    if ( $recref->{slipip} eq '' ) {
+      $recref->{slipip} = '';
+    } elsif ( $recref->{slipip} eq '0e0' ) {
+      $recref->{slipip} = '0e0';
+    } else {
+      $recref->{slipip} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/
+        or return "Illegal slipip: ". $self->slipip;
+      $recref->{slipip} = $1;
+    }
+
+  }
+
+  #arbitrary RADIUS stuff; allow ut_textn for now
+  foreach ( grep /^radius_/, fields('svc_acct') ) {
+    $self->ut_textn($_);
+  }
+
+  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} = $1.$2;
+
+    } else {
+      return 'Illegal (crypt-encoded) password';
+    }
+
+  } elsif ( $recref->{_password_encoding} eq 'plain' ) { 
+
+    #generate a password if it is blank
+    $recref->{_password} = join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) )
+      unless length( $recref->{_password} );
+
+    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 {
+
+    #carp "warning: _password_encoding unspecified\n";
+
+    #generate a password if it is blank
+    unless ( length( $recref->{_password} ) ) {
+
+      $recref->{_password} =
+        join('',map($pw_set[ int(rand $#pw_set) ], (0..7) ) );
+      $recref->{_password_encoding} = 'plain';
+
+    } else {
+  
+      #if ( $recref->{_password} =~ /^((\*SUSPENDED\* )?)([^\t\n]{4,16})$/ ) {
+      if ( $recref->{_password} =~ /^((\*SUSPENDED\* |!!?)?)([^\t\n]{$passwordmin,$passwordmax})$/ ) {
+        $recref->{_password} = $1.$3;
+        $recref->{_password_encoding} = 'plain';
+      } elsif ( $recref->{_password} =~
+                  /^((\*SUSPENDED\* |!!?)?)([\w\.\/\$\;\+]{13,64})$/
+              ) {
+        $recref->{_password} = $1.$3;
+        $recref->{_password_encoding} = 'crypt';
+      } elsif ( $recref->{_password} eq '*' ) {
+        $recref->{_password} = '*';
+        $recref->{_password_encoding} = 'crypt';
+      } elsif ( $recref->{_password} eq '!' ) {
+        $recref->{_password_encoding} = 'crypt';
+        $recref->{_password} = '!';
+      } elsif ( $recref->{_password} eq '!!' ) {
+        $recref->{_password} = '!!';
+        $recref->{_password_encoding} = 'crypt';
+      } else {
+        #return "Illegal password";
+        return gettext('illegal_password'). " $passwordmin-$passwordmax ".
+               FS::Msgcat::_gettext('illegal_password_characters').
+               ": ". $recref->{_password};
+      }
+
+    }
+
+  }
+
+  $self->SUPER::check;
+
+}
+
+=item _check_system
+
+Internal function to check the username against the list of system usernames
+from the I<system_usernames> configuration value.  Returns true if the username
+is listed on the system username list.
+
+=cut
+
+sub _check_system {
+  my $self = shift;
+  scalar( grep { $self->username eq $_ || $self->email eq $_ }
+               $conf->config('system_usernames')
+        );
+}
+
+=item _check_duplicate
+
+Internal function to check for duplicates usernames, username@domain pairs and
+uids.
+
+If the I<global_unique-username> configuration value is set to B<username> or
+B<username@domain>, enforces global username or username@domain uniqueness.
+
+In all cases, check for duplicate uids and usernames or username@domain pairs
+per export and with identical I<svcpart> values.
+
+=cut
+
+sub _check_duplicate {
+  my $self = shift;
+
+  my $global_unique = $conf->config('global_unique-username') || 'none';
+  return '' if $global_unique eq 'disabled';
+
+  warn "$me locking svc_acct table for duplicate search" if $DEBUG;
+  if ( driver_name =~ /^Pg/i ) {
+    dbh->do("LOCK TABLE svc_acct IN SHARE ROW EXCLUSIVE MODE")
+      or die dbh->errstr;
+  } elsif ( driver_name =~ /^mysql/i ) {
+    dbh->do("SELECT * FROM duplicate_lock
+               WHERE lockname = 'svc_acct'
+              FOR UPDATE"
+          ) or die dbh->errstr;
+  } else {
+    die "unknown database ". driver_name.
+        "; don't know how to lock for duplicate search";
+  }
+  warn "$me acquired svc_acct table lock for duplicate search" if $DEBUG;
+
+  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: conflicts with svcnum ". $dup_user->svcnum.
+               " via exportnum ". $conflict_user_svcpart{$dup_svcpart};
+      }
+    }
+
+    foreach my $dup_userdomain ( @dup_userdomain ) {
+      my $dup_svcpart = $dup_userdomain->cust_svc->svcpart;
+      if ( exists($conflict_userdomain_svcpart{$dup_svcpart}) ) {
+        return "duplicate username\@domain: conflicts with svcnum ".
+               $dup_userdomain->svcnum. " via exportnum ".
+               $conflict_userdomain_svcpart{$dup_svcpart};
+      }
+    }
+
+    foreach my $dup_uid ( @dup_uid ) {
+      my $dup_svcpart = $dup_uid->cust_svc->svcpart;
+      if ( exists($conflict_user_svcpart{$dup_svcpart})
+           || exists($conflict_userdomain_svcpart{$dup_svcpart}) ) {
+        return "duplicate uid: conflicts with svcnum ". $dup_uid->svcnum.
+               " via exportnum ". $conflict_user_svcpart{$dup_svcpart}
+                                 || $conflict_userdomain_svcpart{$dup_svcpart};
+      }
+    }
+
+  }
+
+  return '';
+
+}
+
+=item radius
+
+Depriciated, use radius_reply instead.
+
+=cut
+
+sub radius {
+  carp "FS::svc_acct::radius depriciated, use radius_reply";
+  $_[0]->radius_reply;
+}
+
+=item radius_reply
+
+Returns key/value pairs, suitable for assigning to a hash, for any RADIUS
+reply attributes of this record.
+
+Note that this is now the preferred method for reading RADIUS attributes - 
+accessing the columns directly is discouraged, as the column names are
+expected to change in the future.
+
+=cut
+
+sub radius_reply { 
+  my $self = shift;
+
+  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;
+  }
+
+  %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 $password = $self->_password;
+  my $pw_attrib = length($password) <= 12 ? $radius_password : 'Crypt-Password';  $check{$pw_attrib} = $password;
+
+  my $cust_svc = $self->cust_svc;
+  die "FATAL: no cust_svc record for svc_acct.svcnum ". $self->svcnum. "\n"
+    unless $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
+  }
+
+  %check;
+
+}
+
+=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 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 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 + $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 + $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;
+
+  my $action = $op2action{$op};
+
+  if ( &{$op2condition{$op}}($self, $column, $amount) &&
+        ( $action eq 'suspend'   && !$self->overlimit 
+       || $action eq 'unsuspend' &&  $self->overlimit ) 
+     ) {
+    foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+      if ($part_export->option('overlimit_groups')) {
+        my ($new,$old);
+        my $other = new FS::svc_acct $self->hashref;
+        my $groups = &{ $self->_fieldhandlers->{'usergroup'} }
+                       ($self, $part_export->option('overlimit_groups'));
+        $other->usergroup( $groups );
+        if ($action eq 'suspend'){
+          $new = $other; $old = $self;
+        }else{
+          $new = $self; $old = $other;
+        }
+        my $error = $part_export->export_replace($new, $old);
+        $error ||= $self->overlimit($action);
+        if ( $error ) {
+          $dbh->rollback if $oldAutoCommit;
+          return "Error replacing radius groups in export, ${op}: $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 set_usage {
+  my( $self, $valueref ) = @_;
+
+  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 = ();
+  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 { "$_ =  ?" } (keys %handyhash) ).
+    " WHERE 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((values %handyhash), $self->svcnum);
+    die "Error executing $sql: ". $sth->errstr
+      unless defined($rv);
+    die "Can't update usage for svcnum ". $self->svcnum
+      if $rv == 0;
+  }
+
+  if ( $reset ) {
+    my $error;
+
+    if ($self->overlimit) {
+      $error = $self->overlimit('unsuspend');
+      foreach my $part_export ( $self->cust_svc->part_svc->part_export ) {
+        if ($part_export->option('overlimit_groups')) {
+          my $old = new FS::svc_acct $self->hashref;
+          my $groups = &{ $self->_fieldhandlers->{'usergroup'} }
+                         ($self, $part_export->option('overlimit_groups'));
+          $old->usergroup( $groups );
+          $error ||= $part_export->export_replace($self, $old);
+        }
+      }
+    }
+
+    if ( $conf->exists("svc_acct-usage_unsuspend")) {
+      $error ||= $self->cust_svc->cust_pkg->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 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)
+
+=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
+
+=item domain_select_hash %OPTIONS
+
+Returns a hash SVCNUM => DOMAIN ...  representing the domains this customer
+may at present purchase.
+
+Currently available options are: I<pkgnum> I<svcpart>
+
+=cut
+
+sub domain_select_hash {
+  my ($self, %options) = @_;
+  my %domains = ();
+  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);
+}
+
+1;
+
diff --git a/FS/FS/svc_acct_pop.pm b/FS/FS/svc_acct_pop.pm
new file mode 100644 (file)
index 0000000..de41f5b
--- /dev/null
@@ -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 (executable)
index 0000000..68e7496
--- /dev/null
@@ -0,0 +1,297 @@
+package FS::svc_broadband;
+
+use strict;
+use vars qw(@ISA $conf);
+use FS::Record qw( qsearchs qsearch dbh );
+use FS::svc_Common;
+use FS::cust_svc;
+use FS::addr_block;
+use NetAddr::IP;
+
+@ISA = qw( FS::svc_Common );
+
+$FS::UID::callback{'FS::svc_broadband'} = sub { 
+  $conf = new FS::Conf;
+};
+
+=head1 NAME
+
+FS::svc_broadband - Object methods for svc_broadband records
+
+=head1 SYNOPSIS
+
+  use FS::svc_broadband;
+
+  $record = new FS::svc_broadband \%hash;
+  $record = new FS::svc_broadband { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  $error = $record->suspend;
+
+  $error = $record->unsuspend;
+
+  $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_broadband object represents a 'broadband' Internet connection, such
+as a DSL, cable modem, or fixed wireless link.  These services are assumed to
+have the following properties:
+
+FS::svc_broadband inherits from FS::svc_Common.  The following fields are
+currently supported:
+
+=over 4
+
+=item svcnum - primary key
+
+=item blocknum - see FS::addr_block
+
+=item
+speed_up - maximum upload speed, in bits per second.  If set to zero, upload
+speed will be unlimited.  Exports that do traffic shaping should handle this
+correctly, and not blindly set the upload speed to zero and kill the customer's
+connection.
+
+=item
+speed_down - maximum download speed, as above
+
+=item ip_addr - the customer's IP address.  If the customer needs more than one
+IP address, set this to the address of the customer's router.  As a result, the
+customer's router will have the same address for both its internal and external
+interfaces thus saving address space.  This has been found to work on most NAT
+routers available.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new svc_broadband.  To add the record to the database, see
+"insert".
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table_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'    => 'Address block.',
+    },
+  };
+}
+
+sub table { 'svc_broadband'; }
+
+=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_foreign_key('blocknum', 'addr_block', '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'; }
+
+  if (not($self->ip_addr) or $self->ip_addr eq '0.0.0.0') {
+    my $next_addr = $self->addr_block->next_free_addr;
+    if ($next_addr) {
+      $self->ip_addr($next_addr->addr);
+    } else {
+      return "No free addresses in addr_block (blocknum: ".$self->blocknum.")";
+    }
+  }
+
+  # This should catch errors in the ip_addr.  If it doesn't,
+  # they'll almost certainly not map into the block anyway.
+  my $self_addr = $self->NetAddr; #netmask is /32
+  return ('Cannot parse address: ' . $self->ip_addr) unless $self_addr;
+
+  my $block_addr = $self->addr_block->NetAddr;
+  unless ($block_addr->contains($self_addr)) {
+    return 'blocknum '.$self->blocknum.' does not contain address '.$self->ip_addr;
+  }
+
+  my $router = $self->addr_block->router 
+    or return 'Cannot assign address from unallocated block:'.$self->addr_block->blocknum;
+  if(grep { $_->routernum == $router->routernum} $self->allowed_routers) {
+  } # do nothing
+  else {
+    return 'Router '.$router->routernum.' cannot provide svcpart '.$self->svcpart;
+  }
+
+  $self->SUPER::check;
+}
+
+=item NetAddr
+
+Returns a NetAddr::IP object containing the IP address of this service.  The netmask 
+is /32.
+
+=cut
+
+sub NetAddr {
+  my $self = shift;
+  return new NetAddr::IP ($self->ip_addr);
+}
+
+=item addr_block
+
+Returns the FS::addr_block record (i.e. the address block) for this broadband service.
+
+=cut
+
+sub addr_block {
+  my $self = shift;
+
+  return qsearchs('addr_block', { blocknum => $self->blocknum });
+}
+
+=back
+
+=item allowed_routers
+
+Returns a list of allowed FS::router objects.
+
+=cut
+
+sub allowed_routers {
+  my $self = shift;
+
+  return map { $_->router } qsearch('part_svc_router', { svcpart => $self->svcpart });
+}
+
+=head1 BUGS
+
+The business with sb_field has been 'fixed', in a manner of speaking.
+
+=head1 SEE ALSO
+
+FS::svc_Common, FS::Record, FS::addr_block,
+FS::part_svc, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm
new file mode 100644 (file)
index 0000000..758b399
--- /dev/null
@@ -0,0 +1,478 @@
+package FS::svc_domain;
+
+use strict;
+use vars qw( @ISA $whois_hack $conf
+  @defaultrecords $soadefaultttl $soaemail $soaexpire $soamachine
+  $soarefresh $soaretry
+);
+use Carp;
+use Date::Format;
+#use Net::Whois::Raw;
+use Net::Domain::TLD qw(tld_exists);
+use FS::Record qw(fields qsearch qsearchs dbh);
+use FS::Conf;
+use FS::svc_Common;
+use FS::svc_Parent_Mixin;
+use FS::cust_svc;
+use FS::svc_acct;
+use FS::cust_pkg;
+use FS::cust_main;
+use FS::domain_record;
+use FS::queue;
+
+@ISA = qw( FS::svc_Parent_Mixin FS::svc_Common );
+
+#ask FS::UID to run this stuff for us later
+$FS::UID::callback{'FS::domain'} = sub { 
+  $conf = new FS::Conf;
+
+  @defaultrecords = $conf->config('defaultrecords');
+  $soadefaultttl = $conf->config('soadefaultttl');
+  $soaemail      = $conf->config('soaemail');
+  $soaexpire     = $conf->config('soaexpire');
+  $soamachine    = $conf->config('soamachine');
+  $soarefresh    = $conf->config('soarefresh');
+  $soaretry      = $conf->config('soaretry');
+
+};
+
+=head1 NAME
+
+FS::svc_domain - Object methods for svc_domain records
+
+=head1 SYNOPSIS
+
+  use FS::svc_domain;
+
+  $record = new FS::svc_domain \%hash;
+  $record = new FS::svc_domain { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  $error = $record->suspend;
+
+  $error = $record->unsuspend;
+
+  $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_domain object represents a domain.  FS::svc_domain inherits from
+FS::svc_Common.  The following fields are currently supported:
+
+=over 4
+
+=item svcnum - primary key (assigned automatically for new accounts)
+
+=item domain
+
+=item catchall - optional svcnum of an svc_acct record, designating an email catchall account.
+
+=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
+
+=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',
+    },
+  };
+}
+
+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 or I<M>
+for transfers.
+
+A registration or transfer email will be submitted unless
+$FS::svc_domain::whois_hack is true.
+
+The additional field I<email> can be used to manually set the admin contact
+email address on this email.  Otherwise, the svc_acct records for this package 
+(see L<FS::cust_pkg>) are searched.  If there is exactly one svc_acct record
+in the same package, it is automatically used.  Otherwise an error is returned.
+
+If any I<soamachine> configuration file exists, an SOA record is added to
+the domain_record table (see <FS::domain_record>).
+
+If any records are defined in the I<defaultrecords> configuration file,
+appropriate records are added to the domain_record table (see
+L<FS::domain_record>).
+
+Currently available options are: I<depend_jobnum>
+
+If I<depend_jobnum> is set (to a scalar jobnum or an array reference of
+jobnums), all provisioning jobs will have a dependancy on the supplied
+jobnum(s) (they will not run until the specific job(s) complete(s)).
+
+=cut
+
+sub insert {
+  my $self = shift;
+  my $error;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  $error = $self->check;
+  return $error if $error;
+
+  return "Domain in use (here)"
+    if qsearchs( 'svc_domain', { 'domain' => $self->domain } );
+
+
+  $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, $old ) = ( shift, shift );
+
+  # We absolutely have to have an old vs. new record to make this work.
+  $old = $new->replace_old unless defined($old);
+
+  return "Can't change domain - reorder."
+    if $old->getfield('domain') ne $new->getfield('domain'); 
+
+  # Better to do it here than to force the caller to remember that svc_domain is weird.
+  $new->setfield(action => 'M');
+  my $error = $new->SUPER::replace($old, @_);
+  return $error if $error;
+}
+
+=item suspend
+
+Just returns false (no error) for now.
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Just returns false (no error) for now.
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Just returns false (no error) for now.
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid domain.  If there is an error,
+returns the error, otherwise returns false.  Called by the insert and replace
+methods.
+
+Sets any fixed values; see L<FS::part_svc>.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $x = $self->setfixed;
+  return $x unless ref($x);
+  #my $part_svc = $x;
+
+  my $error = $self->ut_numbern('svcnum')
+              || $self->ut_numbern('catchall')
+  ;
+  return $error if $error;
+
+  #hmm
+  my $pkgnum;
+  if ( $self->svcnum ) {
+    my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } );
+    $pkgnum = $cust_svc->pkgnum;
+  } else {
+    $pkgnum = $self->pkgnum;
+  }
+
+  my($recref) = $self->hashref;
+
+  #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->ut_textn('purpose')
+    or $self->SUPER::check;
+
+}
+
+=item domain_record
+
+=cut
+
+sub domain_record {
+  my $self = shift;
+
+  my %order = (
+    'SOA'   => 1,
+    'NS'    => 2,
+    'MX'    => 3,
+    'CNAME' => 4,
+    'A'     => 5,
+    '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 },
+  );
+
+  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 (file)
index 0000000..0fb391f
--- /dev/null
@@ -0,0 +1,204 @@
+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;
+    $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 (file)
index 0000000..3250f8a
--- /dev/null
@@ -0,0 +1,371 @@
+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->check;
+  return $error if $error;
+
+  $error = $self->SUPER::insert(@_);
+  if ($error) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  ''; #no error
+
+}
+
+=item delete
+
+Deletes this mail forwarding alias from the database.  If there is an error,
+returns the error, otherwise returns false.
+
+The corresponding FS::cust_svc record will be deleted as well.
+
+=cut
+
+sub delete {
+  my $self = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::Autocommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::delete(@_);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+}
+
+
+=item replace OLD_RECORD
+
+Replaces OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+  my ( $new, $old ) = ( shift, shift );
+
+  if ( $new->srcsvc != $old->srcsvc
+       && ( $new->dstsvc != $old->dstsvc
+            || ! $new->dstsvc && $new->dst ne $old->dst 
+          )
+      ) {
+    return "Can't change both source and destination of a mail forward!"
+  }
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $new->SUPER::replace($old, @_);
+  if ($error) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+}
+
+=item suspend
+
+Just returns false (no error) for now.
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Just returns false (no error) for now.
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Just returns false (no error) for now.
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid mail forwarding alias.  If there
+is an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+Sets any fixed values; see L<FS::part_svc>.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $x = $self->setfixed;
+  return $x unless ref($x);
+  #my $part_svc = $x;
+
+  my $error = $self->ut_numbern('svcnum')
+              || $self->ut_numbern('srcsvc')
+              || $self->ut_numbern('dstsvc')
+  ;
+  return $error if $error;
+
+  return "Both srcsvc and src were defined; only one can be specified"
+    if $self->srcsvc && $self->src;
+
+  return "one of srcsvc or src is required"
+    unless $self->srcsvc || $self->src;
+
+  return "Unknown srcsvc: ". $self->srcsvc
+    unless ! $self->srcsvc || $self->srcsvc_acct;
+
+  return "Both dstsvc and dst were defined; only one can be specified"
+    if $self->dstsvc && $self->dst;
+
+  return "one of dstsvc or dst is required"
+    unless $self->dstsvc || $self->dst;
+
+  return "Unknown dstsvc: ". $self->dstsvc
+    unless ! $self->dstsvc || $self->dstsvc_acct;
+  #return "Unknown dstsvc"
+  #  unless qsearchs('svc_acct', { 'svcnum' => $self->dstsvc } )
+  #         || ! $self->dstsvc;
+
+  if ( $self->src ) {
+    $self->src =~ /^([\w\.\-\&]*)(\@([\w\-]+\.)+\w+)$/
+       or return "Illegal src: ". $self->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_phone.pm b/FS/FS/svc_phone.pm
new file mode 100644 (file)
index 0000000..00ccc19
--- /dev/null
@@ -0,0 +1,190 @@
+package FS::svc_phone;
+
+use strict;
+use vars qw( @ISA );
+#use FS::Record qw( qsearch qsearchs );
+use FS::svc_Common;
+
+@ISA = qw( FS::svc_Common );
+
+=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 pin - 
+
+=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,
+                         },
+    },
+  };
+}
+
+sub table { 'svc_phone'; }
+
+=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('phonenum', $string );
+}
+
+=item label
+
+Returns the phone number.
+
+=cut
+
+sub label {
+  my $self = shift;
+  $self->phonenum; #XXX format it better
+}
+
+=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 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 $error = 
+    $self->ut_numbern('svcnum')
+    || $self->ut_numbern('countrycode')
+    || $self->ut_number('phonenum')
+    || $self->ut_numbern('pin')
+  ;
+  return $error if $error;
+
+  $self->countrycode(1) unless $self->countrycode;
+
+  $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_www.pm b/FS/FS/svc_www.pm
new file mode 100644 (file)
index 0000000..53225bb
--- /dev/null
@@ -0,0 +1,312 @@
+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 insert {
+  my $self = shift;
+
+  my $error = $self->check;
+  return $error if $error;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  #if ( $self->recnum =~ /^([\w\-]+|\@)\.(([\w\.\-]+\.)+\w+)$/ ) {
+  if ( $self->recnum =~ /^([\w\-]+|\@)\.(\d+)$/ ) {
+    my( $reczone, $domain_svcnum ) = ( $1, $2 );
+    unless ( $apacheip ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "Configuration option apacheip not set; can't autocreate A record";
+             #"for $reczone". $svc_domain->domain;
+    }
+    my $domain_record = new FS::domain_record {
+      'svcnum'  => $domain_svcnum,
+      'reczone' => $reczone,
+      'recaf'   => 'IN',
+      'rectype' => 'A',
+      'recdata' => $apacheip,
+    };
+    $error = $domain_record->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+    $self->recnum($domain_record->recnum);
+  }
+
+  $error = $self->SUPER::insert(@_);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  '';
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+sub delete {
+  my $self = shift;
+  my $error;
+
+  $error = $self->SUPER::delete(@_);
+  return $error if $error;
+
+  '';
+}
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+  my ( $new, $old ) = ( shift, shift );
+  my $error;
+
+  $error = $new->SUPER::replace($old, @_);
+  return $error if $error;
+
+  '';
+}
+
+=item suspend
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid web virtual host.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and 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/type_pkgs.pm b/FS/FS/type_pkgs.pm
new file mode 100644 (file)
index 0000000..bf34e7c
--- /dev/null
@@ -0,0 +1,125 @@
+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_number('typenum')
+    || $self->ut_number('pkgpart')
+  ;
+  return $error if $error;
+
+  return "Unknown typenum"
+    unless qsearchs( 'agent_type', { 'typenum' => $self->typenum } );
+
+  return "Unknown pkgpart"
+    unless qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
+
+  $self->SUPER::check;
+}
+
+=item part_pkg
+
+Returns the FS::part_pkg object associated with this record.
+
+=cut
+
+sub part_pkg {
+  my $self = shift;
+  qsearchs( 'part_pkg', { 'pkgpart' => $self->pkgpart } );
+}
+
+=cut
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, L<FS::agent_type>, L<FS::part_pkgs>, schema.html from the base
+documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
new file mode 100644 (file)
index 0000000..635bc04
--- /dev/null
@@ -0,0 +1,396 @@
+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-expiration-alerter
+bin/freeside-queued
+bin/freeside-radgroup
+bin/freeside-reexport
+bin/freeside-selfservice-server
+bin/freeside-setinvoice
+bin/freeside-setup
+bin/freeside-sqlradius-radacctd
+bin/freeside-sqlradius-reset
+bin/freeside-sqlradius-seconds
+FS.pm
+FS/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/Table.pm
+FS/Report/Table/Monthly.pm
+FS/SearchCache.pm
+FS/UI/Web.pm
+FS/UID.pm
+FS/Msgcat.pm
+FS/Pony.pm
+FS/acct_snarf.pm
+FS/agent.pm
+FS/agent_type.pm
+FS/cust_bill.pm
+FS/cust_bill_pkg.pm
+FS/cust_bill_pkg_detail.pm
+FS/cust_credit.pm
+FS/cust_credit_bill.pm
+FS/cust_main.pm
+FS/cust_main_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_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/part_export.pm
+FS/part_export_option.pm
+FS/part_export/acct_sql.pm
+FS/part_export/apache.pm
+FS/part_export/bind.pm
+FS/part_export/bind_slave.pm
+FS/part_export/bsdshell.pm
+FS/part_export/communigate_pro.pm
+FS/part_export/communigate_pro_singledomain.pm
+FS/part_export/cp.pm
+FS/part_export/cyrus.pm
+FS/part_export/domain_shellcommands.pm
+FS/part_export/forward_shellcommands.pm
+FS/part_export/http.pm
+FS/part_export/infostreet.pm
+FS/part_export/ldap.pm
+FS/part_export/null.pm
+FS/part_export/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/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_svc.t
+t/h_cust_tax_exempt.t
+t/h_Common.t
+t/h_cust_svc.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/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/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/cdr_upstream_rate.pm
+t/cdr_upstream_rate.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_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
diff --git a/FS/MANIFEST.SKIP b/FS/MANIFEST.SKIP
new file mode 100644 (file)
index 0000000..ae335e7
--- /dev/null
@@ -0,0 +1 @@
+CVS/
diff --git a/FS/Makefile.PL b/FS/Makefile.PL
new file mode 100644 (file)
index 0000000..1647f8e
--- /dev/null
@@ -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 (executable)
index 0000000..7b30f7d
--- /dev/null
@@ -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->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 (file)
index 0000000..9cb1219
--- /dev/null
@@ -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 (file)
index 0000000..cbe792a
--- /dev/null
@@ -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 (file)
index 0000000..237e29e
--- /dev/null
@@ -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 - passwordsa 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 (executable)
index 0000000..ea6a7bd
--- /dev/null
@@ -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-count-active-customers b/FS/bin/freeside-count-active-customers
new file mode 100755 (executable)
index 0000000..759085a
--- /dev/null
@@ -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 (executable)
index 0000000..13079b4
--- /dev/null
@@ -0,0 +1,104 @@
+#!/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);
+getopts("p:a:d:vl:sy:nm", \%opt);
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+use FS::Cron::bill qw(bill);
+bill(%opt);
+
+#what to do about the below when using -m?  that is the question.
+
+use FS::Cron::notify qw(notify_flat_delay);
+notify_flat_delay(%opt);
+
+use FS::Cron::expire_user_pref qw(expire_user_pref);
+expire_user_pref();
+
+use FS::Cron::vacuum qw(vacuum);
+vacuum();
+
+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' ] 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 ] [ -v ] [ -l level ] [ -m ] 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.
+
+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 (executable)
index 0000000..a04f425
--- /dev/null
@@ -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-delete-addr_blocks b/FS/bin/freeside-delete-addr_blocks
new file mode 100755 (executable)
index 0000000..a7e9976
--- /dev/null
@@ -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 (file)
index 0000000..afc3a01
--- /dev/null
@@ -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 (file)
index 0000000..dc4ff9c
--- /dev/null
@@ -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 (file)
index 0000000..a2a361a
--- /dev/null
@@ -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 (executable)
index 0000000..0af4609
--- /dev/null
@@ -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 (executable)
index 0000000..7a93f78
--- /dev/null
@@ -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-expiration-alerter b/FS/bin/freeside-expiration-alerter
new file mode 100755 (executable)
index 0000000..ffd75f9
--- /dev/null
@@ -0,0 +1,226 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use Date::Format;
+use Time::Local;
+use Text::Template;
+use Getopt::Std;
+use Net::SMTP;
+use Mail::Header;
+use Mail::Internet;
+use FS::Conf;
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearch);
+use FS::cust_main;
+
+use vars qw($smtpmachine @body);
+
+#hush, perl!
+$FS::alerter::_template::first = "";
+$FS::alerter::_template::last = "";
+$FS::alerter::_template::company = "";
+$FS::alerter::_template::payby = "";
+$FS::alerter::_template::expdate = "";
+
+# Set the mail program  and other variables
+my $mail_sender = "billing\@mydomain.tld";  # or invoice_from if available
+my $failure_recipient = "postmaster";       # or invoice_from if available
+my $warning_time = 30 * 24 * 60 * 60;
+my $urgent_time = 15 * 24 * 60 * 60;
+my $panic_time = 5 * 24 * 60 * 60;
+my $window_time = 24 * 60 * 60;
+
+&untaint_argv; #what it sounds like  (eww)
+
+#we're at now now (and later).
+my($_date)= $^T;
+
+# Get the current month
+my ($sec,$min,$hour,$mday,$mon,$year) =
+       (localtime($_date) )[0,1,2,3,4,5]; 
+$mon++;
+
+# Login to the database
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+# Get the needed configuration files
+my $conf = new FS::Conf;
+$smtpmachine = $conf->config('smtpmachine');
+$mail_sender = $conf->config('invoice_from')
+  if $conf->exists('invoice_from');
+$failure_recipient = $conf->config('invoice_from')
+  if $conf->exists('invoice_from');
+
+
+my(@customers)=qsearch('cust_main',{});
+if (scalar(@customers) == 0)
+{
+  exit 1;
+}
+
+# Prepare for sending email
+
+$ENV{MAILADDRESS} = $mail_sender;
+my $header = new Mail::Header ( [
+  "From: Account Processor",
+  "To: $failure_recipient",
+  "Sender: $mail_sender",
+  "Reply-To: $mail_sender",
+  "Subject: Unnotified Billing Arrangement Expirations",
+] );
+
+my @alerter_template = $conf->config('alerter_template')
+  or die "cannot load config file alerter_template";
+
+my $alerter = new Text::Template (TYPE => 'ARRAY', SOURCE => [ map "$_\n", @alerter_template ])
+  or die "can't create new Text::Template object:  Text::Template::ERROR";
+$alerter->compile() or die "can't compile template:  Text::Template::ERROR";
+
+# Now I can start looping
+foreach my $customer (@customers)
+{
+  my $paydate = $customer->getfield('paydate');
+  next if $paydate =~ /^\s*$/; #skip empty expiration dates
+
+  my $custnum = $customer->getfield('custnum');
+  my $first = $customer->getfield('first');
+  my $last = $customer->getfield('last');
+  my $company = $customer->getfield('company');
+  my $payby = $customer->getfield('payby');
+  my $payinfo = $customer->getfield('payinfo');
+  my $daytime = $customer->getfield('daytime');
+  my $night = $customer->getfield('night');
+
+  my ($payyear,$paymonth,$payday) = split (/-/,$paydate);
+
+  my $expire_time = timelocal(0,0,0,$payday,--$paymonth,$payyear);
+
+  #credit cards expire at the end of the month/year of their exp date
+  if ($payby eq 'CARD' || $payby eq 'DCRD') {
+    ($paymonth < 11) ? $paymonth++ : ($paymonth=0, $payyear++);
+    $expire_time = timelocal(0,0,0,$payday,$paymonth,$payyear);
+    $expire_time--;
+  }
+
+  if (($expire_time < $_date + $warning_time &&
+    $expire_time > $_date + $warning_time - $window_time) ||
+      ($expire_time < $_date + $urgent_time &&
+       $expire_time > $_date + $urgent_time - $window_time) ||
+      ($expire_time < $_date + $panic_time &&
+       $expire_time > $_date + $panic_time - $window_time)) {
+
+
+
+    my @packages = $customer->ncancelled_pkgs;
+    if (scalar(@packages) != 0) {
+      my @invoicing_list = $customer->invoicing_list;
+      if ( grep { $_ ne 'POST' } @invoicing_list ) { 
+        my $header = new Mail::Header ( [
+          "From: $mail_sender",
+          "To: ". join(', ', grep { $_ ne 'POST' } @invoicing_list ),
+          "Sender: $mail_sender",
+          "Reply-To: $mail_sender",
+          "Date: ". time2str("%a, %d %b %Y %X %z", time),
+          "Subject: Billing Arrangement Expiration",
+        ] );
+        $FS::alerter::_template::first = $first;
+        $FS::alerter::_template::last = $last;
+        $FS::alerter::_template::company = $company;
+        if ($payby eq 'CARD' || $payby eq 'DCRD') {
+          $FS::alerter::_template::payby = "credit card (" .
+            substr($payinfo, 0, 2) . "xxxxxxxxxx" .
+            substr($payinfo, -4) . ")";
+        }elsif ($payby eq 'COMP') {
+          $FS::alerter::_template::payby = "complimentary account";
+        }else{
+          $FS::alerter::_template::payby = "current method";
+        }
+        $FS::alerter::_template::expdate = $expire_time;
+
+        $FS::alerter::_template::company_name = $conf->config('company_name');
+        $FS::alerter::_template::company_address =
+          join("\n", $conf->config('company_address') ). "\n";
+
+        my $message = new Mail::Internet (
+          'Header' => $header,
+          'Body' => [ $alerter->fill_in( PACKAGE => 'FS::alerter::_template' ) ],
+        );
+        $!=0;
+        $message->smtpsend( Host => $smtpmachine )
+          or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
+            or die "Can't send expiration email: $!";
+
+      } elsif ( ! @invoicing_list || grep { $_ eq 'POST' } @invoicing_list ) { 
+        push @body, sprintf(qq{%5d %-32.32s %4s %10s %12s %12s},
+          $custnum,
+          $first . " " . $last . "   " . $company,
+          $payby,
+          $paydate,
+          $daytime,
+          $night);
+      }
+    }
+  }
+}
+
+# Now I need to send EMAIL
+if (scalar(@body)) {
+  my $message = new Mail::Internet (
+    'Header' => $header,
+    'Body' => [ (@body) ],
+  );
+  $!=0;
+  $message->smtpsend( Host => $smtpmachine )
+    or $message->smtpsend( Host => $smtpmachine, Debug => 1 )
+      or die "can't send alerter failure email to $failure_recipient".
+             " via server $smtpmachine with SMTP: $!";
+}
+
+# subroutines
+sub untaint_argv {
+  foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV
+    $ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal argument \"$ARGV[$_]\"";
+    $ARGV[$_]=$1;
+  }
+}
+
+sub usage {
+  die "Usage:\n\n  freeside-expiration-alerter user\n";
+}
+
+=head1 NAME
+
+freeside-expiration-alerter - Emails notifications of credit card expirations.
+
+=head1 SYNOPSIS
+
+  freeside-expiration-alerter user
+
+=head1 DESCRIPTION
+
+Emails customers notice that their credit card or other billing arrangement
+is about to expire.  Usually run as a cron job.
+
+user: From the mapsecrets file - see config.html from the base documentation
+
+=head1 BUGS
+
+Yes..... Use at your own risk. No guarantees or warrantees of any
+kind apply to this program. Parts of this program are hacked from
+other GNU licensed software created mainly by Ivan Kohler.
+
+This is released under the GNU Public License. See www.gnu.org
+for more information regarding this license.
+
+=head1 SEE ALSO
+
+L<FS::cust_main>, config.html from the base documentation
+
+=head1 AUTHOR
+
+Jeff Finucane <jeff@cmh.net>
+
+=cut
+
+
diff --git a/FS/bin/freeside-fetch b/FS/bin/freeside-fetch
new file mode 100755 (executable)
index 0000000..89a4f29
--- /dev/null
@@ -0,0 +1,89 @@
+#!/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->agent("FreesideFetcher/0.1 " . $ua->agent);
+
+  my $req = new HTTP::Request GET => $url;
+  my $res = $ua->request($req);
+
+  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 (executable)
index 0000000..77a4332
--- /dev/null
@@ -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 (executable)
index 0000000..fe4729c
--- /dev/null
@@ -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 (executable)
index 0000000..1e41b78
--- /dev/null
@@ -0,0 +1,91 @@
+#!/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' );
+
+###
+# 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-prepaidd b/FS/bin/freeside-prepaidd
new file mode 100644 (file)
index 0000000..a68db39
--- /dev/null
@@ -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( "/usr/local/etc/freeside/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 (executable)
index 0000000..d2b6efe
--- /dev/null
@@ -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 (file)
index 0000000..93d735d
--- /dev/null
@@ -0,0 +1,250 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw( $DEBUG $kids $max_kids %kids );
+use POSIX qw(:sys_wait_h);
+use IO::File;
+use FS::UID qw(adminsuidsetup forksuidsetup driver_name dbh myconnect);
+use FS::Daemon qw(daemonize1 drop_root logfile daemonize2 sigint sigterm);
+use FS::Record qw(qsearch qsearchs);
+use FS::queue;
+use FS::queue_depend;
+
+# no autoloading for non-FS classes...
+use Net::SSH 0.07;
+
+$DEBUG = 0;
+
+$max_kids = '10'; #guess it should be a config file...
+$kids = 0;
+
+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 $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;
+
+  #assuming mysql 4.1 w/subqueries now
+  #my $nodepend = driver_name eq 'mysql'
+  # ? ''
+  # : 'AND 0 = ( SELECT COUNT(*) FROM queue_depend'.
+  #   ' WHERE queue_depend.jobnum = queue.jobnum ) ';
+  my $nodepend = 'AND 0 = ( SELECT COUNT(*) FROM queue_depend'.
+                 '           WHERE queue_depend.jobnum = queue.jobnum ) ';
+
+  my $job = qsearchs(
+    'queue',
+    { 'status' => 'new' },
+    '',
+    driver_name eq 'mysql'
+      ? "$nodepend ORDER BY jobnum LIMIT 1 FOR UPDATE"
+      : "$nodepend ORDER BY jobnum FOR UPDATE LIMIT 1"
+  ) or do {
+    # if $oldAutoCommit {
+    dbh->commit or do {
+      warn "WARNING: database error, closing connection: ". dbh->errstr;
+      undef $FS::UID::dbh;
+      next;
+    };
+    # }
+    sleep 5; #connecting to db is expensive
+    next;
+  };
+
+  #assuming mysql 4.1 w/subqueries now
+  #if ( driver_name eq 'mysql'
+  #     && qsearch('queue_depend', { 'jobnum' => $job->jobnum } ) ) {
+  #  dbh->commit or die dbh->errstr; #if $oldAutoCommit;
+  #  sleep 5; #would be better if mysql could do everything in query above
+  #  next;
+  #}
+
+  my %hash = $job->hash;
+  $hash{'status'} = 'locked';
+  my $ljob = new FS::queue ( \%hash );
+  my $error = $ljob->replace($job);
+  if ( $error ) {
+    warn "WARNING: database error locking job, closing connection: ".
+         dbh->errstr;
+    undef $FS::UID::dbh;
+    next;
+  }
+
+  # if $oldAutoCommit {
+  dbh->commit or do {
+    warn "WARNING: database error, closing connection: ". dbh->errstr;
+    undef $FS::UID::dbh;
+    next;
+  };
+  # }
+
+  $FS::UID::AutoCommit = 1;
+  #} 
+
+  my @args = $ljob->args;
+  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;
+    next; #don't increment the kid counter
+  };
+
+  if ( $pid ) {
+    $kids++;
+    $kids{$pid} = 1;
+  } else { #kid time
+
+    #get new db handle
+    $FS::UID::dbh->{InactiveDestroy} = 1;
+
+    forksuidsetup($user);
+
+    #auto-use classes...
+    #if ( $ljob->job =~ /(FS::part_export::\w+)::/ ) {
+    if (    $ljob->job =~ /(FS::part_export::\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 ( $@ ) {
+      warn "job $eval failed";
+      my %hash = $ljob->hash;
+      $hash{'status'} = 'failed';
+      $hash{'statustext'} = $@;
+      my $fjob = new FS::queue( \%hash );
+      my $error = $fjob->replace($ljob);
+      die $error if $error;
+    } else {
+      $ljob->delete;
+    }
+
+    exit;
+    #end-of-kid
+  }
+
+} continue {
+  if ( sigterm() ) {
+    warn "received TERM signal; exiting\n";
+    exit;
+  }
+  if ( sigint() ) {
+    warn "received INT signal; exiting\n";
+    exit;
+  }
+}
+
+sub usage {
+  die "Usage:\n\n  freeside-queued user\n";
+}
+
+sub reap_kids {
+  foreach my $pid ( keys %kids ) {
+    my $kid = waitpid($pid, WNOHANG);
+    if ( $kid > 0 ) {
+      $kids--;
+      delete $kids{$kid};
+    }
+  }
+}
+
+=head1 NAME
+
+freeside-queued - Job queue daemon
+
+=head1 SYNOPSIS
+
+  freeside-queued user
+
+=head1 DESCRIPTION
+
+Job queue daemon.  Should be running at all times.
+
+user: from the mapsecrets file - see config.html from the base documentation
+
+=head1 VERSION
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/FS/bin/freeside-radgroup b/FS/bin/freeside-radgroup
new file mode 100644 (file)
index 0000000..ed85626
--- /dev/null
@@ -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 (file)
index 0000000..54af9dd
--- /dev/null
@@ -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 (executable)
index 0000000..5829d44
--- /dev/null
@@ -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 (file)
index 0000000..2087e71
--- /dev/null
@@ -0,0 +1,240 @@
+#!/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;
+use FS::ClientAPI_SessionCache;
+
+use FS::Conf;
+use FS::cust_svc;
+
+$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
+
+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');
+
+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 );
+        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 (file)
index 0000000..708e2fa
--- /dev/null
@@ -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 (executable)
index 0000000..9b16d78
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/perl -Tw
+
+#to delay loading dbdef until we're ready
+BEGIN { $FS::Schema::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 || '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 (executable)
index 0000000..441d50f
--- /dev/null
@@ -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 (file)
index 0000000..83fd4bf
--- /dev/null
@@ -0,0 +1,150 @@
+#!/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::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();
+
+#--
+
+#don't just look for ->can('usage_sessions'), we're sqlradius-specific
+# (radiator is supposed to be setup with a radacct table)
+
+@part_export =
+  qsearch('part_export', { 'exporttype' => 'sqlradius' } );
+push @part_export,
+  qsearch('part_export', { 'exporttype' => 'sqlradius_withdomain' } );
+push @part_export,
+  qsearch('part_export', { 'exporttype' => 'radiator' } );
+
+@part_export = grep { ! $_->option('ignore_accounting') } @part_export;
+
+die "no sqlradius, sqlradius_withdomain or radiator 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_acct();
+        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 svc_acct.seconds for each account.
+Runs as a daemon and updates the database in real-time.
+
+B<username> is a username added by freeside-adduser.
+
+=head1 RADIUS DATABASE CHANGES
+
+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 (executable)
index 0000000..94fa68a
--- /dev/null
@@ -0,0 +1,104 @@
+#!/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 )) {
+    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;
+  }
+}
+
+foreach my $export ( @exports ) {
+
+  #my @svcparts = map { $_->svcpart } $export->export_svc;
+  my $overlimit_groups = $export->option('overlimit_groups');
+
+  my @svc_acct =
+    map { qsearchs('svc_acct', { 'svcnum' => $_->svcnum } ) }
+      map { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+        grep { qsearch('cust_svc', { 'svcpart' => $_->svcpart } ) }
+          $export->export_svc;
+
+  foreach my $svc_acct ( @svc_acct ) {
+
+    $svc_acct->check; #set any fixed usergroup so it'll export even if all
+                      #svc_acct records don't have the group yet
+
+    if ($overlimit_groups && $svc_acct->overlimit) {
+      $svc_acct->usergroup( &{ $svc_acct->_fieldhandlers->{'usergroup'} }
+                            ($svc_acct, $overlimit_groups)
+                          );
+    }
+
+    #false laziness with FS::svc_acct::insert (like it matters)
+    my $error = $export->export_insert($svc_acct);
+    die $error if $error;
+
+  }
+}
+
+sub usage {
+  die "Usage:\n\n  freeside-sqlradius-reset user [ exportnum, ... ]\n";
+}
+
+=head1 NAME
+
+freeside-sqlradius-reset - Command line interface to reset and recreate RADIUS SQL tables
+
+=head1 SYNOPSIS
+
+  freeside-sqlradius-reset [ -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
+
+
+
diff --git a/FS/bin/freeside-sqlradius-seconds b/FS/bin/freeside-sqlradius-seconds
new file mode 100644 (file)
index 0000000..9999cbb
--- /dev/null
@@ -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 (executable)
index 0000000..ad85630
--- /dev/null
@@ -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 (executable)
index 0000000..d143d92
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/perl -w
+
+use strict;
+use vars qw($opt_d $opt_q $opt_v);
+use vars qw($DEBUG $DRY_RUN);
+use Getopt::Std;
+use DBIx::DBSchema 0.31;
+use FS::UID qw(adminsuidsetup checkeuid datasrc );  #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);
+
+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;
+$FS::CurrentUser::upgrade_hack = 1;
+$FS::UID::callback_hack = 1;
+my $dbh = adminsuidsetup($user);
+$FS::UID::callback_hack = 0;
+
+#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);
+
+$DBIx::DBSchema::DEBUG = $DEBUG;
+$DBIx::DBSchema::Table::DEBUG = $DEBUG;
+$DBIx::DBSchema::Index::DEBUG = $DEBUG;
+
+my @bugfix = ();
+
+if (dbdef->table('cust_main')->column('agent_custid')) { 
+  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
+
+if ( $DRY_RUN ) {
+  print
+    join(";\n", @bugfix, dbdef->sql_update_schema( dbdef_dist(datasrc), $dbh ) ). ";\n";
+  exit;
+} else {
+  foreach my $statement ( @bugfix ) {
+    $dbh->do( $statement )
+      or die "Error: ". $dbh->errstr. "\n executing: $statement";
+  }
+
+  dbdef->update_schema( dbdef_dist(datasrc), $dbh );
+}
+
+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;
+
+if ( $dbh->{Driver}->{Name} =~ /^mysql/i ) {
+
+  my $sth = $dbh->prepare(
+    "SELECT COUNT(*) FROM duplicate_lock WHERE lockname = 'svc_acct'"
+  ) or die $dbh->errstr;
+
+  $sth->execute or die $sth->errstr;
+
+  unless ( $sth->fetchrow_arrayref->[0] ) {
+
+    $sth = $dbh->prepare(
+      "INSERT INTO duplicate_lock ( lockname ) VALUES ( 'svc_acct' )"
+    ) or die $dbh->errstr;
+
+    $sth->execute or die $sth->errstr;
+
+  }
+}
+
+$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 ) {
+  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);
+
+upgrade()
+  unless $DRY_RUN;
+
+$dbh->commit or die $dbh->errstr;
+$dbh->disconnect or die $dbh->errstr;
+
+###
+
+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 ] [ -q | -v ] user\n"; 
+}
+
+=head1 NAME
+
+freeside-upgrade - Upgrades database schema for new freeside verisons.
+
+=head1 SYNOPSIS
+
+  freeside-upgrade [ -d ] [ -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.
+
+  [ -v ]: Run verbosely, sending debugging information to STDERR.  This is the
+          current default.
+
+=head1 SEE ALSO
+
+=cut
+
diff --git a/FS/t/AccessRight.t b/FS/t/AccessRight.t
new file mode 100644 (file)
index 0000000..a966842
--- /dev/null
@@ -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 (file)
index 0000000..1b4e238
--- /dev/null
@@ -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 (file)
index 0000000..973d8da
--- /dev/null
@@ -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 (file)
index 0000000..605803e
--- /dev/null
@@ -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 (file)
index 0000000..a9f7653
--- /dev/null
@@ -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 (file)
index 0000000..433555a
--- /dev/null
@@ -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 (file)
index 0000000..c7932d7
--- /dev/null
@@ -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 (file)
index 0000000..847d41a
--- /dev/null
@@ -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 (file)
index 0000000..42c7b4f
--- /dev/null
@@ -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 (file)
index 0000000..eaa6b76
--- /dev/null
@@ -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 (file)
index 0000000..24893fd
--- /dev/null
@@ -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 (file)
index 0000000..0ce60c8
--- /dev/null
@@ -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 (file)
index 0000000..cc7751a
--- /dev/null
@@ -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 (file)
index 0000000..29e71b3
--- /dev/null
@@ -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 (file)
index 0000000..00de1ed
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::Record;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/Report-Table-Monthly.t b/FS/t/Report-Table-Monthly.t
new file mode 100644 (file)
index 0000000..6ff365d
--- /dev/null
@@ -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 (file)
index 0000000..866d498
--- /dev/null
@@ -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 (file)
index 0000000..76d6ea4
--- /dev/null
@@ -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 (file)
index 0000000..3c26f35
--- /dev/null
@@ -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 (file)
index 0000000..9f7da4e
--- /dev/null
@@ -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 (file)
index 0000000..be14109
--- /dev/null
@@ -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 (file)
index 0000000..aff1f25
--- /dev/null
@@ -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 (file)
index 0000000..66cd362
--- /dev/null
@@ -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 (file)
index 0000000..cab679d
--- /dev/null
@@ -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 (file)
index 0000000..2822098
--- /dev/null
@@ -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 (file)
index 0000000..383a7cf
--- /dev/null
@@ -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 (file)
index 0000000..552bdc8
--- /dev/null
@@ -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 (file)
index 0000000..642760f
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::acct_snarf;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/agent.t b/FS/t/agent.t
new file mode 100644 (file)
index 0000000..769cce2
--- /dev/null
@@ -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 (file)
index 0000000..af78a9a
--- /dev/null
@@ -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 (file)
index 0000000..99c66a1
--- /dev/null
@@ -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 (file)
index 0000000..bef1ff2
--- /dev/null
@@ -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/cdr.t b/FS/t/cdr.t
new file mode 100644 (file)
index 0000000..1d1f3eb
--- /dev/null
@@ -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_calltype.t b/FS/t/cdr_calltype.t
new file mode 100644 (file)
index 0000000..d4e1394
--- /dev/null
@@ -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 (file)
index 0000000..1e21615
--- /dev/null
@@ -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_type.t b/FS/t/cdr_type.t
new file mode 100644 (file)
index 0000000..9dff15a
--- /dev/null
@@ -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/cdr_upstream_rate.t b/FS/t/cdr_upstream_rate.t
new file mode 100644 (file)
index 0000000..f9458c5
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cdr_upstream_rate;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/clientapi_session.t b/FS/t/clientapi_session.t
new file mode 100644 (file)
index 0000000..a6414c3
--- /dev/null
@@ -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 (file)
index 0000000..a9d4fa9
--- /dev/null
@@ -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 (file)
index 0000000..5e52079
--- /dev/null
@@ -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/cust_bill.t b/FS/t/cust_bill.t
new file mode 100644 (file)
index 0000000..b43f08e
--- /dev/null
@@ -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 (file)
index 0000000..fa03d34
--- /dev/null
@@ -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 (file)
index 0000000..0e2ca3e
--- /dev/null
@@ -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 (file)
index 0000000..001eed0
--- /dev/null
@@ -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 (file)
index 0000000..bc3a827
--- /dev/null
@@ -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 (file)
index 0000000..b8fcddb
--- /dev/null
@@ -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 (file)
index 0000000..0e45bdb
--- /dev/null
@@ -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 (file)
index 0000000..ea6e3d1
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pkg_detail;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_credit.t b/FS/t/cust_credit.t
new file mode 100644 (file)
index 0000000..cddf75c
--- /dev/null
@@ -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 (file)
index 0000000..0ef54c3
--- /dev/null
@@ -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 (file)
index 0000000..4eb84c3
--- /dev/null
@@ -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 (file)
index 0000000..6b2b599
--- /dev/null
@@ -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 (file)
index 0000000..7812c5b
--- /dev/null
@@ -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_main.t b/FS/t/cust_main.t
new file mode 100644 (file)
index 0000000..b0ffbdb
--- /dev/null
@@ -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 (file)
index 0000000..c8b9291
--- /dev/null
@@ -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 (file)
index 0000000..dd61199
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_main_county;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_main_invoice.t b/FS/t/cust_main_invoice.t
new file mode 100644 (file)
index 0000000..9661620
--- /dev/null
@@ -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 (file)
index 0000000..41a7bac
--- /dev/null
@@ -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 (file)
index 0000000..f6d0b75
--- /dev/null
@@ -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 (file)
index 0000000..02b572c
--- /dev/null
@@ -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 (file)
index 0000000..9ab2b5e
--- /dev/null
@@ -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 (file)
index 0000000..85d6c23
--- /dev/null
@@ -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 (file)
index 0000000..dca9bec
--- /dev/null
@@ -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 (file)
index 0000000..c6a6860
--- /dev/null
@@ -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_option.t b/FS/t/cust_pkg_option.t
new file mode 100644 (file)
index 0000000..12314bf
--- /dev/null
@@ -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 (file)
index 0000000..2f0a4fa
--- /dev/null
@@ -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_refund.t b/FS/t/cust_refund.t
new file mode 100644 (file)
index 0000000..91583da
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_refund;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_svc.t b/FS/t/cust_svc.t
new file mode 100644 (file)
index 0000000..267d731
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_svc;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cust_tax_exempt.t b/FS/t/cust_tax_exempt.t
new file mode 100644 (file)
index 0000000..8af13e3
--- /dev/null
@@ -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 (file)
index 0000000..099a0ce
--- /dev/null
@@ -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/domain_record.t b/FS/t/domain_record.t
new file mode 100644 (file)
index 0000000..794518c
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::domain_record;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/export_svc.t b/FS/t/export_svc.t
new file mode 100644 (file)
index 0000000..773c5de
--- /dev/null
@@ -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 (file)
index 0000000..174bb99
--- /dev/null
@@ -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 (file)
index 0000000..ceccb2a
--- /dev/null
@@ -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 (file)
index 0000000..e20f476
--- /dev/null
@@ -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 (file)
index 0000000..6a3fe95
--- /dev/null
@@ -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_svc.t b/FS/t/h_cust_svc.t
new file mode 100644 (file)
index 0000000..a7dabbe
--- /dev/null
@@ -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 (file)
index 0000000..432238a
--- /dev/null
@@ -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 (file)
index 0000000..f48e72e
--- /dev/null
@@ -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 (file)
index 0000000..9c94d08
--- /dev/null
@@ -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 (file)
index 0000000..b8e5c7c
--- /dev/null
@@ -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 (file)
index 0000000..87d2a09
--- /dev/null
@@ -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 (file)
index 0000000..5248f87
--- /dev/null
@@ -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 (file)
index 0000000..64731d5
--- /dev/null
@@ -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_www.t b/FS/t/h_svc_www.t
new file mode 100644 (file)
index 0000000..07558ce
--- /dev/null
@@ -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 (file)
index 0000000..80b2fa2
--- /dev/null
@@ -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 (file)
index 0000000..8ce9d67
--- /dev/null
@@ -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/msgcat.t b/FS/t/msgcat.t
new file mode 100644 (file)
index 0000000..c38c639
--- /dev/null
@@ -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 (file)
index 0000000..6f8ae36
--- /dev/null
@@ -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 (file)
index 0000000..ad26141
--- /dev/null
@@ -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 (file)
index 0000000..5626a9f
--- /dev/null
@@ -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_event-Action.t b/FS/t/part_event-Action.t
new file mode 100644 (file)
index 0000000..a665277
--- /dev/null
@@ -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 (file)
index 0000000..c44a438
--- /dev/null
@@ -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 (file)
index 0000000..027b20c
--- /dev/null
@@ -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 (file)
index 0000000..fa5a05c
--- /dev/null
@@ -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 (file)
index 0000000..492fc82
--- /dev/null
@@ -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 (file)
index 0000000..f714011
--- /dev/null
@@ -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 (file)
index 0000000..546a78f
--- /dev/null
@@ -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 (file)
index 0000000..9eed472
--- /dev/null
@@ -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 (file)
index 0000000..b999508
--- /dev/null
@@ -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 (file)
index 0000000..d0c96be
--- /dev/null
@@ -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 (file)
index 0000000..c6a0386
--- /dev/null
@@ -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 (file)
index 0000000..eaf417a
--- /dev/null
@@ -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 (file)
index 0000000..88b8b64
--- /dev/null
@@ -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 (file)
index 0000000..6f8a64e
--- /dev/null
@@ -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 (file)
index 0000000..bbefa6c
--- /dev/null
@@ -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 (file)
index 0000000..e0b3f35
--- /dev/null
@@ -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 (file)
index 0000000..a2a44fb
--- /dev/null
@@ -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 (file)
index 0000000..78ca68d
--- /dev/null
@@ -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 (file)
index 0000000..ea41b93
--- /dev/null
@@ -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 (file)
index 0000000..1b33418
--- /dev/null
@@ -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 (file)
index 0000000..826c341
--- /dev/null
@@ -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 (file)
index 0000000..055cdce
--- /dev/null
@@ -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 (file)
index 0000000..0f18f30
--- /dev/null
@@ -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 (file)
index 0000000..9518caa
--- /dev/null
@@ -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 (file)
index 0000000..546e9de
--- /dev/null
@@ -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 (file)
index 0000000..54e4b63
--- /dev/null
@@ -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 (file)
index 0000000..7bb47d3
--- /dev/null
@@ -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 (file)
index 0000000..c0bd1bb
--- /dev/null
@@ -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 (file)
index 0000000..b048a75
--- /dev/null
@@ -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 (file)
index 0000000..5fb23a5
--- /dev/null
@@ -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 (file)
index 0000000..504bf67
--- /dev/null
@@ -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 (file)
index 0000000..7fc24ac
--- /dev/null
@@ -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 (file)
index 0000000..d8a48a0
--- /dev/null
@@ -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 (file)
index 0000000..2e37114
--- /dev/null
@@ -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 (file)
index 0000000..2ea79cf
--- /dev/null
@@ -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 (file)
index 0000000..26b3987
--- /dev/null
@@ -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 (file)
index 0000000..13200c2
--- /dev/null
@@ -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 (file)
index 0000000..3eee7a7
--- /dev/null
@@ -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 (file)
index 0000000..fefa57e
--- /dev/null
@@ -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 (file)
index 0000000..05d3ac4
--- /dev/null
@@ -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 (file)
index 0000000..851b58d
--- /dev/null
@@ -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 (file)
index 0000000..ed63846
--- /dev/null
@@ -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 (file)
index 0000000..d32b1c0
--- /dev/null
@@ -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 (file)
index 0000000..4f02cfc
--- /dev/null
@@ -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 (file)
index 0000000..6ceaa3c
--- /dev/null
@@ -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 (file)
index 0000000..366ed01
--- /dev/null
@@ -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 (file)
index 0000000..299a7c6
--- /dev/null
@@ -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 (file)
index 0000000..2a4ed79
--- /dev/null
@@ -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 (file)
index 0000000..10b4479
--- /dev/null
@@ -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 (file)
index 0000000..2d988a3
--- /dev/null
@@ -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 (file)
index 0000000..8d54204
--- /dev/null
@@ -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 (file)
index 0000000..fd96073
--- /dev/null
@@ -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_option.t b/FS/t/part_pkg_option.t
new file mode 100644 (file)
index 0000000..6239b2d
--- /dev/null
@@ -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_taxclass.t b/FS/t/part_pkg_taxclass.t
new file mode 100644 (file)
index 0000000..bbe4073
--- /dev/null
@@ -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_pop_local.t b/FS/t/part_pop_local.t
new file mode 100644 (file)
index 0000000..4e4ad17
--- /dev/null
@@ -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 (file)
index 0000000..d20b979
--- /dev/null
@@ -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 (file)
index 0000000..bdb2a7a
--- /dev/null
@@ -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 (file)
index 0000000..467025c
--- /dev/null
@@ -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 (file)
index 0000000..c43133d
--- /dev/null
@@ -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 (file)
index 0000000..7430bc8
--- /dev/null
@@ -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 (file)
index 0000000..3567c8e
--- /dev/null
@@ -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 (file)
index 0000000..4bcc781
--- /dev/null
@@ -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 (file)
index 0000000..19e6451
--- /dev/null
@@ -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/pkg_class.t b/FS/t/pkg_class.t
new file mode 100644 (file)
index 0000000..fb3774f
--- /dev/null
@@ -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 (file)
index 0000000..ff047ba
--- /dev/null
@@ -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 (file)
index 0000000..77d3429
--- /dev/null
@@ -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 (file)
index 0000000..46377aa
--- /dev/null
@@ -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 (file)
index 0000000..e7626bd
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::prepay_credit;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/queue.t b/FS/t/queue.t
new file mode 100644 (file)
index 0000000..43e3373
--- /dev/null
@@ -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 (file)
index 0000000..cf3f91d
--- /dev/null
@@ -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 (file)
index 0000000..8eaa2cd
--- /dev/null
@@ -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 (file)
index 0000000..ac28d07
--- /dev/null
@@ -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 (file)
index 0000000..325742c
--- /dev/null
@@ -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 (file)
index 0000000..ae9c8bb
--- /dev/null
@@ -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 (file)
index 0000000..163972e
--- /dev/null
@@ -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 (file)
index 0000000..d4bd513
--- /dev/null
@@ -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 (file)
index 0000000..6e0db8f
--- /dev/null
@@ -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 (file)
index 0000000..d5e4dc9
--- /dev/null
@@ -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 (file)
index 0000000..279d5b9
--- /dev/null
@@ -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 (file)
index 0000000..4b95990
--- /dev/null
@@ -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 (file)
index 0000000..7f89ffa
--- /dev/null
@@ -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 (file)
index 0000000..a6ba134
--- /dev/null
@@ -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/session.t b/FS/t/session.t
new file mode 100644 (file)
index 0000000..c4b714e
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::session;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_Common.t b/FS/t/svc_Common.t
new file mode 100644 (file)
index 0000000..ed49e1e
--- /dev/null
@@ -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_External_Common.t b/FS/t/svc_External_Common.t
new file mode 100644 (file)
index 0000000..a0b2ea2
--- /dev/null
@@ -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 (file)
index 0000000..ed9923f
--- /dev/null
@@ -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 (file)
index 0000000..9ca78c9
--- /dev/null
@@ -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 (file)
index 0000000..e612c40
--- /dev/null
@@ -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 (file)
index 0000000..02dc112
--- /dev/null
@@ -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 (file)
index 0000000..4d91898
--- /dev/null
@@ -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 (file)
index 0000000..20a6767
--- /dev/null
@@ -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 (file)
index 0000000..d653d34
--- /dev/null
@@ -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_phone.t b/FS/t/svc_phone.t
new file mode 100644 (file)
index 0000000..15b9ca2
--- /dev/null
@@ -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 (file)
index 0000000..eb4e83f
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_www;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/type_pkgs.t b/FS/t/type_pkgs.t
new file mode 100644 (file)
index 0000000..9840180
--- /dev/null
@@ -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/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..4b9b085
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1 @@
+See httemplate/docs/index.html
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..a820cb1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,363 @@
+#!/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
+
+#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/apachew/conf.d" in httpd.conf)
+#deb (3.1+), 
+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 = localhost
+# SELFSERVICE_MACHINES = www.example.com
+# SELFSERVICE_MACHINES = web1.example.com web2.example.com
+
+#user with sudo access on SELFSERVICE_MACHINES for automated self-service
+#installation.
+SELFSERVICE_INSTALL_USER = ivan
+SELFSERVICE_INSTALL_USERADD = /usr/sbin/useradd
+#SELFSERVICE_INSTALL_USERADD = "/usr/sbin/pw useradd"
+
+#RT_ENABLED = 0
+RT_ENABLED = 1
+RT_DOMAIN = example.com
+RT_TIMEZONE = US/Pacific
+#RT_TIMEZONE = US/Eastern
+FREESIDE_URL = "http://localhost/freeside/"
+
+#for now, same db as specified in DATASOURCE... eventually, otherwise?
+RT_DB_DATABASE = freeside
+
+#---
+
+
+#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.8.8/
+
+VERSION=1.9.0cvs
+TAG=freeside_1_9_0
+
+help:
+       @echo "supported targets:"
+       @echo "                   create-database create-config"
+       @echo "                   install deploy"
+       @echo "                   configure-rt create-rt"
+       @echo "                   clean help"
+       @echo
+       @echo "                   install-docs install-perl-modules"
+       @echo "                   install-init install-apache"
+       @echo "                   install-rt"
+       @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 update-webdemo"
+
+
+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}
+         perl -p -i -e "\
+           s'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \
+           s'%%%RT_ENABLED%%%'${RT_ENABLED}'g; \
+           s'%%%MASONDATA%%%'${MASONDATA}'g;\
+         " ${MASON_HANDLER}
+       [ ! -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'%%%FREESIDE_DOCUMENT_ROOT%%%'${FREESIDE_DOCUMENT_ROOT}'g; \
+         s'%%%RT_ENABLED%%%'${RT_ENABLED}'g; \
+         s'%%%MASONDATA%%%'${MASONDATA}'g;\
+         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;\
+       " blib/lib/FS/*.pm;\
+       perl -p -i -e "\
+         s|%%%FREESIDE_EXPORT%%%|${FREESIDE_EXPORT}|g;\
+       " blib/lib/FS/part_export/*.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;\
+       " 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
+
+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-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'%%%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 ] || 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; 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
+
+deploy: install
+       ${HTTPD_RESTART}
+       ${FREESIDE_RESTART}
+
+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
+
+       echo -e "${DATASOURCE}\n${DB_USER}\n${DB_PASSWORD}" >${FREESIDE_CONF}/secrets
+       chmod 600 ${FREESIDE_CONF}/secrets
+       chown freeside ${FREESIDE_CONF}/secrets
+
+       mkdir "${FREESIDE_CONF}/conf.${DATASOURCE}"
+       rm -rf conf/registries #old dirs just won't go away
+       #cp conf/[a-z]* "${FREESIDE_CONF}/conf.${DATASOURCE}"
+       cp `ls -d conf/[a-z]* | grep -v CVS` "${FREESIDE_CONF}/conf.${DATASOURCE}"
+       chown -R freeside "${FREESIDE_CONF}/conf.${DATASOURCE}"
+
+       mkdir "${FREESIDE_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}"
+
+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
+
+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 --action insert_initial \
+       && rt/sbin/rt-setup-database --action insert --datafile ${RT_PATH}/etc/initialdata \
+       || true
+
+install-rt:
+       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
+       [ ${RT_ENABLED} -eq 1 ] && ( cd rt; make install ) || true
+
+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
+release:
+       cd /home/ivan/freeside
+       #cvs tag ${TAG}
+       cvs tag -F ${TAG}
+
+       #cd /home/ivan
+       cvs export -r ${TAG} -d freeside-${VERSION} freeside
+       tar czvf freeside-${VERSION}.tar.gz freeside-${VERSION}
+
+       scp freeside-${VERSION}.tar.gz ivan@420.am:/var/www/www.sisd.com/freeside/
+       mv freeside-${VERSION} freeside-${VERSION}.tar.gz ..
+
+update-webdemo:
+       ssh ivan@420.am '( cd freeside; cvs update -d -P )'
+       #ssh root@420.am '( cd /home/ivan/freeside; make clean; make deploy )'
+       ssh root@420.am '( cd /home/ivan/freeside; make deploy )'
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..09484d2
--- /dev/null
+++ b/README
@@ -0,0 +1,54 @@
+Freeside
+
+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 CREDITS 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>.
+
+    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.
+
+    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.
+
+Freeside is a billing and administration package for Internet Service 
+Providers.
+
+The Freeside home page is at `http://www.freeside.biz/freeside'.
+
+The documentation is at `http://www.freeside.biz/mediawiki'.
+
+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 installation, customization and support services as well as
+preconfigured appliances are available from Freeside Internet Services, Inc.
+Contact us at: http://www.freeside.biz/freeside/contact.html
+
+Ivan Kohler <ivan-freeside_readme@420.am>
+
diff --git a/SCHEMA_CHANGE b/SCHEMA_CHANGE
new file mode 100644 (file)
index 0000000..b3d77aa
--- /dev/null
@@ -0,0 +1,17 @@
+primarily:
+- edit FS/FS/Schema.pm
+
+if the changes are something other than table and/or column additions:
+- httemplate/docs/upgrade10.html
+- README.1.7.X
+
+for new tables:
+- make sure the new tables are added to FS/FS/Schema.pm and run make install-perl-modules
+- run bin/generate-table-module tablename
+- edit the resulting FS/FS/table.pm
+
+docs:
+- sorta neglected: FS/FS.pm
+- somehwat neglected: httemplate/docs/schema.html
+- really neglected: httemplate/docs/schema.dia
+
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..c90fa16
--- /dev/null
+++ b/TODO
@@ -0,0 +1,7 @@
+
+The TODO list / bug-tracking is temporarily unavailable.
+
+If you are interested in helping with development, please join the
+*development* mailing list (send a blank message to
+freeside-devel-subscribe@sisd.com) to avoid duplication of effort.
+
diff --git a/bin/add-history-records.pl b/bin/add-history-records.pl
new file mode 100755 (executable)
index 0000000..fbf9d09
--- /dev/null
@@ -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 (executable)
index 0000000..ef5dff6
--- /dev/null
@@ -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 (executable)
index 0000000..da2d73c
--- /dev/null
@@ -0,0 +1,93 @@
+#!/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 $file = "$spooldir/$machine.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 (file)
index 0000000..716ddda
--- /dev/null
@@ -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 (file)
index 0000000..d0314b4
--- /dev/null
@@ -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 (executable)
index 0000000..813e841
--- /dev/null
@@ -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 (executable)
index 0000000..91e9431
--- /dev/null
@@ -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 (executable)
index 0000000..63c4ad2
--- /dev/null
@@ -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 (executable)
index 0000000..0d77dd0
--- /dev/null
@@ -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 (executable)
index 0000000..e1a3376
--- /dev/null
@@ -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 (executable)
index 0000000..1430a58
--- /dev/null
@@ -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 (executable)
index 0000000..813e841
--- /dev/null
@@ -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 (executable)
index 0000000..63c4ad2
--- /dev/null
@@ -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/billco-upload b/bin/billco-upload
new file mode 100644 (file)
index 0000000..ce4a43d
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+AGENTNUMS="1 2 3"
+
+date=`date +"%Y%m%d"`
+dir="/usr/local/etc/freeside/export.DBI:Pg:dbname=freeside/cust_bill"
+cd "$dir"
+
+for AGENTNUM in $AGENTNUMS; do
+
+  for a in header detail; do
+    mv agentnum$AGENTNUM-$a.csv agentnum$AGENTNUM-$date-$a.csv
+  done
+
+  zip agentnum$AGENTNUM-$date.zip agentnum$AGENTNUM-$date-header.csv agentnum$AGENTNUM-$date-detail.csv
+
+  echo $dir/agentnum$AGENTNUM-$date.zip
+
+done
+
diff --git a/bin/bind.export b/bin/bind.export
new file mode 100755 (executable)
index 0000000..286e43a
--- /dev/null
@@ -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 (executable)
index 0000000..1cdf567
--- /dev/null
@@ -0,0 +1,234 @@
+#!/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
+# -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);
+getopts("p:n:sc:d");
+
+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 ] [ -f ] 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 (file)
index 0000000..44c3e36
--- /dev/null
@@ -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 (executable)
index 0000000..6e0d103
--- /dev/null
@@ -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/cdr_calltype.import b/bin/cdr_calltype.import
new file mode 100755 (executable)
index 0000000..a998284
--- /dev/null
@@ -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 (executable)
index 0000000..fda3883
--- /dev/null
@@ -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/create-fetchmailrc b/bin/create-fetchmailrc
new file mode 100644 (file)
index 0000000..11bde0c
--- /dev/null
@@ -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/customer-faker b/bin/customer-faker
new file mode 100755 (executable)
index 0000000..d57e5e1
--- /dev/null
@@ -0,0 +1,122 @@
+#!/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 $agentnum = 1;
+my $refnum = 1;
+
+my @pkgs = ( 2, 3, 4 );
+#my @pkgs = ( 4, 5, 6 );
+my $svcpart = 2;
+
+use vars qw( $opt_p );
+getopts('p:');
+
+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 ] user num_fakes\n";
+}
diff --git a/bin/expand-country b/bin/expand-country
new file mode 100755 (executable)
index 0000000..c6f2a1f
--- /dev/null
@@ -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 (file)
index 0000000..f154430
--- /dev/null
@@ -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/find-overapplied b/bin/find-overapplied
new file mode 100644 (file)
index 0000000..7973cef
--- /dev/null
@@ -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 (executable)
index 0000000..dc4abd7
--- /dev/null
@@ -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/freeside-init b/bin/freeside-init
new file mode 100755 (executable)
index 0000000..fe12931
--- /dev/null
@@ -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 (file)
index 0000000..76643b8
--- /dev/null
@@ -0,0 +1,229 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw( qsearch );
+use FS::part_bill_event;
+use FS::part_event;
+use FS::cust_bill_event;
+use FS::cust_event;
+
+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'     => 'NOTYET', #XXX "if balance" becomes a balance condition
+  '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'                 => 'NOTYET',
+  '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'             => 'NOTYET', 
+  '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 plandata2option names!!!
+
+  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' => 0 );
+  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;
+
+  }
+  
+  #my $derror = $part_bill_event->delete;
+  #die "error removing part_bill_event: $derror\n" if $derror;
+
+  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...)
+
+=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 (executable)
index 0000000..d5fd703
--- /dev/null
@@ -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 (executable)
index 0000000..c603365
--- /dev/null
@@ -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 (file)
index 0000000..fdfcc08
--- /dev/null
@@ -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 (executable)
index 0000000..ede80b0
--- /dev/null
@@ -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 (executable)
index 0000000..b0f3ac5
--- /dev/null
@@ -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 (executable)
index 0000000..1584197
--- /dev/null
@@ -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 (executable)
index 0000000..07f7b61
--- /dev/null
@@ -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 (executable)
index 0000000..4e4769e
--- /dev/null
@@ -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 (executable)
index 0000000..3de0137
--- /dev/null
@@ -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 (executable)
index 0000000..cb4ba7f
--- /dev/null
@@ -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 (executable)
index 0000000..af21c05
--- /dev/null
@@ -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 (executable)
index 0000000..509feed
--- /dev/null
@@ -0,0 +1,92 @@
+#!/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 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 (executable)
index 0000000..73fd29e
--- /dev/null
@@ -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/import-county-tax-rates b/bin/import-county-tax-rates
new file mode 100755 (executable)
index 0000000..05798c9
--- /dev/null
@@ -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/ispman.ldap.import b/bin/ispman.ldap.import
new file mode 100755 (executable)
index 0000000..7495f47
--- /dev/null
@@ -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/mapsecrets2access_user b/bin/mapsecrets2access_user
new file mode 100755 (executable)
index 0000000..945f130
--- /dev/null
@@ -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->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 (executable)
index 0000000..509ef3e
--- /dev/null
@@ -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/passwd.import b/bin/passwd.import
new file mode 100755 (executable)
index 0000000..8ab9e2a
--- /dev/null
@@ -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 (executable)
index 0000000..03316e1
--- /dev/null
@@ -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 (file)
index 0000000..ad69fbd
--- /dev/null
@@ -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-version b/bin/pg-version
new file mode 100755 (executable)
index 0000000..b6cddb6
--- /dev/null
@@ -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/pod2x b/bin/pod2x
new file mode 100755 (executable)
index 0000000..6b7153f
--- /dev/null
+++ b/bin/pod2x
@@ -0,0 +1,148 @@
+#!/usr/bin/perl -w
+
+use strict;
+use WWW::Mediawiki::Client;
+#sub WWW::Mediawiki::Client::pagename_to_url {
+#    my ($self, $name, $action) = @_;
+#    WWW::Mediawiki::Client::URLConstructionException->throw(
+#            error => 'No action supplied.',
+#        ) unless $action;
+#    WWW::Mediawiki::Client::URLConstructionException->throw(
+#            error => "Page name $name ends with '.wiki'.",
+#        ) if $name =~ /.wiki$/;
+#    my $char = $self->space_substitute;
+#    $name =~ s/ /$char/;
+#    my $lang = $self->language_code;
+#    my $host = $self->host;
+#    $host =~ s/__LANG__/$lang/g;
+#    my $wiki_path = $self->wiki_path;
+#    $wiki_path =~ s/__LANG__/$lang/g;
+#    my $protocol = $self->protocol;
+#    return "$protocol://$host/$wiki_path?" . ACTION . "=$action&" . TITLE . "=$name" . '&wpRecreate=1';
+#}
+
+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");
+}
+
+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;
+
+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");
+
+  open(RAW, "<$html/$name.rawwiki") or die $!;
+  open(WIKI,">$html/$name.wiki"   ) or die $!;
+  while (<RAW>) {
+    s/\[\[([^#p][^\]]*)\]\]/"[[$html\/". w_e($1). "|$1]]"/ge;
+    print WIKI $_;
+  }
+  close RAW;
+  close WIKI;
+
+  print "  uploading to ". $mvs->filename_to_pagename("$html/$name.wiki"). "\n";
+  $mvs->commit_message( 'import from POD' );
+  $mvs->do_commit("$html/$name.wiki");
+
+}
+
+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 (executable)
index 0000000..61380da
--- /dev/null
@@ -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 (executable)
index 0000000..12c138b
--- /dev/null
@@ -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-schema b/bin/print-schema
new file mode 100755 (executable)
index 0000000..886e325
--- /dev/null
@@ -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 (executable)
index 0000000..66ac5de
--- /dev/null
@@ -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.import b/bin/rate.import
new file mode 100755 (executable)
index 0000000..fdd756d
--- /dev/null
@@ -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/reset-cust_credit-otaker b/bin/reset-cust_credit-otaker
new file mode 100755 (executable)
index 0000000..93002d0
--- /dev/null
@@ -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 (executable)
index 0000000..7f83ef4
--- /dev/null
@@ -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 (executable)
index 0000000..7bef0bb
--- /dev/null
@@ -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 (executable)
index 0000000..b027542
--- /dev/null
@@ -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 (file)
index 0000000..75d554f
--- /dev/null
@@ -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 (file)
index 0000000..ef745fc
--- /dev/null
@@ -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 (file)
index 0000000..2dc1d3b
--- /dev/null
@@ -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 (executable)
index 0000000..7957011
--- /dev/null
@@ -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 (executable)
index 0000000..0798c1a
--- /dev/null
@@ -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 (executable)
index 0000000..b7d0166
--- /dev/null
@@ -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 (file)
index 0000000..e75f65b
--- /dev/null
@@ -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 (executable)
index 0000000..2218a3f
--- /dev/null
@@ -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 (executable)
index 0000000..2c2d124
--- /dev/null
@@ -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.import b/bin/svc_acct.import
new file mode 100755 (executable)
index 0000000..aff26b9
--- /dev/null
@@ -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 (executable)
index 0000000..9e3d38b
--- /dev/null
@@ -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 (executable)
index 0000000..980fa00
--- /dev/null
@@ -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 (executable)
index 0000000..435dd5f
--- /dev/null
@@ -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 (executable)
index 0000000..c13912c
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/perl -w
+
+# sysvshell export
+
+use strict;
+use File::Rsync;
+use Net::SSH qw(ssh);
+use FS::UID qw(adminsuidsetup datasrc);
+use FS::Record qw(qsearch qsearchs);
+use FS::part_export;
+use FS::cust_svc;
+use FS::svc_acct;
+
+my @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' );
+
+my $user = shift or die &usage;
+adminsuidsetup $user;
+
+my $spooldir = "/usr/local/etc/freeside/export.". datasrc;
+#my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/shell";
+
+my @sysv_exports = qsearch('part_export', { 'exporttype' => 'sysvshell' } );
+
+my $rsync = File::Rsync->new({
+  rsh     => 'ssh',
+#  dry_run => 1,
+});
+
+foreach my $export ( @sysv_exports ) {
+  my $machine = $export->machine;
+  my $prefix = "$spooldir/$machine";
+  mkdir $prefix, 0700 unless -d $prefix;
+
+  #LOCKING!!!
+
+  ( open(SHADOW,">$prefix/shadow")
+    #!!!  and flock(SHADOW,LOCK_EX|LOCK_NB)
+  ) or die "Can't open $prefix/shadow: $!";
+  ( open(PASSWD,">$prefix/passwd")
+    #!!!  and flock(PASSWD,LOCK_EX|LOCK_NB)
+  ) or die "Can't open $prefix/passwd: $!";
+
+  chmod 0644, "$prefix/passwd";
+  chmod 0600, "$prefix/shadow";
+
+  my @svc_acct = $export->svc_x;
+
+  next unless @svc_acct;
+
+  foreach my $svc_acct ( sort { $a->uid <=> $b->uid } @svc_acct ) {
+
+    my $password = $svc_acct->_password;
+    my $cpassword;
+    #if ( ( length($password) <= 8 )
+    if ( ( length($password) <= 12 )
+         && ( $password ne '*' )
+         && ( $password ne '!!' )
+         && ( $password ne '' )
+    ) {
+      $cpassword=crypt($password,
+                       $saltset[int(rand(64))].$saltset[int(rand(64))]
+      );
+      # MD5 !!!!
+    } else {
+      $cpassword=$password;
+    }
+
+    ###
+    # FORMAT OF THE PASSWD FILE HERE
+    print PASSWD join(":",
+      $svc_acct->username,
+      'x', # "##". $username,
+      $svc_acct->uid,
+      $svc_acct->gid,
+      $svc_acct->finger,
+      $svc_acct->dir,
+      $svc_acct->shell,
+    ), "\n";
+
+    ###
+    # FORMAT OF THE SHADOW FILE HERE
+    print SHADOW join(":",
+      $svc_acct->username,
+      $cpassword,
+      '',
+      '',
+      '',
+      '',
+      '',
+      '',
+      '',
+    ), "\n";
+
+  }
+
+  #!!! flock(SHADOW,LOCK_UN);
+  #!!! flock(PASSWD,LOCK_UN);
+  close SHADOW;
+  close PASSWD;
+
+  $rsync->exec( {
+    src  => "$prefix/shadow",
+    dest => "root\@$machine:/etc/shadow"
+  } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+
+  $rsync->exec( {
+    src  => "$prefix/passwd",
+    dest => "root\@$machine:/etc/passwd"
+  } ) or die "rsync to $machine failed: ". join(" / ", $rsync->err);
+
+  # UNLOCK!!
+}
diff --git a/conf/agent_defaultpkg b/conf/agent_defaultpkg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/conf/alerter_template b/conf/alerter_template
new file mode 100644 (file)
index 0000000..6fb66b7
--- /dev/null
@@ -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 (file)
index 0000000..e7e3bab
--- /dev/null
@@ -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 (file)
index 0000000..3824862
--- /dev/null
@@ -0,0 +1,2 @@
+1234 Example Lane
+Exampleton, CA  54321
diff --git a/conf/company_name b/conf/company_name
new file mode 100644 (file)
index 0000000..2cd5323
--- /dev/null
@@ -0,0 +1 @@
+ExampleCo
diff --git a/conf/cust_pkg-change_svcpart b/conf/cust_pkg-change_svcpart
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/conf/declinetemplate b/conf/declinetemplate
new file mode 100644 (file)
index 0000000..14b8c60
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..deb396a
--- /dev/null
@@ -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 (file)
index 0000000..110ec8f
--- /dev/null
@@ -0,0 +1 @@
+ivan-unconfigured-freeside-installation@420.am
diff --git a/conf/invoice_html b/conf/invoice_html
new file mode 100644 (file)
index 0000000..9d97243
--- /dev/null
@@ -0,0 +1,166 @@
+<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?$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="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">I</FONT><FONT SIZE="+2">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>
+      <td align="right">
+        Terms: <%= $terms %><BR>
+        <%= $po_line %>
+      </td>
+    </tr>
+
+  </table>
+
+  <%=
+      foreach my $section ( @sections ) {
+        $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>'.
+            '<th align="center">Ref</th>'.
+            '<th align="left">Description</th>'.
+            '<th align="right">Amount</th>'.
+          '</tr>';
+
+        foreach my $line (
+          grep { ( scalar(@sections) > 1 
+                 ? $section->{'description'} eq $_->{'section'}->{'description'}
+                 : 1
+               ) }
+          @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>'
+          }
+        }
+
+
+        if (scalar(@sections) > 1) {
+          my $style = 'border-top: 3px solid #000000;'.
+                      'border-bottom: 3px solid #000000;';
+          $OUT .=
+            '<tr class="invoice_totaldesc">'.
+              qq(<td style="$style">&nbsp;</td>).
+              qq(<td align="left" style="$style">).
+                $section->{'description'}. ' Total </td>'.
+              qq(<td align="right" style="$style">).
+                $section->{'subtotal'}. '</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_html_statement b/conf/invoice_html_statement
new file mode 100644 (file)
index 0000000..4e4d259
--- /dev/null
@@ -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?$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_latex b/conf/invoice_latex
new file mode 100644 (file)
index 0000000..6a81c4c
--- /dev/null
@@ -0,0 +1,260 @@
+%% file: Standard Multipage.tex\r
+%% Purpose: Multipage bill template for e-Bills\r
+%% \r
+%% Created by Mark Asplen-Taylor\r
+%% Asplen Management Ltd\r
+%% www.asplen.co.uk\r
+%%\r
+%% Modified for Freeside by Kristian Hoffman\r
+%%\r
+%% Changes\r
+%%     0.1     4/12/00 Created\r
+%%     0.2     18/10/01        More fields added\r
+%%     1.0     16/11/01        RELEASED\r
+%%     1.2     16/10/02        Invoice number added\r
+%%     1.3     2/12/02 Logo graphic added\r
+%%     1.4     7/2/03  Multipage headers/footers added\r
+%%      n/a     forked for Freeside; checked into CVS\r
+%%\r
+\r
+\documentclass[letterpaper]{article}\r
+\r
+\usepackage{fancyhdr,lastpage,ifthen,longtable,afterpage}\r
+\usepackage{graphicx}                  % required for logo graphic\r
+\r
+\addtolength{\voffset}{-0.0cm}         % top margin to top of header\r
+\addtolength{\hoffset}{-0.6cm}         % left margin on page\r
+\addtolength{\topmargin}{-1.25cm}      % top margin to top of header\r
+\setlength{\headheight}{2.0cm}                 % height of header\r
+\setlength{\headsep}{1.0cm}            % between header and text\r
+\setlength{\footskip}{1.0cm}           % bottom of footer from bottom of text\r
+\r
+%\addtolength{\textwidth}{2.1in}       % width of text\r
+\setlength{\textwidth}{19.5cm}\r
+\setlength{\textheight}{19.5cm}\r
+\setlength{\oddsidemargin}{-0.9cm}     % odd page left margin\r
+\setlength{\evensidemargin}{-0.9cm}    % even page left margin\r
+\r
+\renewcommand{\headrulewidth}{0pt}\r
+\renewcommand{\footrulewidth}{1pt}\r
+\r
+% Adjust the inset of the mailing address\r
+\newcommand{\addressinset}[1][]{\hspace{1.0cm}}\r
+\r
+% Adjust the inset of the return address and logo\r
+\newcommand{\returninset}[1][]{\hspace{-0.25cm}}\r
+\r
+% New command for address lines i.e. skip them if blank\r
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}}\r
+\r
+% Inserts dollar symbol\r
+\newcommand{\dollar}[1][]{\symbol{36}}\r
+\r
+% Remove plain style header/footer\r
+\fancypagestyle{plain}{\r
+  \fancyhead{}\r
+}\r
+\fancyhf{}\r
+\r
+% Define fancy header/footer for first and subsequent pages\r
+\fancyfoot[C]{\r
+  \ifthenelse{\equal{\thepage}{1}}\r
+  { % First page\r
+    \small{\r
+[@-- $footer --@]\r
+    }\r
+  }\r
+  { % ... pages\r
+    \small{\r
+[@-- $smallfooter --@]\r
+    }\r
+  }\r
+}\r
+\r
+\fancyfoot[R]{\r
+  \ifthenelse{\equal{\thepage}{1}}\r
+  { % First page\r
+  }\r
+  { % ... pages\r
+    \small{\thepage\ of \pageref{LastPage}}\r
+  }\r
+}\r
+\r
+\fancyhead[L]{\r
+  \ifthenelse{\equal{\thepage}{1}}\r
+  { % First page\r
+    \returninset\r
+    \makebox{\r
+      \begin{tabular}{ll}\r
+        \includegraphics{[@-- $logo_file --@]} &\r
+        \begin{minipage}[b]{5.5cm}\r
+[@-- $returnaddress --@]\r
+        \end{minipage}\r
+      \end{tabular}\r
+    }\r
+  }\r
+  { % ... pages\r
+    %\includegraphics{[@-- $logo_file --@]}    % Uncomment if you want the logo on all pages.\r
+  }\r
+}\r
+\r
+\fancyhead[R]{\r
+  \ifthenelse{\equal{\thepage}{1}}\r
+  { % First page\r
+    \begin{tabular}{ccc}\r
+    Invoice date & Invoice \#& Customer\#\\\r
+    \vspace{0.2cm}\r
+    \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]} \\\hline\r
+    \rule{0pt}{5ex} &~~ \huge{\textsc{Invoice}} & \\\r
+    \vspace{-0.2cm}\r
+     & & \\\hline\r
+    \end{tabular}\r
+  }\r
+  { % ... pages\r
+    \small{\r
+      \begin{tabular}{lll}\r
+      Invoice date & Invoice \#& Customer\#\\\r
+      \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]} & \textbf{[@-- $custnum --@]}\\\r
+      \end{tabular}\r
+    }\r
+  }\r
+}\r
+\r
+\pagestyle{fancy}\r
+\r
+\r
+%% Font options are:\r
+%%     bch     Bitsream Charter\r
+%%     put     Utopia\r
+%%     phv     Adobe Helvetica\r
+%%     pnc     New Century Schoolbook\r
+%%     ptm     Times\r
+%%     pcr     Courier\r
+\r
+\renewcommand{\familydefault}{phv}\r
+\r
+\r
+% Commands for freeside description...\r
+\newcommand{\FSdesc}[3]{\r
+  \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &\r
+  \textbf{#2} &\r
+  \multicolumn{1}{r}{\textbf{\dollar #3}}\\\r
+}\r
+% ...extended description...\r
+\newcommand{\FSextdesc}[1]{\r
+  \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &\r
+  \multicolumn{2}{l}{\small{~-~#1}}\\\r
+}\r
+% ...and total line items.\r
+\newcommand{\FStotaldesc}[2]{\r
+  & \multicolumn{1}{l}{#1} & #2\\\r
+}\r
+\r
+\r
+\begin{document}\r
+%\r
+%%     Headers and footers defined for the first page\r
+%\r
+%%     The LH Heading comprising logo\r
+%%     UNCOMMENT the following FOUR lines and change the path if necssary to provide a logo\r
+%\r
+%%     The Heading comprising isue date, customer ref & INVOICE name\r
+%\r
+%%     Header & footer changes for subsequent pages\r
+%\r
+%\r
+%\r
+\begin{tabular}{ll}\r
+\addressinset \rule{0cm}{0cm} &\r
+\makebox{\r
+\begin{minipage}[t]{5.0cm}\r
+\vspace{0.25cm}\r
+\textbf{[@-- $payname --@]}\\\r
+\addressline{[@-- $company --@]}\r
+\addressline{[@-- $address1 --@]}\r
+\addressline{[@-- $address2 --@]}\r
+\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}\r
+\addressline{[@-- $country --@]}\r
+\end{minipage}}\r
+\end{tabular}\r
+\hfill\r
+\makebox{\r
+\begin{minipage}[t]{6.4cm}\r
+\begin{flushright}\r
+Terms: [@-- $terms --@]\\\r
+[@-- $po_line --@]\\\r
+\end{flushright}\r
+\end{minipage}}\r
+\vspace{1.5cm}\r
+%\r
+[@--\r
+  foreach my $section ( @sections ) {\r
+    $OUT .= '\section*{\textsc{';\r
+    $OUT .= ($section->{'description'}) ? $section->{'description'} : 'Charges';\r
+    $OUT .= '}}\begin{longtable}{clr}';\r
+    $OUT .= '\hline';\r
+    $OUT .= '\rule{0pt}{2.5ex}';\r
+    $OUT .= '\makebox[1.4cm]{\textbf{Ref}} & ';\r
+    $OUT .= '\makebox[12.8cm][l]{\textbf{Description}} & ';\r
+    $OUT .= '\makebox[2.5cm][r]{\textbf{Amount}} \\\\';\r
+    $OUT .= '\hline';\r
+    $OUT .= '\endfirsthead';\r
+    $OUT .= '\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\\';\r
+    $OUT .= '\hline';\r
+    $OUT .= '\rule{0pt}{2.5ex}';\r
+    $OUT .= '\makebox[1.4cm]{\textbf{Ref}} & ';\r
+    $OUT .= '\makebox[12.8cm][l]{\textbf{Description}} & ';\r
+    $OUT .= '\makebox[2.5cm][r]{\textbf{Amount}} \\\\';\r
+    $OUT .= '\hline';\r
+    $OUT .= '\endhead';\r
+    $OUT .= '\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\\\';\r
+    $OUT .= '\endfoot';\r
+    $OUT .= '\hline';\r
+\r
+    if (scalar(@sections) > 1) {\r
+      $OUT .= '\FStotaldesc{' . $section->{'description'} . ' Total}' .\r
+              '{' . $section->{'subtotal'} . '}' . "\n";\r
+    }\r
+\r
+    if ($section == $sections[$#sections]) {\r
+      foreach my $line (@total_items) {\r
+        $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .\r
+                '{' . $line->{'total_amount'} . '}' . "\n";\r
+      }\r
+    }\r
+\r
+    $OUT .= '\hline';\r
+    $OUT .= '\endlastfoot';\r
+\r
+    foreach my $line (\r
+      grep { ( scalar( @sections ) > 1 \r
+             ? $section->{'description'} eq $_->{'section'}->{'description'}\r
+             : 1\r
+           ) }\r
+      @detail_items )\r
+    {\r
+      my $ext_description = $line->{'ext_description'};\r
+  \r
+      # Don't break-up small packages.\r
+      my $rowbreak = @$ext_description < 5 ? '*' : '';\r
+  \r
+      $OUT .= "\\hline\n";\r
+      $OUT .= '\FSdesc{' . $line->{'ref'} . '}{' . $line->{'description'} . '}' .\r
+              '{' . $line->{'amount'} . "}${rowbreak}\n";\r
+\r
+      foreach my $ext_desc (@$ext_description) {\r
+        $ext_desc = substr($ext_desc, 0, 80) . '...'\r
+          if (length($ext_desc) > 80);\r
+        $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n";\r
+      }\r
+\r
+    }\r
+\r
+    $OUT .= '\end{longtable}';\r
+\r
+  }\r
+\r
+--@]\r
+\vfill\r
+[@-- $notes --@]\r
+\end{document}\r
diff --git a/conf/invoice_latex.diff b/conf/invoice_latex.diff
new file mode 100644 (file)
index 0000000..b66a522
--- /dev/null
@@ -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\r
+ %% www.asplen.co.uk\r
+ %%\r
+-%% Modified for Freeside by Ivan Kohler\r
++%% Modified for Freeside by Ivan Kohler and Kristian Hoffman\r
+ %%\r
+ %% Changes\r
+ %%    0.1     4/12/00 Created\r
+@@ -61,7 +61,7 @@
+ %%    Headers and footers defined for the first page\r
+ \fancyfoot[CO,CE]{\small{\r
+ \begin{tabular}{c}\r
+-$footer\r
++[@-- $footer --@]\r
+ \end{tabular}}}\r
+ %\r
+ %%    The LH Heading comprising logo\r
+@@ -76,7 +76,7 @@
+ \begin{tabular}{rcl}\r
+ Invoice date & & Invoice number \\\r
+ \vspace{0.2cm}\r
+-\textbf{$date} & & \textbf{$invnum} \\\hline\r
++\textbf{[@-- $date --@]} & & \textbf{[@-- $invnum --@]} \\\hline\r
+ \rule{0pt}{5ex} &~~ \huge{\textsc{Invoice}}& \\\r
+ \vspace{-0.2cm}\r
+  & & \\\hline\r
+@@ -85,71 +85,76 @@
+ %%    Header & footer changes for subsequent pages\r
+ %\r
+ \afterpage{ \fancyfoot[RO,RE]{\small{\thepage\ of \pageref{LastPage}}} }\r
+-\afterpage{ \fancyfoot[CO,CE]{\small{$smallfooter}} }\r
++\afterpage{ \fancyfoot[CO,CE]{\small{[@-- $smallfooter --@]}} }\r
+ \afterpage{ \fancyhead[LO,LE]{\small{}} }\r
+ \afterpage{ \fancyhead[RO,RE]{\small{\r
+ \begin{tabular}{ll}\r
+ Invoice date & Invoice number\\\r
+-\textbf{$date} & \textbf{$invnum}\\\r
++\textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]}\\\r
+ \end{tabular}}} }\r
+ %\r
+ %\r
+ \makebox{\r
+ \begin{minipage}[t]{2.9in}\r
+ \vspace{0.20in}\r
+-\textbf{$payname}\\\r
+-\addressline{$company}\r
+-\addressline{$address1}\r
+-\addressline{$address2}\r
+-\addressline{$city, $state  $zip}\r
+-\addressline{$country}\r
++\textbf{[@-- $payname --@]}\\\r
++\addressline{[@-- $company --@]}\r
++\addressline{[@-- $address1 --@]}\r
++\addressline{[@-- $address2 --@]}\r
++\addressline{[@-- $city --@], [@-- $state --@]  [@-- $zip --@]}\r
++\addressline{[@-- $country --@]}\r
+ \end{minipage}}\r
+ \hfill\r
+ \makebox{\r
+ \begin{minipage}[t]{2.5in}\r
+ \begin{flushright}\r
+-Terms: $terms\\\r
+-$po_line\\\r
++Terms: [@-- $terms --@]\\\r
++[@-- $po_line --@]\\\r
+ \end{flushright}\r
+ \end{minipage}}\r
+ \vspace{0.5cm}\r
+ %\r
+ \section*{\textsc{Charges}}\r
+-\begin{longtable}{|c|l|c|r|r|}\r
++\begin{longtable}{|c|l|r|}\r
+ \hline\r
+ \rule{0pt}{2.5ex}\r
+ \makebox[1.4cm]{\textbf{Ref}} & \r
+-\makebox[7.9cm][l]{\textbf{Description}} & \r
+-\makebox[1.3cm][c]{\textbf{Quantity}} & \r
+-\makebox[2.5cm][r]{\textbf{Unit Price}} & \r
+-\makebox[2.5cm][r]{\textbf{Amount}} \\\r
++\makebox[13cm][l]{\textbf{Description}} & \r
++\makebox[2cm][r]{\textbf{Amount}} \\\r
+ \hline\r
+ \endfirsthead\r
+-\multicolumn{5}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\r
++\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\r
+ \hline\r
+ \rule{0pt}{2.5ex}\r
+ \makebox[1.4cm]{\textbf{Ref}} & \r
+-\makebox[7.9cm][l]{\textbf{Description}} & \r
+-\makebox[1.3cm][c]{\textbf{Quantity}} & \r
+-\makebox[2.5cm][r]{\textbf{Unit Price}} & \r
+-\makebox[2.5cm][r]{\textbf{Amount}} \\\r
++\makebox[13cm][l]{\textbf{Description}} & \r
++\makebox[2cm][r]{\textbf{Amount}} \\\r
+ \hline\r
+ \endhead\r
+-\multicolumn{5}{r}{\rule{0pt}{2.5ex}/cont...}\\\r
++\multicolumn{3}{r}{\rule{0pt}{2.5ex}/cont...}\\\r
+ \endfoot\r
+-%%TotalDetails\r
+- & \multicolumn{3}{l}{$total_item}    & $total_amount\\\r
+-%%EndTotalDetails\r
++[@--\r
++\r
++  foreach my $line (@total_items) {\r
++    $OUT .= ' & \multicolumn{1}{l}{' . $line->{'total_item'} . '} & ' .\r
++            $line->{'total_amount'} . '\\\\' . "\n";\r
++  }\r
++\r
++--@]\r
+ \hline\r
+ \endlastfoot\r
+-%%Detail\r
+-\rule{0pt}{2.5ex}$ref & \r
+-\begin{tabular}{l}\r
+-$description\tabularnewline\r
+-\end{tabular}\r
+-& $quantity & \dollar $amount & \dollar $amount\\\hline\r
+-%%EndDetail\r
++[@--\r
++\r
++  foreach my $line (@detail_items) {\r
++    $OUT .= '\rule{0pt}{2.5ex}' . $line->{'ref'} . ' &' . "\n".\r
++            '\begin{tabular}{l}' . "\n".\r
++            $line->{'description'} . '\tabularnewline' . "\n".\r
++            '\end{tabular}' . "\n".\r
++            '& \dollar ' . $line->{'amount'} . '\\\\\\hline' . "\n";\r
++  }\r
++\r
++--@]\r
+ \end{longtable}\r
+ \vfill\r
+-$notes\r
++[@-- $notes --@]\r
+ \end{document}\r
diff --git a/conf/invoice_latex_statement b/conf/invoice_latex_statement
new file mode 100644 (file)
index 0000000..302306a
--- /dev/null
@@ -0,0 +1,244 @@
+%% file: Standard Multipage.tex\r
+%% Purpose: Multipage bill template for e-Bills\r
+%% \r
+%% Created by Mark Asplen-Taylor\r
+%% Asplen Management Ltd\r
+%% www.asplen.co.uk\r
+%%\r
+%% Modified for Freeside by Kristian Hoffman\r
+%%\r
+%% Changes\r
+%%     0.1     4/12/00 Created\r
+%%     0.2     18/10/01        More fields added\r
+%%     1.0     16/11/01        RELEASED\r
+%%     1.2     16/10/02        Invoice number added\r
+%%     1.3     2/12/02 Logo graphic added\r
+%%     1.4     7/2/03  Multipage headers/footers added\r
+%%      n/a     forked for Freeside; checked into CVS\r
+%%\r
+\r
+\documentclass[letterpaper]{article}\r
+\r
+\usepackage{fancyhdr,lastpage,ifthen,longtable,afterpage}\r
+\usepackage{graphicx}                  % required for logo graphic\r
+\r
+\addtolength{\voffset}{-0.0cm}         % top margin to top of header\r
+\addtolength{\hoffset}{-0.6cm}         % left margin on page\r
+\addtolength{\topmargin}{-1.25cm}      % top margin to top of header\r
+\setlength{\headheight}{2.0cm}                 % height of header\r
+\setlength{\headsep}{1.0cm}            % between header and text\r
+\setlength{\footskip}{1.0cm}           % bottom of footer from bottom of text\r
+\r
+%\addtolength{\textwidth}{2.1in}       % width of text\r
+\setlength{\textwidth}{19.5cm}\r
+\setlength{\textheight}{19.5cm}\r
+\setlength{\oddsidemargin}{-0.9cm}     % odd page left margin\r
+\setlength{\evensidemargin}{-0.9cm}    % even page left margin\r
+\r
+\renewcommand{\headrulewidth}{0pt}\r
+\renewcommand{\footrulewidth}{1pt}\r
+\r
+% Adjust the inset of the mailing address\r
+\newcommand{\addressinset}[1][]{\hspace{1.0cm}}\r
+\r
+% Adjust the inset of the return address and logo\r
+\newcommand{\returninset}[1][]{\hspace{-0.25cm}}\r
+\r
+% New command for address lines i.e. skip them if blank\r
+\newcommand{\addressline}[1]{\ifthenelse{\equal{#1}{}}{}{#1\newline}}\r
+\r
+% Inserts dollar symbol\r
+\newcommand{\dollar}[1][]{\symbol{36}}\r
+\r
+% Remove plain style header/footer\r
+\fancypagestyle{plain}{\r
+  \fancyhead{}\r
+}\r
+\fancyhf{}\r
+\r
+% Define fancy header/footer for first and subsequent pages\r
+\fancyfoot[C]{\r
+  \ifthenelse{\equal{\thepage}{1}}\r
+  { % First page\r
+    \small{\r
+[@-- $footer --@]\r
+    }\r
+  }\r
+  { % ... pages\r
+    \small{\r
+[@-- $smallfooter --@]\r
+    }\r
+  }\r
+}\r
+\r
+\fancyfoot[R]{\r
+  \ifthenelse{\equal{\thepage}{1}}\r
+  { % First page\r
+  }\r
+  { % ... pages\r
+    \small{\thepage\ of \pageref{LastPage}}\r
+  }\r
+}\r
+\r
+\fancyhead[L]{\r
+  \ifthenelse{\equal{\thepage}{1}}\r
+  { % First page\r
+    \returninset\r
+    \makebox{\r
+      \begin{tabular}{ll}\r
+        \includegraphics{[@-- $conf_dir --@]/logo.eps} &\r
+        \begin{minipage}[b]{5.5cm}\r
+[@-- $returnaddress --@]\r
+        \end{minipage}\r
+      \end{tabular}\r
+    }\r
+  }\r
+  { % ... pages\r
+    %\includegraphics{[@-- $conf_dir --@]/logo.eps}    % Uncomment if you want the logo on all pages.\r
+  }\r
+}\r
+\r
+\fancyhead[R]{\r
+  \ifthenelse{\equal{\thepage}{1}}\r
+  { % First page\r
+    \begin{tabular}{rcl}\r
+    Invoice date & & Invoice number \\\r
+    \vspace{0.2cm}\r
+    \textbf{[@-- $date --@]} & & \textbf{[@-- $invnum --@]} \\\hline\r
+    \rule{0pt}{5ex} &~~ \huge{\textsc{Statement}} & \\\r
+    \vspace{-0.2cm}\r
+     & & \\\hline\r
+    \end{tabular}\r
+  }\r
+  { % ... pages\r
+    \small{\r
+      \begin{tabular}{ll}\r
+      Invoice date & Invoice number\\\r
+      \textbf{[@-- $date --@]} & \textbf{[@-- $invnum --@]}\\\r
+      \end{tabular}\r
+    }\r
+  }\r
+}\r
+\r
+\pagestyle{fancy}\r
+\r
+\r
+%% Font options are:\r
+%%     bch     Bitsream Charter\r
+%%     put     Utopia\r
+%%     phv     Adobe Helvetica\r
+%%     pnc     New Century Schoolbook\r
+%%     ptm     Times\r
+%%     pcr     Courier\r
+\r
+\renewcommand{\familydefault}{phv}\r
+\r
+\r
+% Commands for freeside description...\r
+\newcommand{\FSdesc}[3]{\r
+  \multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &\r
+  \textbf{#2} &\r
+  \multicolumn{1}{r}{\textbf{\dollar #3}}\\\r
+}\r
+% ...extended description...\r
+\newcommand{\FSextdesc}[1]{\r
+  \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &\r
+  \multicolumn{2}{l}{\small{~-~#1}}\\\r
+}\r
+% ...and total line items.\r
+\newcommand{\FStotaldesc}[2]{\r
+  & \multicolumn{1}{l}{#1} & #2\\\r
+}\r
+\r
+\r
+\begin{document}\r
+%\r
+%%     Headers and footers defined for the first page\r
+%\r
+%%     The LH Heading comprising logo\r
+%%     UNCOMMENT the following FOUR lines and change the path if necssary to provide a logo\r
+%\r
+%%     The Heading comprising isue date, customer ref & INVOICE name\r
+%\r
+%%     Header & footer changes for subsequent pages\r
+%\r
+%\r
+%\r
+\begin{tabular}{ll}\r
+\addressinset \rule{0cm}{0cm} &\r
+\makebox{\r
+\begin{minipage}[t]{5.0cm}\r
+\vspace{0.25cm}\r
+\textbf{[@-- $payname --@]}\\\r
+\addressline{[@-- $company --@]}\r
+\addressline{[@-- $address1 --@]}\r
+\addressline{[@-- $address2 --@]}\r
+\addressline{[@-- $city --@], [@-- $state --@]~~[@-- $zip --@]}\r
+\addressline{[@-- $country --@]}\r
+\end{minipage}}\r
+\end{tabular}\r
+\hfill\r
+\makebox{\r
+\begin{minipage}[t]{6.4cm}\r
+\begin{flushright}\r
+Terms: [@-- $terms --@]\\\r
+[@-- $po_line --@]\\\r
+\end{flushright}\r
+\end{minipage}}\r
+\vspace{1.5cm}\r
+%\r
+\section*{\textsc{Charges}}\r
+\begin{longtable}{clr}\r
+\hline\r
+\rule{0pt}{2.5ex}\r
+\makebox[1.4cm]{\textbf{Ref}} & \r
+\makebox[12.8cm][l]{\textbf{Description}} & \r
+\makebox[2.5cm][r]{\textbf{Amount}} \\\r
+\hline\r
+\endfirsthead\r
+\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued from previous page}\\\r
+\hline\r
+\rule{0pt}{2.5ex}\r
+\makebox[1.4cm]{\textbf{Ref}} & \r
+\makebox[12.8cm][l]{\textbf{Description}} & \r
+\makebox[2.5cm][r]{\textbf{Amount}} \\\r
+\hline\r
+\endhead\r
+\multicolumn{3}{r}{\rule{0pt}{2.5ex}Continued on next page...}\\\r
+\endfoot\r
+\hline\r
+[@--\r
+\r
+  foreach my $line (@total_items) {\r
+    $OUT .= '\FStotaldesc{' . $line->{'total_item'} . '}' .\r
+            '{' . $line->{'total_amount'} . '}' . "\n";\r
+  }\r
+\r
+--@]\r
+\hline\r
+\endlastfoot\r
+[@--\r
+\r
+  foreach my $line (@detail_items) {\r
+    my $ext_description = $line->{'ext_description'};\r
+\r
+    # Don't break-up small packages.\r
+    my $rowbreak = @$ext_description < 5 ? '*' : '';\r
+\r
+    $OUT .= "\\hline\n";\r
+    $OUT .= '\FSdesc{' . $line->{'ref'} . '}{' . $line->{'description'} . '}' .\r
+            '{' . $line->{'amount'} . "}${rowbreak}\n";\r
+\r
+    foreach my $ext_desc (@$ext_description) {\r
+      $ext_desc = substr($ext_desc, 0, 80) . '...'\r
+        if (length($ext_desc) > 80);\r
+      $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n";\r
+    }\r
+\r
+  }\r
+\r
+--@]\r
+\end{longtable}\r
+\vfill\r
+[@-- $notes --@]\r
+\end{document}\r
diff --git a/conf/invoice_latexfooter b/conf/invoice_latexfooter
new file mode 100644 (file)
index 0000000..2e32123
--- /dev/null
@@ -0,0 +1 @@
+[@-- $company_name --@]
diff --git a/conf/invoice_latexnotes b/conf/invoice_latexnotes
new file mode 100644 (file)
index 0000000..5303d3c
--- /dev/null
@@ -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 (file)
index 0000000..0836d27
--- /dev/null
@@ -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 (file)
index 0000000..2e32123
--- /dev/null
@@ -0,0 +1 @@
+[@-- $company_name --@]
diff --git a/conf/invoice_template b/conf/invoice_template
new file mode 100644 (file)
index 0000000..b33c4dd
--- /dev/null
@@ -0,0 +1,26 @@
+
+                                 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 (file)
index 0000000..db02915
--- /dev/null
@@ -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 (file)
index 0000000..7741b83
--- /dev/null
@@ -0,0 +1 @@
+en_US
diff --git a/conf/logo.eps b/conf/logo.eps
new file mode 100644 (file)
index 0000000..ff25dd4
--- /dev/null
@@ -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 (file)
index 0000000..1e415e6
Binary files /dev/null and b/conf/logo.png differ
diff --git a/conf/lpr b/conf/lpr
new file mode 100644 (file)
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 (file)
index 0000000..29d6383
--- /dev/null
@@ -0,0 +1 @@
+100
diff --git a/conf/payment_receipt_email b/conf/payment_receipt_email
new file mode 100644 (file)
index 0000000..1a0a758
--- /dev/null
@@ -0,0 +1,26 @@
+
+{ $date }
+
+Dear { $name },
+
+This message is to inform you that your payment of ${ $paid } has been
+received.
+
+Payment ID: { $paynum }
+Date:       { $date }
+Amount:     { $paid } 
+Type:       { $payby } # { $payinfo }
+
+{
+  if ( $balance > 0 ) {
+    $OUT .= "Your current balance is now \$$balance.\n\n";
+  } elsif ( $balance < 0 ) {
+    $OUT .= 'You have a credit balance of $'. sprintf("%.2f",0-$balance).
+              ".\n".
+            "Future charges will be deducted from this balance before billing ".
+              "you again.\n\n";
+            
+  }
+}
+Thank you for your business.
+
diff --git a/conf/report_template b/conf/report_template
new file mode 100644 (file)
index 0000000..9c6bb2b
--- /dev/null
@@ -0,0 +1,14 @@
+{ sprintf("%-19s", "Page $page of $total_pages"); } { 
+ my $spacer = (40 - length($title) > 0) ? 40 - length($title) : 0;
+ $spacer = int($spacer / 2);
+ my $titlelen = 40 - $spacer;
+ sprintf("%*s%-*s", $spacer, " ", $titlelen, $title);
+ } { use Date::Format; time2str("%x %X", $date); } 
+
+
+{
+  join("\n", map { $_ } report_lines(57));
+}
+
+
+
diff --git a/conf/shells b/conf/shells
new file mode 100644 (file)
index 0000000..a41fc62
--- /dev/null
@@ -0,0 +1,5 @@
+
+/bin/sh
+/bin/csh
+/bin/bash
+/bin/false
diff --git a/conf/show-msgcat-codes b/conf/show-msgcat-codes
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/conf/smtpmachine b/conf/smtpmachine
new file mode 100644 (file)
index 0000000..2fbb50c
--- /dev/null
@@ -0,0 +1 @@
+localhost
diff --git a/conf/soadefaultttl b/conf/soadefaultttl
new file mode 100644 (file)
index 0000000..92f616f
--- /dev/null
@@ -0,0 +1 @@
+259200
diff --git a/conf/soaexpire b/conf/soaexpire
new file mode 100644 (file)
index 0000000..d235b91
--- /dev/null
@@ -0,0 +1 @@
+3600000
diff --git a/conf/soarefresh b/conf/soarefresh
new file mode 100644 (file)
index 0000000..9f35f8e
--- /dev/null
@@ -0,0 +1 @@
+10800
diff --git a/conf/soaretry b/conf/soaretry
new file mode 100644 (file)
index 0000000..bb08106
--- /dev/null
@@ -0,0 +1 @@
+1800
diff --git a/conf/ticket_system b/conf/ticket_system
new file mode 100644 (file)
index 0000000..631f98a
--- /dev/null
@@ -0,0 +1 @@
+RT_Internal
diff --git a/conf/welcome_letter b/conf/welcome_letter
new file mode 100644 (file)
index 0000000..be7b484
--- /dev/null
@@ -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 (file)
index 0000000..b51eee8
--- /dev/null
@@ -0,0 +1,6 @@
+freeside for Debian
+-------------------
+
+<possible notes regarding this package - if none, delete this file>
+
+ -- Ivan Kohler <ivan-debian@420.am>, Thu, 12 Apr 2001 15:49:17 -0700
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..d8283b5
--- /dev/null
@@ -0,0 +1,9 @@
+freeside (1.4.1-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- Ivan Kohler <ivan-debian@420.am>  Thu, 12 Apr 2001 15:49:17 -0700
+
+Local variables:
+mode: debian-changelog
+End:
diff --git a/debian/conffiles.ex b/debian/conffiles.ex
new file mode 100644 (file)
index 0000000..8686d2a
--- /dev/null
@@ -0,0 +1,7 @@
+#
+# If you want to use this conffile, remove all comments and put files that
+# you want dpkg to process here using their absolute pathnames.
+# See section 9.1 of the packaging manual.
+#
+# for example:
+# /etc/freeside/freeside.conf
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..d7873b2
--- /dev/null
@@ -0,0 +1,59 @@
+Source: freeside
+Section: admin
+Priority: optional
+Maintainer: Ivan Kohler <ivan-debian@420.am>
+Build-Depends: debhelper (>> 3.0.0)
+Standards-Version: 3.5.2
+
+Package: freeside
+Architecture: any
+Depends: freeside-lib
+Recommends: freeside-doc, freeside-ui-web
+Suggests: freeside-selfservice-server
+Description: Billing and administration package for ISPs.
+ Freeside is a billing and account administration package for ISPs.  It stores
+ customer information in an SQL database, and will update UNIX passwd and
+ shadow files, RADIUS users file and SQL databases, and configuration for
+ sendmail, qmail, BIND and/or Apache.  It is also useful as a central database
+ of accounts/domains/web-space for a large number of machines.
+
+Package: freeside-doc
+Architecture: all
+Description: Documentation for freeside
+ This package provides the HTML documentation for Freeside, a billing and
+ account administration package for ISPs.
+
+Package: freeside-lib
+Architecture: all
+Depends: libmime-base64-perl, libdigest-md5-perl, liburi-perl, libhtml-tagset-perl, libhtml-parser-perl, libnet-perl, liblocale-codes-perl, libnet-whois-perl, libwww-perl, libbusiness-creditcard-perl, libmailtools-perl, libtimedate-perl, libdate-manip-perl, libfile-counterfile-perl, libfreezethaw-perl, libtext-template-perl, libdbd-pg-perl, libdbix-datasource-perl, libdbix-dbschema-perl, libnet-ssh-perl, libnet-scp-perl, libapache-asp-perl, libtie-ixhash-perl, libtime-duration-perl, libhtml-widgets-selectlayers-perl, libstorable-perl, libapache-dbi-perl
+Description: Freeside libraries and extension API
+ This package contains the libraries which implement the business logic and
+ backend functions of Freeside, a billing and account administration package
+ for ISPs.  This package also contains the manual pages for the library API.
+ (? like a libmodule-perl package)
+
+Package: freeside-ui-web
+Architecture: all
+Depends: libhtml-mason-perl, libstring-approx-perl, freeside-lib, libapache-mod-perl|apache-perl
+Suggests: libapache-mod-ssl|apache-ssl
+Description: Easy-to-use web interface for Freeside
+ This package contains the web interface for Freeside, a billing and account
+ administration package for ISPs.  This is what sales or support folks will
+ typically use to add new accounts, edit exiting accounts and so on.
+
+Package: freeside-selfservice-server
+Architecture: all
+Depends: freeside-lib, libnet-ssh-perl, ssh
+Description:
+ This package contains the server side of the customer self-service interface.
+ It is installed on a private backend machine, and opens an outgoing ssh
+ connection to one or more public web server(s).
+
+Package: freeside-selfservice-client
+Architecture: all
+Depends: libstorable-perl, libhttp-browserdetect-perl, libbusiness-creditcard-perl, ssh
+Description:
+ This package contains the client side of the customer self-service interface.
+ It is typically installed on a public webserver and interfaces with
+ freeside-selfservice-server installed on a private backend machine.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..e148fce
--- /dev/null
@@ -0,0 +1,10 @@
+This package was debianized by Ivan Kohler <ivan-debian@420.am> on
+Thu, 12 Apr 2001 15:49:17 -0700.
+
+It was downloaded from <fill in ftp site>
+
+Upstream Author(s): <put author(s) name and email here>
+
+Copyright:
+
+<Must follow here>
diff --git a/debian/cron.d.ex b/debian/cron.d.ex
new file mode 100644 (file)
index 0000000..61c074d
--- /dev/null
@@ -0,0 +1,4 @@
+#
+# Regular cron jobs for the freeside package
+#
+0 4    * * *   root    freeside_maintenance
diff --git a/debian/dirs b/debian/dirs
new file mode 100644 (file)
index 0000000..ca882bb
--- /dev/null
@@ -0,0 +1,2 @@
+usr/bin
+usr/sbin
diff --git a/debian/docs b/debian/docs
new file mode 100644 (file)
index 0000000..16636bd
--- /dev/null
@@ -0,0 +1,3 @@
+INSTALL
+README
+TODO
diff --git a/debian/ex.doc-base.package b/debian/ex.doc-base.package
new file mode 100644 (file)
index 0000000..2a055d1
--- /dev/null
@@ -0,0 +1,22 @@
+Document: freeside
+Title: Debian freeside Manual
+Author: <insert document author here>
+Abstract: This manual describes what freeside is
+ and how it can be used to
+ manage online manuals on Debian systems.
+Section: unknown
+
+Format: debiandoc-sgml
+Files: /usr/share/doc/freeside/freeside.sgml.gz
+
+Format: postscript
+Files: /usr/share/doc/freeside/freeside.ps.gz
+
+Format: text
+Files: /usr/share/doc/freeside/freeside.text.gz
+
+Format: HTML
+Index: /usr/share/doc/freeside/html/index.html
+Files: /usr/share/doc/freeside/html/*.html
+
+  
diff --git a/debian/freeside-doc.docs b/debian/freeside-doc.docs
new file mode 100644 (file)
index 0000000..299950c
--- /dev/null
@@ -0,0 +1,2 @@
+#DOCS#
+
diff --git a/debian/freeside-doc.files b/debian/freeside-doc.files
new file mode 100644 (file)
index 0000000..299950c
--- /dev/null
@@ -0,0 +1,2 @@
+#DOCS#
+
diff --git a/debian/init.d.ex b/debian/init.d.ex
new file mode 100644 (file)
index 0000000..5791049
--- /dev/null
@@ -0,0 +1,70 @@
+#! /bin/sh
+#
+# skeleton     example file to build /etc/init.d/ scripts.
+#              This file should be used to construct scripts for /etc/init.d.
+#
+#              Written by Miquel van Smoorenburg <miquels@cistron.nl>.
+#              Modified for Debian GNU/Linux
+#              by Ian Murdock <imurdock@gnu.ai.mit.edu>.
+#
+# Version:     @(#)skeleton  1.8  03-Mar-1998  miquels@cistron.nl
+#
+# This file was automatically customized by dh-make on Thu, 12 Apr 2001 15:49:17 -0700
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/freeside
+NAME=freeside
+DESC=freeside
+
+test -f $DAEMON || exit 0
+
+set -e
+
+case "$1" in
+  start)
+       echo -n "Starting $DESC: "
+       start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
+               --exec $DAEMON
+       echo "$NAME."
+       ;;
+  stop)
+       echo -n "Stopping $DESC: "
+       start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
+               --exec $DAEMON
+       echo "$NAME."
+       ;;
+  #reload)
+       #
+       #       If the daemon can reload its config files on the fly
+       #       for example by sending it SIGHUP, do it here.
+       #
+       #       If the daemon responds to changes in its config file
+       #       directly anyway, make this a do-nothing entry.
+       #
+       # echo "Reloading $DESC configuration files."
+       # start-stop-daemon --stop --signal 1 --quiet --pidfile \
+       #       /var/run/$NAME.pid --exec $DAEMON
+  #;;
+  restart|force-reload)
+       #
+       #       If the "reload" option is implemented, move the "force-reload"
+       #       option to the "reload" entry above. If not, "force-reload" is
+       #       just the same as "restart".
+       #
+       echo -n "Restarting $DESC: "
+       start-stop-daemon --stop --quiet --pidfile \
+               /var/run/$NAME.pid --exec $DAEMON
+       sleep 1
+       start-stop-daemon --start --quiet --pidfile \
+               /var/run/$NAME.pid --exec $DAEMON
+       echo "$NAME."
+       ;;
+  *)
+       N=/etc/init.d/$NAME
+       # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $N {start|stop|restart|force-reload}" >&2
+       exit 1
+       ;;
+esac
+
+exit 0
diff --git a/debian/manpage.1.ex b/debian/manpage.1.ex
new file mode 100644 (file)
index 0000000..ec542bb
--- /dev/null
@@ -0,0 +1,60 @@
+.\"                                      Hey, EMACS: -*- nroff -*-
+.\" First parameter, NAME, should be all caps
+.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
+.\" other parameters are allowed: see man(7), man(1)
+.TH FREESIDE SECTION "April 12, 2001"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh        disable hyphenation
+.\" .hy        enable hyphenation
+.\" .ad l      left justify
+.\" .ad b      justify to both left and right margins
+.\" .nf        disable filling
+.\" .fi        enable filling
+.\" .br        insert line break
+.\" .sp <n>    insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.SH NAME
+freeside \- program to do something
+.SH SYNOPSIS
+.B freeside
+.RI [ options ] " files" ...
+.br
+.B bar
+.RI [ options ] " files" ...
+.SH DESCRIPTION
+This manual page documents briefly the
+.B freeside
+and
+.B bar
+commands.
+This manual page was written for the Debian GNU/Linux distribution
+because the original program does not have a manual page.
+Instead, it has documentation in the GNU Info format; see below.
+.PP
+.\" TeX users may be more comfortable with the \fB<whatever>\fP and
+.\" \fI<whatever>\fP escape sequences to invode bold face and italics, 
+.\" respectively.
+\fBfreeside\fP is a program that...
+.SH OPTIONS
+These programs follow the usual GNU command line syntax, with long
+options starting with two dashes (`-').
+A summary of options is included below.
+For a complete description, see the Info files.
+.TP
+.B \-h, \-\-help
+Show summary of options.
+.TP
+.B \-v, \-\-version
+Show version of program.
+.SH SEE ALSO
+.BR bar (1),
+.BR baz (1).
+.br
+The programs are documented fully by
+.IR "The Rise and Fall of a Fooish Bar" ,
+available via the Info system.
+.SH AUTHOR
+This manual page was written by Ivan Kohler <ivan-debian@420.am>,
+for the Debian GNU/Linux system (but may be used by others).
diff --git a/debian/manpage.sgml.ex b/debian/manpage.sgml.ex
new file mode 100644 (file)
index 0000000..9bc3a86
--- /dev/null
@@ -0,0 +1,143 @@
+<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
+
+<!-- Process this file with docbook-to-man to generate an nroff manual
+     page: `docbook-to-man manpage.sgml > manpage.1'.  You may view
+     the manual page with: `docbook-to-man manpage.sgml | nroff -man |
+     less'.  A typical entry in a Makefile or Makefile.am is:
+
+manpage.1: manpage.sgml
+       docbook-to-man $< > $@
+  -->
+
+  <!-- Fill in your name for FIRSTNAME and SURNAME. -->
+  <!ENTITY dhfirstname "<firstname>FIRSTNAME</firstname>">
+  <!ENTITY dhsurname   "<surname>SURNAME</surname>">
+  <!-- Please adjust the date whenever revising the manpage. -->
+  <!ENTITY dhdate      "<date>April 12, 2001</date>">
+  <!-- SECTION should be 1-8, maybe w/ subsection other parameters are
+       allowed: see man(7), man(1). -->
+  <!ENTITY dhsection   "<manvolnum>SECTION</manvolnum>">
+  <!ENTITY dhemail     "<email>ivan-debian@420.am</email>">
+  <!ENTITY dhusername  "Ivan Kohler">
+  <!ENTITY dhucpackage "<refentrytitle>FREESIDE</refentrytitle>">
+  <!ENTITY dhpackage   "freeside">
+
+  <!ENTITY debian      "<productname>Debian GNU/Linux</productname>">
+  <!ENTITY gnu         "<acronym>GNU</acronym>">
+]>
+
+<refentry>
+  <refentryinfo>
+    <address>
+      &dhemail;
+    </address>
+    <author>
+      &dhfirstname;
+      &dhsurname;
+    </author>
+    <copyright>
+      <year>2001</year>
+      <holder>&dhusername;</holder>
+    </copyright>
+    &dhdate;
+  </refentryinfo>
+  <refmeta>
+    &dhucpackage;
+
+    &dhsection;
+  </refmeta>
+  <refnamediv>
+    <refname>&dhpackage;</refname>
+
+    <refpurpose>program to do something</refpurpose>
+  </refnamediv>
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>&dhpackage;</command>
+
+      <arg><option>-e <replaceable>this</replaceable></option></arg>
+
+      <arg><option>--example <replaceable>that</replaceable></option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+  <refsect1>
+    <title>DESCRIPTION</title>
+
+    <para>This manual page documents briefly the
+      <command>&dhpackage;</command> and <command>bar</command>
+      commands.</para>
+
+    <para>This manual page was written for the &debian; distribution
+      because the original program does not have a manual page.
+      Instead, it has documentation in the &gnu;
+      <application>Info</application> format; see below.</para>
+
+    <para><command>&dhpackage;</command> is a program that...</para>
+
+  </refsect1>
+  <refsect1>
+    <title>OPTIONS</title>
+
+    <para>These programs follow the usual GNU command line syntax,
+      with long options starting with two dashes (`-').  A summary of
+      options is included below.  For a complete description, see the
+      <application>Info</application> files.</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>-h</option>
+          <option>--help</option>
+        </term>
+        <listitem>
+          <para>Show summary of options.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><option>-v</option>
+          <option>--version</option>
+        </term>
+        <listitem>
+          <para>Show version of program.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
+    <title>SEE ALSO</title>
+
+    <para>bar (1), baz (1).</para>
+
+    <para>The programs are documented fully by <citetitle>The Rise and
+      Fall of a Fooish Bar</citetitle> available via the
+      <application>Info</application> system.</para>
+  </refsect1>
+  <refsect1>
+    <title>AUTHOR</title>
+
+    <para>This manual page was written by &dhusername; &dhemail; for
+      the &debian; system (but may be used by others).  Permission is
+      granted to copy, distribute and/or modify this document under
+      the terms of the <acronym>GNU</acronym> Free Documentation
+      License, Version 1.1 or any later version published by the Free
+      Software Foundation; with no Invariant Sections, no Front-Cover
+      Texts and no Back-Cover Texts.</para>
+
+  </refsect1>
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-omittag:t
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:2
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:nil
+sgml-exposed-tags:nil
+sgml-local-catalogs:nil
+sgml-local-ecat-files:nil
+End:
+-->
diff --git a/debian/menu.ex b/debian/menu.ex
new file mode 100644 (file)
index 0000000..ddc947e
--- /dev/null
@@ -0,0 +1,2 @@
+?package(freeside):needs=X11|text|vc|wm section=Apps/see-menu-manual\
+  title="freeside" command="/usr/bin/freeside"
diff --git a/debian/postinst.ex b/debian/postinst.ex
new file mode 100644 (file)
index 0000000..c4d4bfb
--- /dev/null
@@ -0,0 +1,47 @@
+#! /bin/sh
+# postinst script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postinst> `configure' <most-recently-configured-version>
+#        * <old-postinst> `abort-upgrade' <new version>
+#        * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+#          <new-version>
+#        * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+#          <failed-install-package> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see /usr/share/doc/packaging-manual/
+#
+# quoting from the policy:
+#     Any necessary prompting should almost always be confined to the
+#     post-installation script, and should be protected with a conditional
+#     so that unnecessary prompting doesn't happen if a package's
+#     installation fails and the `postinst' is called with `abort-upgrade',
+#     `abort-remove' or `abort-deconfigure'.
+
+case "$1" in
+    configure)
+
+    ;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+
+    ;;
+
+    *)
+        echo "postinst called with unknown argument \`$1'" >&2
+        exit 0
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/postrm.ex b/debian/postrm.ex
new file mode 100644 (file)
index 0000000..bed8abd
--- /dev/null
@@ -0,0 +1,36 @@
+#! /bin/sh
+# postrm script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <postrm> `remove'
+#        * <postrm> `purge'
+#        * <old-postrm> `upgrade' <new-version>
+#        * <new-postrm> `failed-upgrade' <old-version>
+#        * <new-postrm> `abort-install'
+#        * <new-postrm> `abort-install' <old-version>
+#        * <new-postrm> `abort-upgrade' <old-version>
+#        * <disappearer's-postrm> `disappear' <r>overwrit>r> <new-version>
+# for details, see /usr/share/doc/packaging-manual/
+
+case "$1" in
+       purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+
+
+        ;;
+
+    *)
+        echo "postrm called with unknown argument \`$1'" >&2
+        exit 0
+
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+
diff --git a/debian/preinst.ex b/debian/preinst.ex
new file mode 100644 (file)
index 0000000..0b42bb2
--- /dev/null
@@ -0,0 +1,42 @@
+#! /bin/sh
+# preinst script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <new-preinst> `install'
+#        * <new-preinst> `install' <old-version>
+#        * <new-preinst> `upgrade' <old-version>
+#        * <old-preinst> `abort-upgrade' <new-version>
+#
+# For details see /usr/share/doc/packaging-manual/
+
+case "$1" in
+    install|upgrade)
+#        if [ "$1" = "upgrade" ]
+#        then
+#            start-stop-daemon --stop --quiet --oknodo  \
+#                --pidfile /var/run/freeside.pid  \
+#                --exec /usr/sbin/freeside 2>/dev/null || true
+#        fi
+    ;;
+
+    abort-upgrade)
+    ;;
+
+    *)
+        echo "preinst called with unknown argument \`$1'" >&2
+        exit 0
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/prerm.ex b/debian/prerm.ex
new file mode 100644 (file)
index 0000000..ebb87c5
--- /dev/null
@@ -0,0 +1,37 @@
+#! /bin/sh
+# prerm script for freeside
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+#        * <prerm> `remove'
+#        * <old-prerm> `upgrade' <new-version>
+#        * <new-prerm> `failed-upgrade' <old-version>
+#        * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
+#        * <deconfigured's-prerm> `deconfigure' `in-favour'
+#          <package-being-installed> <version> `removing'
+#          <conflicting-package> <version>
+# for details, see /usr/share/doc/packaging-manual/
+
+case "$1" in
+    remove|upgrade|deconfigure)
+#       install-info --quiet --remove /usr/info/freeside.info.gz
+        ;;
+    failed-upgrade)
+        ;;
+    *)
+        echo "prerm called with unknown argument \`$1'" >&2
+        exit 0
+    ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
+
+
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..71016c4
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/bin/make -f
+# Sample debian/rules that uses debhelper. 
+# GNU copyright 1997 by Joey Hess.
+#
+# This version is for a hypothetical package that builds an
+# architecture-dependant package, as well as an architecture-independent
+# package.
+
+# Uncomment this to turn on verbose mode. 
+#export DH_VERBOSE=1
+
+# This is the debhelper compatability version to use.
+export DH_COMPAT=3
+
+configure: configure-stamp
+configure-stamp:
+       dh_testdir
+       # Add here commands to configure the package.
+       
+
+       touch configure-stamp
+
+build: configure-stamp build-stamp
+build-stamp:
+       dh_testdir
+
+       # Add here commands to compile the package.
+       $(MAKE)
+
+       touch build-stamp
+
+clean:
+       dh_testdir
+       dh_testroot
+       rm -f build-stamp configure-stamp
+
+       # Add here commands to clean up after the build process.
+       -$(MAKE) clean
+
+       dh_clean
+
+install: DH_OPTIONS=
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k
+       dh_installdirs
+
+       # Add here commands to install the package into debian/freeside.
+       $(MAKE) install DESTDIR=$(CURDIR)/debian/freeside
+
+       dh_movefiles
+
+# Build architecture-independent files here.
+# Pass -i to all debhelper commands in this target to reduce clutter.
+binary-indep: build install
+       dh_testdir -i
+       dh_testroot -i
+#      dh_installdebconf -i
+       dh_installdocs -i
+       dh_installexamples -i
+       dh_installmenu -i
+#      dh_installlogrotate -i
+#      dh_installemacsen -i
+#      dh_installpam -i
+#      dh_installmime -i
+#      dh_installinit -i
+       dh_installcron -i
+#      dh_installman -i
+       dh_installinfo -i
+#      dh_undocumented -i
+       dh_installchangelogs  -i
+       dh_link -i
+       dh_compress -i
+       dh_fixperms -i
+       dh_installdeb -i
+#      dh_perl -i
+       dh_gencontrol -i
+       dh_md5sums -i
+       dh_builddeb -i
+
+# Build architecture-dependent files here.
+binary-arch: build install
+       dh_testdir -a
+       dh_testroot -a
+#      dh_installdebconf -a
+       dh_installdocs -a
+       dh_installexamples -a
+       dh_installmenu -a
+#      dh_installlogrotate -a
+#      dh_installemacsen -a
+#      dh_installpam -a
+#      dh_installmime -a
+#      dh_installinit -a
+       dh_installcron -a
+#      dh_installman -a
+       dh_installinfo -a
+#      dh_undocumented -a
+       dh_installchangelogs  -a
+       dh_strip -a
+       dh_link -a
+       dh_compress -a
+       dh_fixperms -a
+#      dh_makeshlibs -a
+       dh_installdeb -a
+#      dh_perl -a
+       dh_shlibdeps -a
+       dh_gencontrol -a
+       dh_md5sums -a
+       dh_builddeb -a
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/debian/watch.ex b/debian/watch.ex
new file mode 100644 (file)
index 0000000..3f57ae0
--- /dev/null
@@ -0,0 +1,5 @@
+# Example watch control file for uscan
+# Rename this file to "watch" and then you can run the "uscan" command
+# to check for upstream updates and more.
+# Site         Directory               Pattern                 Version Script
+sunsite.unc.edu        /pub/Linux/Incoming     freeside-(.*)\.tar\.gz  debian  uupdate
diff --git a/eg/TEMPLATE_cust_main.import b/eg/TEMPLATE_cust_main.import
new file mode 100755 (executable)
index 0000000..f6d88c7
--- /dev/null
@@ -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/export_template.pm b/eg/export_template.pm
new file mode 100644 (file)
index 0000000..2830ce3
--- /dev/null
@@ -0,0 +1,106 @@
+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 two 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;
+}
+
+###
+
+#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 (file)
index 0000000..c2f5ba5
--- /dev/null
@@ -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 (file)
index 0000000..cc05843
--- /dev/null
@@ -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 (file)
index 0000000..47dcbe6
--- /dev/null
@@ -0,0 +1,215 @@
+package FS::svc_table;
+
+use strict;
+use vars qw(@ISA);
+#use FS::Record qw( qsearch qsearchs );
+use FS::svc_Common;
+use FS::cust_svc;
+
+@ISA = qw(FS::svc_Common);
+
+=head1 NAME
+
+FS::table_name - Object methods for table_name records
+
+=head1 SYNOPSIS
+
+  use FS::table_name;
+
+  $record = new FS::table_name \%hash;
+  $record = new FS::table_name { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  $error = $record->suspend;
+
+  $error = $record->unsuspend;
+
+  $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::table_name object represents an example.  FS::table_name inherits from
+FS::svc_Common.  The following fields are currently supported:
+
+=over 4
+
+=item field - description
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example.  To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'table_name'; }
+
+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 (file)
index 0000000..5da6f3b
--- /dev/null
@@ -0,0 +1,118 @@
+package FS::table_name;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::table_name - Object methods for table_name records
+
+=head1 SYNOPSIS
+
+  use FS::table_name;
+
+  $record = new FS::table_name \%hash;
+  $record = new FS::table_name { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::table_name object represents an example.  FS::table_name inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item field - description
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example.  To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'table_name'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  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 (executable)
index 0000000..7a2a0a6
--- /dev/null
@@ -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 (file)
index 0000000..7e4f57f
--- /dev/null
@@ -0,0 +1,72 @@
+State/Possession               Abbreviation
+
+ALABAMA                         AL
+ALASKA                          AK
+AMERICAN SAMOA                  AS
+ARIZONA                         AZ
+ARKANSAS                        AR
+CALIFORNIA                      CA
+COLORADO                        CO
+CONNECTICUT                     CT
+DELAWARE                        DE
+DISTRICT OF COLUMBIA            DC
+FEDERATED STATES OF MICRONESIA  FM
+FLORIDA                         FL
+GEORGIA                         GA
+GUAM                            GU
+HAWAII                          HI
+IDAHO                           ID
+ILLINOIS                        IL
+INDIANA                         IN
+IOWA                            IA
+KANSAS                          KS
+KENTUCKY                        KY
+LOUISIANA                       LA
+MAINE                           ME
+MARSHALL ISLANDS                MH
+MARYLAND                        MD
+MASSACHUSETTS                   MA
+MICHIGAN                        MI
+MINNESOTA                       MN
+MISSISSIPPI                     MS
+MISSOURI                        MO
+MONTANA                         MT
+NEBRASKA                        NE
+NEVADA                          NV
+NEW HAMPSHIRE                   NH
+NEW JERSEY                      NJ
+NEW MEXICO                      NM
+NEW YORK                        NY
+NORTH CAROLINA                  NC
+NORTH DAKOTA                    ND
+NORTHERN MARIANA ISLANDS        MP
+OHIO                            OH
+OKLAHOMA                        OK
+OREGON                          OR
+PALAU                           PW
+PENNSYLVANIA                    PA
+PUERTO RICO                     PR
+RHODE ISLAND                    RI
+SOUTH CAROLINA                  SC
+SOUTH DAKOTA                    SD
+TENNESSEE                       TN
+TEXAS                           TX
+UTAH                            UT
+VERMONT                         VT
+VIRGIN ISLANDS                  VI
+VIRGINIA                        VA
+WASHINGTON                      WA
+WEST VIRGINIA                   WV
+WISCONSIN                       WI
+WYOMING                         WY
+
+
+Military "State"               Abbreviation
+
+Armed Forces Africa            AE
+Armed Forces Americas          AA
+(except Canada)
+Armed Forces Canada            AE
+Armed Forces Europe            AE
+Armed Forces Middle East       AE
+Armed Forces Pacific           AP
diff --git a/etc/countries.txt b/etc/countries.txt
new file mode 100644 (file)
index 0000000..73c3975
--- /dev/null
@@ -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 (file)
index 0000000..8e4983c
--- /dev/null
@@ -0,0 +1,231 @@
+[ URL ftp://rs.internic.net/templates/domain-template.txt ] [ 03/98 ] 
+
+******* Please DO NOT REMOVE Version Number or Sections A-Q ********
+
+Domain Version Number: 4.0
+
+******* Email completed agreement to hostmaster@internic.net *******
+
+       NETWORK SOLUTIONS, INC.
+
+       DOMAIN NAME REGISTRATION AGREEMENT
+
+
+A.     Introduction. This domain name registration agreement
+("Registration Agreement") is submitted to NETWORK SOLUTIONS, INC.
+("NSI") for the purpose of applying for and registering a domain name
+on the Internet. If this Registration Agreement is accepted by NSI,
+and a domain name is registered in NSI's domain name database and
+assigned to the Registrant, Registrant ("Registrant") agrees to be
+bound by the terms of this Registration Agreement and the terms of
+NSI's Domain Name Dispute Policy ("Dispute Policy") which is
+incorporated herein by reference and made a part of this Registration
+Agreement. This Registration Agreement shall be accepted at the
+offices of NSI. 
+
+B. Fees and Payments.
+
+1) Registration or renewal (re-registration) date through March 31, 1998:
+Registrant agrees to pay a registration fee of One Hundred United States
+Dollars (US$100) as consideration for the registration of each new domain
+name or Fifty United States Dollars (US$50) to renew (re-register) an
+existing registration.
+2) Registration or renewal date on and after April 1, 1998:  Registrant
+agrees to pay a registration fee of Seventy United States Dollars (US$70) 
+as consideration for the registration of each new domain name or the 
+applicable renewal (re-registration) fee (currently Thirty-Five United 
+States Dollars (US$35)) at the time of renewal (re-registration).
+3) Period of Service:  The non-refundable fee covers a period of two (2)
+years for each new registration, and one (1) year for each renewal, 
+and includes any permitted modification(s) to the domain name record
+during the covered period.
+4) Payment:  Payment is due to Network Solutions within thirty (30) 
+days from the date of the invoice.
+
+C.     Dispute Policy. Registrant agrees, as a condition to
+submitting this Registration Agreement, and if the Registration
+Agreement is accepted by NSI, that the Registrant shall be bound by
+NSI's current Dispute Policy. The current version of the Dispute
+Policy may be found at the InterNIC Registration Services web site:
+"http://www.netsol.com/rs/dispute-policy.html". 
+
+D.     Dispute Policy Changes or Modifications. Registrant agrees
+that NSI, in its sole discretion, may change or modify the Dispute
+Policy, incorporated by reference herein, at any time. Registrant
+agrees that Registrant's maintaining the registration of a domain name
+after changes or modifications to the Dispute Policy become effective
+constitutes Registrant's continued acceptance of these changes or
+modifications. Registrant agrees that if Registrant considers any such
+changes or modifications to be unacceptable, Registrant may request
+that the domain name be deleted from the domain name database. 
+
+E.     Disputes. Registrant agrees that, if the registration of its
+domain name is challenged by any third party, the Registrant will be
+subject to the provisions specified in the Dispute Policy. 
+
+F.     Agents. Registrant agrees that if this Registration Agreement
+is completed by an agent for the Registrant, such as an ISP or
+Administrative Contact/Agent, the Registrant is nonetheless bound as a
+principal by all terms and conditions herein, including the Dispute
+Policy. 
+
+G.     Limitation of Liability. Registrant agrees that NSI shall have
+no liability to the Registrant for any loss Registrant may incur in
+connection with NSI's processing of this Registration Agreement, in
+connection with NSI's processing of any authorized modification to the
+domain name's record during the covered period, as a result of the
+Registrant's ISP's failure to pay either the initial registration fee
+or renewal fee, or as a result of the application of the provisions of
+the Dispute Policy. Registrant agrees that in no event shall the
+maximum liability of NSI under this Agreement for any matter exceed
+Five Hundred United States Dollars (US$500). 
+
+H.     Indemnity. Registrant agrees, in the event the Registration
+Agreement is accepted by NSI and a subsequent dispute arises with any
+third party, to indemnify and hold NSI harmless pursuant to the terms
+and conditions contained in the Dispute Policy. 
+
+I.     Breach. Registrant agrees that failure to abide by any
+provision of this Registration Agreement or the Dispute Policy may be
+considered by NSI to be a material breach and that NSI may provide a
+written notice, describing the breach, to the Registrant. If, within
+thirty (30) days of the date of mailing such notice, the Registrant
+fails to provide evidence, which is reasonably satisfactory to NSI,
+that it has not breached its obligations, then NSI may delete
+Registrant's registration of the domain name. Any such breach by a
+Registrant shall not be deemed to be excused simply because NSI did
+not act earlier in response to that, or any other, breach by the
+Registrant. 
+
+J.     No Guaranty. Registrant agrees that, by registration of a
+domain name, such registration does not confer immunity from objection
+to either the registration or use of the domain name. 
+
+K.     Warranty. Registrant warrants by submitting this Registration
+Agreement that, to the best of Registrant's knowledge and belief, the
+information submitted herein is true and correct, and that any future
+changes to this information will be provided to NSI in a timely manner
+according to the domain name modification procedures in place at that
+time. Breach of this warranty will constitute a material breach. 
+
+L.     Revocation. Registrant agrees that NSI may delete a
+Registrant's domain name if this Registration Agreement, or subsequent
+modification(s) thereto, contains false or misleading information, or
+conceals or omits any information NSI would likely consider material
+to its decision to approve this Registration Agreement. 
+
+M.     Right of Refusal. NSI, in its sole discretion, reserves the
+right to refuse to approve the Registration Agreement for any
+Registrant. Registrant agrees that the submission of this Registration
+Agreement does not obligate NSI to accept this Registration Agreement.
+Registrant agrees that NSI shall not be liable for loss or damages
+that may result from NSI's refusal to accept this Registration
+Agreement. 
+
+N.     Severability. Registrant agrees that the terms of this
+Registration Agreement are severable. If any term or provision is
+declared invalid, it shall not affect the remaining terms or
+provisions which shall continue to be binding. 
+
+O.     Entirety. Registrant agrees that this Registration Agreement
+and the Dispute Policy is the complete and exclusive agreement between
+Registrant and NSI regarding the registration of Registrant's domain
+name. This Registration Agreement and the Dispute Policy supersede all
+prior agreements and understandings, whether established by custom,
+practice, policy, or precedent. 
+
+P.     Governing Law. Registrant agrees that this Registration
+Agreement shall be governed in all respects by and construed in
+accordance with the laws of the Commonwealth of Virginia, United
+States of America. By submitting this Registration Agreement,
+Registrant consents to the exclusive jurisdiction and venue of the
+United States District Court for the Eastern District of Virginia,
+Alexandria Division. If there is no jurisdiction in the United States
+District Court for the Eastern District of Virginia, Alexandria
+Division, then jurisdiction shall be in the Circuit Court of Fairfax
+County, Fairfax, Virginia. 
+
+Q.     This is Domain Name Registration Agreement Version
+Number 4.0. This Registration Agreement is only for registrations
+under top-level domains: COM, ORG, NET, and EDU. By completing
+and submitting this Registration Agreement for consideration and
+acceptance by NSI, the Registrant agrees that he/she has read and
+agrees to be bound by A through P above. 
+
+
+Authorization
+0a.  (N)ew (M)odify (D)elete....:###action###
+0b.  Auth Scheme................: 
+0c.  Auth Info..................: 
+
+1.   Comments...................:###purpose###
+
+2.   Complete Domain Name.......:###domain###
+
+Organization Using Domain Name
+
+3a.  Organization Name..........:###company###
+###LOOP###
+3b.  Street Address.............:###address###
+###ENDLOOP###
+3c.  City.......................:###city###
+3d.  State......................:###state###
+3e.  Postal Code................:###zip###
+3f.  Country....................:###country###
+
+Administrative Contact
+4a.  NIC Handle (if known)......: 
+4b.  (I)ndividual (R)ole........:I
+4c.  Name (Last, First).........:###last###, ###first###
+4d.  Organization Name..........:###company###
+###LOOP###
+4e.  Street Address.............:###address###
+###ENDLOOP###
+4f.  City.......................:###city###
+4g.  State......................:###state###
+4h.  Postal Code................:###zip### 
+4i.  Country....................:###country###
+4j.  Phone Number...............:###daytime###
+4k.  Fax Number.................:###fax###
+4l.  E-Mailbox..................:###email###
+
+Technical Contact
+5a.  NIC Handle (if known)......:###tech_contact###
+5b.  (I)ndividual (R)ole........: 
+5c.  Name (Last, First).........: 
+5d.  Organization Name..........: 
+5e.  Street Address.............: 
+5f.  City.......................: 
+5g.  State......................: 
+5h.  Postal Code................: 
+5i.  Country....................: 
+5j.  Phone Number...............: 
+5k.  Fax Number.................: 
+5l.  E-Mailbox..................: 
+
+Billing Contact
+6a.  NIC Handle (if known)......: 
+6b.  (I)ndividual (R)ole........: 
+6c.  Name (Last, First).........: 
+6d.  Organization Name..........: 
+6e.  Street Address.............: 
+6f.  City.......................: 
+6g.  State......................:
+6h.  Postal Code................:
+6i.  Country....................:
+6j.  Phone Number...............:
+6k.  Fax Number.................: 
+6l.  E-Mailbox..................: 
+
+Prime Name Server
+7a.  Primary Server Hostname....:###primary###
+7b.  Primary Server Netaddress..:###primary_ip###
+
+Secondary Name Server(s)
+###LOOP###
+8a.  Secondary Server Hostname..:###secondary###
+8b.  Secondary Server Netaddress:###secondary_ip###
+###ENDLOOP###
+
+END OF AGREEMENT
+
diff --git a/etc/megapop.pl b/etc/megapop.pl
new file mode 100755 (executable)
index 0000000..e2930fb
--- /dev/null
@@ -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 (file)
index 0000000..dc507ce
--- /dev/null
@@ -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 (executable)
index 0000000..feddb46
--- /dev/null
@@ -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_selfadmin/FS-MailAdminServer/MailAdminClient.pm b/fs_selfadmin/FS-MailAdminServer/MailAdminClient.pm
new file mode 100755 (executable)
index 0000000..d0a7410
--- /dev/null
@@ -0,0 +1,537 @@
+package FS::MailAdminClient;
+
+use strict;
+use vars qw($VERSION @ISA @EXPORT_OK $fs_mailadmind_socket);
+use Exporter;
+use Socket;
+use FileHandle;
+use IO::Handle;
+
+$VERSION = '0.01';
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( signup_info authenticate list_packages list_mailboxes delete_mailbox password_mailbox add_mailbox list_forwards list_pkg_forwards delete_forward add_forward new_customer );
+
+$fs_mailadmind_socket = "/usr/local/freeside/fs_mailadmind_socket";
+
+$ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+my $freeside_uid = scalar(getpwnam('freeside'));
+die "not running as the freeside user\n" if $> != $freeside_uid;
+
+=head1 NAME
+
+FS::MailAdminClient - Freeside mail administration client API
+
+=head1 SYNOPSIS
+
+  use FS::MailAdminClient qw( signup_info list_mailboxes  new_customer );
+
+  ( $locales, $packages, $pops ) = signup_info;
+
+  ( $accounts ) = list_mailboxes;
+
+  $error = new_customer ( {
+    'first'          => $first,
+    'last'           => $last,
+    'ss'             => $ss,
+    'comapny'        => $company,
+    'address1'       => $address1,
+    'address2'       => $address2,
+    'city'           => $city,
+    'county'         => $county,
+    'state'          => $state,
+    'zip'            => $zip,
+    'country'        => $country,
+    'daytime'        => $daytime,
+    'night'          => $night,
+    'fax'            => $fax,
+    'payby'          => $payby,
+    'payinfo'        => $payinfo,
+    'paydate'        => $paydate,
+    'payname'        => $payname,
+    'invoicing_list' => $invoicing_list,
+    'pkgpart'        => $pkgpart,
+    'username'       => $username,
+    '_password'       => $password,
+    'popnum'         => $popnum,
+  } );
+
+=head1 DESCRIPTION
+
+This module provides an API for a remote mail administration server.
+
+It needs to be run as the freeside user.  Because of this, the program which
+calls these subroutines should be written very carefully.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item signup_info
+
+Returns three array references of hash references.
+
+The first set of hash references is of allowable locales.  Each hash reference
+has the following keys:
+  taxnum
+  state
+  county
+  country
+
+The second set of hash references is of allowable packages.  Each hash
+reference has the following keys:
+  pkgpart
+  pkg
+
+The third set of hash references is of allowable POPs (Points Of Presence).
+Each hash reference has the following keys:
+  popnum
+  city
+  state
+  ac
+  exch
+
+=cut
+
+sub signup_info {
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "signup_info\n";
+  SOCK->flush;
+
+  chop ( my $n_cust_main_county = <SOCK> );
+  my @cust_main_county = map {
+    chop ( my $taxnum  = <SOCK> ); 
+    chop ( my $state   = <SOCK> ); 
+    chop ( my $county  = <SOCK> ); 
+    chop ( my $country = <SOCK> );
+    {
+      'taxnum'  => $taxnum,
+      'state'   => $state,
+      'county'  => $county,
+      'country' => $country,
+    };
+  } 1 .. $n_cust_main_county;
+
+  chop ( my $n_part_pkg = <SOCK> );
+  my @part_pkg = map {
+    chop ( my $pkgpart = <SOCK> ); 
+    chop ( my $pkg     = <SOCK> ); 
+    {
+      'pkgpart' => $pkgpart,
+      'pkg'     => $pkg,
+    };
+  } 1 .. $n_part_pkg;
+
+  chop ( my $n_svc_acct_pop = <SOCK> );
+  my @svc_acct_pop = map {
+    chop ( my $popnum = <SOCK> ); 
+    chop ( my $city   = <SOCK> ); 
+    chop ( my $state  = <SOCK> ); 
+    chop ( my $ac     = <SOCK> );
+    chop ( my $exch   = <SOCK> );
+    chop ( my $loc    = <SOCK> );
+    {
+      'popnum' => $popnum,
+      'city'   => $city,
+      'state'  => $state,
+      'ac'     => $ac,
+      'exch'   => $exch,
+      'loc'    => $loc,
+    };
+  } 1 .. $n_svc_acct_pop;
+
+  close SOCK;
+
+  \@cust_main_county, \@part_pkg, \@svc_acct_pop;
+}
+
+=item authenticate
+
+Authentictes against a service on the remote Freeside system.  Requires a hash
+reference as a parameter with the following keys:
+    authuser
+    _password
+
+Returns a scalar error message of the form "authuser OK|FAILED" or an error
+message.
+
+=cut
+
+sub authenticate {
+  my $hashref = shift;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "authenticate", "\n";
+  SOCK->flush;
+
+  print SOCK join("\n", map { $hashref->{$_} } qw(
+    authuser _password
+  ) ), "\n";
+  SOCK->flush;
+
+  chop( my $error = <SOCK> );
+  close SOCK;
+
+  $error;
+}
+
+=item list_packages
+
+Returns one array reference of hash references.
+
+The set of hash references is of existing packages.  Each hash reference
+has the following keys:
+  pkgnum
+  domain
+  account
+
+=cut
+
+sub list_packages {
+  my $user = shift;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "list_packages\n", $user, "\n";
+  SOCK->flush;
+
+  chop ( my $n_packages = <SOCK> );
+  my @packages = map {
+    chop ( my $pkgnum  = <SOCK> ); 
+    chop ( my $domain  = <SOCK> ); 
+    chop ( my $account = <SOCK> ); 
+    {
+      'pkgnum'  => $pkgnum,
+      'domain'  => $domain,
+      'account' => $account,
+    };
+  } 1 .. $n_packages;
+
+  close SOCK;
+
+  \@packages;
+}
+
+=item list_mailboxes
+
+Returns one array references of hash references.
+
+The set of hash references is of existing accounts.  Each hash reference
+has the following keys:
+  svcnum
+  username
+  _password
+
+=cut
+
+sub list_mailboxes {
+  my ($user, $package) = @_;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "list_mailboxes\n", $user, "\n", $package, "\n";
+  SOCK->flush;
+
+  chop ( my $n_svc_acct = <SOCK> );
+  my @svc_acct = map {
+    chop ( my $svcnum  = <SOCK> ); 
+    chop ( my $username  = <SOCK> ); 
+    chop ( my $_password   = <SOCK> ); 
+    {
+      'svcnum'  => $svcnum,
+      'username'  => $username,
+      '_password'   => $_password,
+    };
+  } 1 .. $n_svc_acct;
+
+  close SOCK;
+
+  \@svc_acct;
+}
+
+=item delete_mailbox
+
+Deletes a mailbox service from the remote Freeside system.  Requires a hash
+reference as a paramater with the following keys:
+    authuser
+    account
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub delete_mailbox {
+  my $hashref = shift;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "delete_mailbox", "\n";
+  SOCK->flush;
+
+  print SOCK join("\n", map { $hashref->{$_} } qw(
+    authuser account
+  ) ), "\n";
+  SOCK->flush;
+
+  chop( my $error = <SOCK> );
+  close SOCK;
+
+  $error;
+}
+
+=item password_mailbox
+
+Changes the password for a mailbox service on the remote Freeside system.
+  Requires a hash reference as a paramater with the following keys:
+    authuser
+    account
+    _password
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub password_mailbox {
+  my $hashref = shift;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "password_mailbox", "\n";
+  SOCK->flush;
+
+  print SOCK join("\n", map { $hashref->{$_} } qw(
+    authuser account _password
+  ) ), "\n";
+  SOCK->flush;
+
+  chop( my $error = <SOCK> );
+  close SOCK;
+
+  $error;
+}
+
+=item add_mailbox
+
+Creates a mailbox service on the remote Freeside system.  Requires a hash
+reference as a parameter with the following keys:
+    authuser
+    package
+    account
+    _password
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub add_mailbox {
+  my $hashref = shift;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "add_mailbox", "\n";
+  SOCK->flush;
+
+  print SOCK join("\n", map { $hashref->{$_} } qw(
+    authuser package account _password
+  ) ), "\n";
+  SOCK->flush;
+
+  chop( my $error = <SOCK> );
+  close SOCK;
+
+  $error;
+}
+
+=item list_forwards
+
+Returns one array references of hash references.
+
+The set of hash references is of existing forwards.  Each hash reference
+has the following keys:
+  svcnum
+  dest
+
+=cut
+
+sub list_forwards {
+  my ($user, $service) = @_;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "list_forwards\n", $user, "\n", $service, "\n";
+  SOCK->flush;
+
+  chop ( my $n_svc_forward = <SOCK> );
+  my @svc_forward = map {
+    chop ( my $svcnum  = <SOCK> ); 
+    chop ( my $dest  = <SOCK> ); 
+    {
+      'svcnum'  => $svcnum,
+      'dest'  => $dest,
+    };
+  } 1 .. $n_svc_forward;
+
+  close SOCK;
+
+  \@svc_forward;
+}
+
+=item list_pkg_forwards
+
+Returns one array references of hash references.
+
+The set of hash references is of existing forwards.  Each hash reference
+has the following keys:
+  svcnum
+  srcsvc
+  dest
+
+=cut
+
+sub list_pkg_forwards {
+  my ($user, $package) = @_;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "list_pkg_forwards\n", $user, "\n", $package, "\n";
+  SOCK->flush;
+
+  chop ( my $n_svc_forward = <SOCK> );
+  my @svc_forward = map {
+    chop ( my $svcnum  = <SOCK> ); 
+    chop ( my $srcsvc  = <SOCK> ); 
+    chop ( my $dest  = <SOCK> ); 
+    {
+      'svcnum'  => $svcnum,
+      'srcsvc'  => $srcsvc,
+      'dest'  => $dest,
+    };
+  } 1 .. $n_svc_forward;
+
+  close SOCK;
+
+  \@svc_forward;
+}
+
+=item delete_forward
+
+Deletes a forward service from the remote Freeside system.  Requires a hash
+reference as a paramater with the following keys:
+    authuser
+    svcnum
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub delete_forward {
+  my $hashref = shift;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "delete_forward", "\n";
+  SOCK->flush;
+
+  print SOCK join("\n", map { $hashref->{$_} } qw(
+    authuser svcnum
+  ) ), "\n";
+  SOCK->flush;
+
+  chop( my $error = <SOCK> );
+  close SOCK;
+
+  $error;
+}
+
+=item add_forward
+
+Creates a forward service on the remote Freeside system.  Requires a hash
+reference as a parameter with the following keys:
+    authuser
+    package
+    source
+    dest
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub add_forward {
+  my $hashref = shift;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "add_forward", "\n";
+  SOCK->flush;
+
+  print SOCK join("\n", map { $hashref->{$_} } qw(
+    authuser package source dest
+  ) ), "\n";
+  SOCK->flush;
+
+  chop( my $error = <SOCK> );
+  close SOCK;
+
+  $error;
+}
+
+=item new_customer HASHREF
+
+Adds a customer to the remote Freeside system.  Requires a hash reference as
+a paramater with the following keys:
+  first
+  last
+  ss
+  comapny
+  address1
+  address2
+  city
+  county
+  state
+  zip
+  country
+  daytime
+  night
+  fax
+  payby
+  payinfo
+  paydate
+  payname
+  invoicing_list
+  pkgpart
+  username
+  _password
+  popnum
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub new_customer {
+  my $hashref = shift;
+
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_mailadmind_socket)) or die "connect: $!";
+  print SOCK "new_customer\n";
+
+  print SOCK join("\n", map { $hashref->{$_} } qw(
+    first last ss company address1 address2 city county state zip country
+    daytime night fax payby payinfo paydate payname invoicing_list
+    pkgpart username _password popnum
+  ) ), "\n";
+  SOCK->flush;
+
+  chop( my $error = <SOCK> );
+  $error;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<fs_signupd>, L<FS::SignupServer>, L<FS::cust_main>
+
+=cut
+
+1;
+
diff --git a/fs_selfadmin/FS-MailAdminServer/cgi/mailadmin.cgi b/fs_selfadmin/FS-MailAdminServer/cgi/mailadmin.cgi
new file mode 100755 (executable)
index 0000000..c26c3dc
--- /dev/null
@@ -0,0 +1,698 @@
+#!/usr/bin/perl
+########################################################################
+#                                                                      #
+#    mailadmin.cgi                NCI2000                              #
+#                                 Jeff Finucane <jeff@nci2000.net>     #
+#                                 26 April 2001                        #
+#                                                                      #
+########################################################################
+
+use DBI;
+use strict;
+use CGI;
+use FS::MailAdminClient qw(authenticate list_packages list_mailboxes delete_mailbox password_mailbox add_mailbox list_forwards list_pkg_forwards delete_forward add_forward);
+
+my $sessionfile = '/usr/local/apache/htdocs/mailadmin/adminsess';   # session file
+my $tmpdir = '/usr/local/apache/htdocs/mailadmin/tmp';         # Location to store temp files
+my $cookiedomain = ".your.dom";      # domain if THIS server, should prepend with a '.'
+my $cookieexpire = '+12h';              # expire the cookie session after this much idle time
+my $sessexpire = 43200;                 # expire session after this long of no use (in seconds)
+
+my $body = "<body bgcolor=dddddd>";
+
+#### Should not have to change anything under this line ####
+my $printmainpage = 1;
+my $i = 0;
+my $printheader = 1;
+my $query = new CGI;
+my $cgi = $query->url();
+my $now = getdatetime();
+my $current_package = 0;
+my $current_account = 0;
+my $current_domname = "";
+
+# if they are trying to login we wont check the session yet
+if ($query->param('login') eq '' && $query->param('action') ne 'login') {
+  checksession();
+  printheader();
+}
+
+if ($query->param('login') ne '') {
+
+   my $username = $query->param('username');
+   my $password = $query->param('password');
+
+   if (!checkuserpass($username, $password)) {
+      printheader();
+      error('not_admin');
+   }
+
+   my @alpha = ('A'..'Z', 'a'..'z', 0..9);
+   my $sessid = '';
+   for (my $i = 0; $i < 10; $i++) {
+       $sessid .= @alpha[rand(@alpha)];
+   }
+
+   my $cookie1 = $query->cookie(-name=>'username',
+                               -value=>$username,
+                               -expires=>$cookieexpire,
+                               -domain=>$cookiedomain);
+
+   my $cookie2 = $query->cookie(-name=>'ma_sessionid',
+                               -value=>$sessid,
+                               -expires=>$cookieexpire,
+                               -domain=>$cookiedomain);
+
+   my $now = time();
+   open(NEWSESS, ">>$sessionfile") || error('open');
+   print NEWSESS "$username $sessid $now 0 0\n";
+   close(NEWSESS);
+
+   print $query->header(-COOKIE=>[$cookie1, $cookie2]);
+   $printmainpage = 1;
+
+} elsif ($query->param('action') eq 'blankframe') {
+   
+  print "<html>$body</body></html>\n";
+   $printmainpage = 0;
+
+} elsif ($query->param('action') eq 'list_packages') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  my $list = list_packages($username);
+  print "<html>$body\n";
+  print "<center><table border=0>\n";
+  print "<tr><td></td><td><p>Package Number</td><td><p>Description</td></tr>\n";
+  foreach my $package ( @{$list} ) {
+    print "<tr>";
+    print "<td></td><td><p>$package->{'pkgnum'}</td><td><p>$package->{'domain'}</td>\n";
+    print "<td></td><td><a href=\"$cgi\?action=select&package=$package->{'pkgnum'}&account=$package->{'account'}&domname=$package->{'domain'}\" target=\"rightmainframe\">select</td>\n";
+    print "</tr>";
+  }
+  print "</table>\n";
+  print "</body></html>\n";
+  $printmainpage=0;
+
+} elsif ($query->param('action') eq 'list_mailboxes') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username)  unless $current_package;
+  my $list = list_mailboxes($username, $current_package);
+  my $forwardlist = list_pkg_forwards($username, $current_package);
+  print "<html>$body\n";
+  print "<center><table border=0>\n";
+  print "<tr><td></td><td><p>Username</td><td><p>Password</td></tr>\n";
+  foreach my $account ( @{$list} ) {
+    print "<tr>";
+    print "<td></td><td><p>$account->{'username'}</td><td><p>$account->{'_password'}</td>\n";
+    print "<td></td><td><a href=\"$cgi\?action=change&account=$account->{'svcnum'}&mailbox=$account->{'username'}\" target=\"rightmainframe\">change</td>\n";
+    print "</tr>";
+
+#    my $forwardlist = list_forwards($username, $account->{'svcnum'});
+#    foreach my $forward ( @{$forwardlist} ) {
+#      my $label = qq!=> ! . $forward->{'dest'};
+#      print "<tr><td></td><td></td><td><p>$label</td></tr>\n";
+#    }
+    foreach my $forward ( @{$forwardlist} ) {
+      if ($forward->{'srcsvc'} == $account->{'svcnum'}) {
+        my $label = qq!=> ! . $forward->{'dest'};
+        print "<tr><td></td><td></td><td><p>$label</td></tr>\n";
+      }
+    }
+
+  }
+  print "</table>\n";
+  print "</body></html>\n";
+  $printmainpage=0;
+
+} elsif ($query->param('action') eq 'select') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  $current_package = $query->param('package');
+  $current_account = $query->param('account');
+  $current_domname = $query->param('domname');
+  set_package();
+  print "<html>$body\n";
+  print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+  print "<center>\n";
+  print "<p>Selected package $current_package\n";
+  print "</center>\n";
+  print "</form>\n";
+  print "</body></html>\n";
+  $printmainpage=0;
+
+} elsif ($query->param('action') eq 'change') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $account  = $query->param('account');
+  my $mailbox  = $query->param('mailbox');
+  my $list = list_forwards($username, $account);
+  print "<html>$body\n";
+  print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+  print "<center><table border=0>\n";
+  print "<tr><td></td><td><p>Username</td><td><p>$mailbox</td></tr>\n";
+  print "<input type=hidden name=\"account\" value=\"$account\">\n";
+  print "<input type=hidden name=\"mailbox\" value=\"$mailbox\">\n";
+  foreach my $forward ( @{$list} ) {
+    my $label = qq!=> ! . $forward->{'dest'};
+#    print "<tr><td></td><td></td><td><p>$label</td></tr>\n";
+    print "<tr><td></td><td></td><td><p>$label</td><td><a href=\"$cgi\?action=deleteforward&service=$forward->{'svcnum'}&mailbox=$mailbox&dest=$forward->{'dest'}\" target=\"rightmainframe\">remove</td></tr>\n";
+  }
+  print "<tr><td></td><td><p>Password</td><td><input type=text name=\"_password\" value=\"\"></td></tr>\n";
+  print "</table>\n";
+  print "<input type=submit name=\"deleteaccount\" value=\"Delete This User\">\n";
+  print "<input type=submit name=\"changepassword\" value=\"Change The Password\">\n";
+  print "<input type=submit name=\"addforward\" value=\"Add Forwarding\">\n";
+  print "</center>\n";
+  print "</form>\n";
+  print "<br>\n";
+  print "<p> You may delete this user and all mailforwarding by pressing <B>Delete This User</B>.\n";
+  print "<p> To set or change the password for this user, type the new password in the box next to <B>Password</B> and press <B>Change The Password</B>.\n";
+  print "<p> If you would like to have mail destined for this user forwarded to another email address then press the <B>Add Forwarding</B> button.\n";
+  print "</body></html>\n";
+  $printmainpage=0;
+
+} elsif ($query->param('deleteaccount') ne '') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $account  = $query->param('account');
+  my $mailbox  = $query->param('mailbox');
+  print "<html>$body\n";
+  print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+  print "<p>Are you certain you want to delete user $mailbox?\n";
+  print "<p><input type=hidden name=\"account\" value=\"$account\">\n";
+  print "<input type=submit name=\"deleteaccounty\" value=\"Confirm\">\n";
+  print "</body></html>\n";
+  $printmainpage=0;
+
+} elsif ($query->param('deleteaccounty') ne '') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $account  = $query->param('account');
+  
+  if  ( my $error = delete_mailbox ( {
+      'authuser'         => $username,
+      'account'          => $account,
+    } ) ) {
+    print "<html>$body\n";
+    print "<p>$error\n";
+    print "</body></html>\n";
+      
+  } else {
+    print "<html>$body\n";
+    print "<p>Deleted\n";
+    print "</body></html>\n";
+  }
+
+  $printmainpage=0;
+
+} elsif ($query->param('changepassword') ne '') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $account  = $query->param('account');
+  my $_password  = $query->param('_password');
+  
+  if  ( my $error = password_mailbox ( {
+      'authuser'         => $username,
+      'account'          => $account,
+      '_password'        => $_password,
+    } ) ) {
+    print "<html>$body\n";
+    print "<p>$error\n";
+    print "</body></html>\n";
+      
+  } else {
+    print "<html>$body\n";
+    print "<p>Changed\n";
+    print "</body></html>\n";
+  }
+
+  $printmainpage=0;
+
+} elsif ($query->param('action') eq 'newmailbox') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  print "<html>$body\n";
+  print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+  print "<center><table border=0>\n";
+  print "<tr><td></td><td><p>Username </td><td><input type=text name=\"account\" value=\"\"></td><td>@ " . $current_domname . "</td></tr>\n";
+  print "<tr><td></td><td><p>Password</td><td><input type=text name=\"_password\" value=\"\"></td></tr>\n";
+  print "</table>\n";
+  print "<input type=submit name=\"addmailbox\" value=\"Add This User\">\n";
+  print "</center>\n";
+  print "</form>\n";
+  print "<br>\n";
+  print "<p>Use this screen to add a new mailbox user.  If the domain name of the email address (the part after the <B>@</B> sign) is not what you expect then you may need to use <B>List Packages</B> to select the package with the correct domain.\n";
+  print "<p>Enter the first portion of the email address in the box adjacent to <B>Username</B> and enter the password for that user in the space next to <B>Password</B>.  Then press the button labeled <B>Add The User</B>.\n";
+  print "<p>If you do not want to add a new user at this time then select a choice from the menu at the left, such as <B>List Mailboxes</B>.\n";
+  print "</body></html>\n";
+  $printmainpage=0;
+
+} elsif ($query->param('addmailbox') ne '') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $account  = $query->param('account');
+  my $_password  = $query->param('_password');
+  
+  if  ( my $error = add_mailbox ( {
+      'authuser'         => $username,
+      'package'          => $current_package,
+      'account'          => $account,
+      '_password'        => $_password,
+    } ) ) {
+    print "<html>$body\n";
+    print "<p>$error\n";
+    print "</body></html>\n";
+      
+  } else {
+    print "<html>$body\n";
+    print "<p>Created\n";
+    print "</body></html>\n";
+  }
+
+  $printmainpage=0;
+
+} elsif ($query->param('action') eq 'deleteforward') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $svcnum   = $query->param('service');
+  my $mailbox  = $query->param('mailbox');
+  my $dest  = $query->param('dest');
+  print "<html>$body\n";
+  print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+  print "<p>Are you certain you want to remove the forwarding from $mailbox to $dest?\n";
+  print "<p><input type=hidden name=\"service\" value=\"$svcnum\">\n";
+  print "<input type=submit name=\"deleteforwardy\" value=\"Confirm\">\n";
+  print "</body></html>\n";
+  $printmainpage=0;
+
+} elsif ($query->param('deleteforwardy') ne '') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $service  = $query->param('service');
+  
+  if  ( my $error = delete_forward ( {
+      'authuser'        => $username,
+      'svcnum'          => $service,
+    } ) ) {
+    print "<html>$body\n";
+    print "<p>$error\n";
+    print "</body></html>\n";
+      
+  } else {
+    print "<html>$body\n";
+    print "<p>Forwarding Removed\n";
+    print "</body></html>\n";
+  }
+
+  $printmainpage=0;
+
+} elsif ($query->param('addforward') ne '') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $account  = $query->param('account');
+  my $mailbox  = $query->param('mailbox');
+  
+  print "<html>$body\n";
+  print "<form name=form1 action=\"$cgi\" method=post target=\"rightmainframe\">\n";
+  print "<center><table border=0>\n";
+  print "<input type=hidden name=\"account\" value=\"$account\">\n";
+  print "<input type=hidden name=\"mailbox\" value=\"$mailbox\">\n";
+  print "<tr><td>Forward mail from </td><td><p>$mailbox:</td><td> to </td></tr>\n";
+  print "<tr><td></td><td><p>Destination:</td><td><input type=text name=\"dest\" value=\"\"></td></tr>\n";
+  print "</table>\n";
+  print "<input type=submit name=\"addforwarddst\" value=\"Add the Forwarding\">\n";
+  print "</center>\n";
+  print "</form>\n";
+  print "<br>\n";
+  print "<p> If you would like mail originally destined for the above address to be forwarded to a different email address then type that email address in the box next to <B>Destination:</B> and press the <B>Add the Forwarding</B> button.\n";
+  print "<p> If you do not want to add mail forwarding then select a choice from the menu at the left, such as <B>List Accounts</B>.\n";
+
+  $printmainpage=0;
+
+} elsif ($query->param('addforwarddst') ne '') {
+
+  my $username = $query->cookie(-name=>'username');  # session checked
+  select_package($username) unless $current_package;
+  my $account  = $query->param('account');
+  my $dest  = $query->param('dest');
+  
+  if  ( my $error = add_forward ( {
+      'authuser'         => $username,
+      'package'          => $current_package,
+      'source'           => $account,
+      'dest'             => $dest,
+    } ) ) {
+    print "<html>$body\n";
+    print "<p>$error\n";
+    print "</body></html>\n";
+      
+  } else {
+    print "<html>$body\n";
+    print "<p>Forwarding Created\n";
+    print "</body></html>\n";
+  }
+
+  $printmainpage=0;
+
+} elsif ($query->param('action') eq 'navframe') {
+
+  print "<html><body bgcolor=bbbbbb>\n";
+  print "<center><h2>NCI2000 MAIL ADMIN Web Interface</h2></center>\n";
+
+  print "<br><center>Choose Action:</center><br>\n";
+  print "<center><table border=0>\n";
+  print "<ul>\n";
+  print "<tr><td><li><a href=\"$cgi\?action=logout\" target=\"_top\">Log Off</a></td><tr>\n";
+  print "<tr><td><li><a href=\"$cgi\?action=list_packages\" target=\"rightmainframe\">List Packages</a></td><tr>\n";
+  print "<tr><td><li><a href=\"$cgi\?action=list_mailboxes\" target=\"rightmainframe\">List Accounts</a></td><tr>\n";
+  print "<tr><td><li><a href=\"$cgi\?action=newmailbox\" target=\"rightmainframe\">Add Account</a></td><tr>\n";
+  print "</ul>\n";
+  print "</table></center>\n";
+
+  print "<br><br><br>\n";
+  print "</body></html>\n";
+
+  $printmainpage = 0;
+
+} elsif ($query->param('action') eq 'rightmainframe') {
+
+  print "<html>$body\n";
+  print "<br><br><br>\n";
+  print "<font size=4><----- Please choose function on the left menu</font>\n";
+  print "<br><br>\n";
+  print "<p> Choose <B>Log Off</B> when you are finished.  This helps prevent unauthorized access to your accounts.\n";
+  print "<p> Use <B>List Packages</B> when you administer multiple packages.  When you have multiple domains at NCI2000 you are likely to have multiple packages.  Use of <B>List Packages</B> is not necessary if administer only one package.\n";
+  print "<p> Use <B>List Accounts</B> to view your current arrangement of mailboxes.  From this list you my choose to make changes to existing mailboxes or delete mailboxes.  If you would like to modify the forwarding associated with a mailbox then choose it from this list.\n";
+  print "<p> Use <B>Add Account</B> when you would like an additional mailbox.  After you have added the mailbox you may choose to make additional changes from the list provided by <B>List Accounts<B>.\n";
+  print "</body></html>\n";
+
+  $printmainpage = 0;
+
+}
+
+
+if ($query->param('action') eq 'login') {
+
+    printheader();
+    printlogin();
+
+} elsif ($query->param('action') eq 'logout') {
+
+    destroysession();
+    printheader();
+    printlogin();
+
+} elsif ($printmainpage) {
+
+
+  print "<html><head><title>NCI2000 MAIL ADMIN Web Interface</title></head>\n";
+  print "<FRAMESET cols=\"160,*\" BORDER=\"3\">\n";
+  print "<FRAME NAME=\"navframe\" src=\"$cgi?action=navframe\">\n";
+  print "<FRAME NAME=\"rightmainframe\" src=\"$cgi?action=rightmainframe\">\n";
+  print "</FRAMESET>\n";
+  print "</html>\n";
+
+
+}
+
+sub getdatetime {
+  my $today = localtime(time());
+  my ($day,$mon,$dayofmon,$time,$year) = split(/\s+/,$today);
+  my @datemonths = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
+
+  my $numidx = "01";
+  my ($nummon);
+  foreach my $mons (@datemonths) {
+    if ($mon eq $mons) {
+     $nummon = $numidx;
+    }
+    $numidx++;
+  }
+
+  return "$year-$nummon-$dayofmon $time";
+
+}
+
+sub error {
+
+  my $error = shift;
+  my $arg1 = shift;
+
+   printheader();
+
+   if ($error eq 'not_admin') {
+     print "<html><head><title>Error!</title></head>\n";
+     print "$body\n";
+     print "<center><h1><font face=arial>Error!</font></h1></center>\n";
+     print "<font face=arial>Unauthorized attempt to access mail administration.</font>\n";
+     print "<br><font face=arial>Please login again if you think this is an error.</font>\n";
+     print "<form><input type=button value=\"<<Back\" OnClick=\"history.back()\"></form>\n";
+     print "</body></html>\n";
+   } elsif ($error eq 'exists') {
+     print "<html><head><title>Error!</title></head>\n";
+     print "$body\n";
+     print "<center><h1><font face=arial>Error!</font></h1></center>\n";
+     print "<font face=arial>The user you are trying to enter already exists. Please go back and enter a different username</font>\n";
+     print "</font></body></html>\n";
+   } elsif ($error eq 'ingroup') {
+     print "<html><head><title>Error!</title></head>\n";
+     print "$body\n";
+     print "<center><h1><font face=arial>Error!</font></h1></center>\n";
+     print "<font face=arial>This user is already in the group <i>$arg1</i>. Please go back and deselect group <i>$arg1</i> from the list.</font>\n";
+     print "<form><input type=button value=\"<<Back\" OnClick=\"history.back()\"></form>\n";
+     print "</font></body></html>\n";
+   } elsif ($error eq 'sess_expired') {
+     print "<html>$body\n";
+     print "<center><font size=4>Your session has expired.</font></center>\n";
+     print "<br><br><center>Please login again <a href=\"$cgi\?action=login\" target=\"_top\"> HERE</a></center>\n";
+     print "</body></html>\n";
+   } elsif ($error eq 'open') {
+     print "<html>$body\n";
+     print "<center><font size=4>Unable to open or rename file.</font></center>\n";
+     print "<br><br><center>If this continues, please contact your administrator</center>\n";
+     print "</body></html>\n";
+   }
+
+
+   exit;
+
+}
+
+
+#print a html header if not printed yet
+sub printheader {
+
+  if ($printheader) {
+     print "Content-Type: text/html\n\n";
+     $printheader = 0;
+  }
+
+}
+
+
+#verify user can access administration
+sub checksession {
+
+  my $username = $query->cookie(-name=>'username');
+  my $sessionid = $query->cookie(-name=>'ma_sessionid');
+
+  if ($sessionid eq '') {
+     printheader();
+     if ($query->param()) {
+        error('sess_expired');
+     } else {
+        printlogin();
+        exit;
+    }
+  }
+
+  my $now = time();
+  my $founduser = 0;
+  open(SESSFILE, "$sessionfile") || error('open');
+  error('open') if -l "$tmpdir/adminsess.$$";
+  open(NEWSESS, ">$tmpdir/adminsess.$$") || error('open');
+  while (<SESSFILE>) {
+       chomp();
+       my ($user, $sess, $time, $pkgnum, $svcdomain, $domname) = split(/\s+/);
+       next if $now - $sessexpire > $time;
+       if ($username eq $user && !$founduser) {
+               if ($sess eq $sessionid) {
+                       $founduser = 1;
+                       print NEWSESS "$user $sess $now $pkgnum $svcdomain $domname\n";
+                        $current_package=$pkgnum;
+                        $current_account=$svcdomain;
+                        $current_domname=$domname;
+                       next;
+               }
+       }
+       print NEWSESS "$user $sess $time $pkgnum $svcdomain $domname\n";
+  }
+  close(SESSFILE);
+  close(NEWSESS);
+  system("mv $tmpdir/adminsess.$$ $sessionfile");
+  error('sess_expired') unless $founduser;
+
+  my $cookie1 = $query->cookie(-name=>'username',
+                               -value=>$username,
+                               -expires=>$cookieexpire,
+                               -domain=>$cookiedomain);
+
+  my $cookie2 = $query->cookie(-name=>'ma_sessionid',
+                               -value=>$sessionid,
+                               -expires=>$cookieexpire,
+                               -domain=>$cookiedomain);
+
+  print $query->header(-COOKIE=>[$cookie1, $cookie2]);
+  
+  $printheader = 0;
+
+  return 0;
+
+}
+
+sub destroysession {
+
+  my $username = $query->cookie(-name=>'username');
+  my $sessionid = $query->cookie(-name=>'ma_sessionid');
+
+  if ($sessionid eq '') {
+     printheader();
+     if ($query->param()) {
+        error('sess_expired');
+     } else {
+        printlogin();
+        exit;
+    }
+  }
+
+  my $now = time();
+  my $founduser = 0;
+  open(SESSFILE, "$sessionfile") || error('open');
+  error('open') if -l "$tmpdir/adminsess.$$";
+  open(NEWSESS, ">$tmpdir/adminsess.$$") || error('open');
+  while (<SESSFILE>) {
+       chomp();
+       my ($user, $sess, $time, $pkgnum, $svcdomain, $domname) = split(/\s+/);
+       next if $now - $sessexpire > $time;
+       if ($username eq $user && !$founduser) {
+               if ($sess eq $sessionid) {
+                       $founduser = 1;
+                       next;
+               }
+       }
+       print NEWSESS "$user $sess $time $pkgnum $svcdomain $domname\n";
+  }
+  close(SESSFILE);
+  close(NEWSESS);
+  system("mv $tmpdir/adminsess.$$ $sessionfile");
+  error('sess_expired') unless $founduser;
+
+  $printheader = 0;
+
+  return 0;
+
+}
+
+# checks the username and pass against the database
+sub checkuserpass {
+
+  my $username = shift;
+  my $password = shift;
+
+  my $error = authenticate ( {
+      'authuser'         => $username,
+      '_password'        => $password,
+    } ); 
+
+  if ($error eq "$username OK") {
+    return 1;
+  }else{
+    return 0;
+  }
+
+}
+
+#printlogin prints a login page
+sub printlogin {
+
+        print "<html>$body\n";
+        print "<center><font size=4>Please login to access MAIL ADMIN</font></center>\n";
+        print "<form action=\"$cgi\" method=post>\n";
+        print "<center>Email Address: &nbsp; <input type=text name=\"username\">\n";
+        print "<br>Email Password: <input type=password name=\"password\">\n";
+        print "<br><input type=submit name=\"login\" value=\"Login\">\n";
+        print "</form></center>\n";
+        print "</body></html>\n";
+}
+
+
+#select_package chooses a administrable package if more than one exists
+sub select_package {
+        my $user = shift;
+        my $packages = list_packages($user);
+        if (scalar(@{$packages}) eq 1) {
+          $current_package = @{$packages}[0]->{'pkgnum'};
+          set_package();
+        }
+        if (scalar(@{$packages}) > 1) {
+#          print $query->redirect("$cgi\?action=list_packages");
+           print "<p>No package selected.  You must first <a href=\"$cgi\?action=list_packages\" target=\"rightmainframe\">select a package</a>.\n";
+          exit;
+        }
+}
+
+sub set_package {
+
+  my $username = $query->cookie(-name=>'username');
+  my $sessionid = $query->cookie(-name=>'ma_sessionid');
+
+  if ($sessionid eq '') {
+     printheader();
+     if ($query->param()) {
+        error('sess_expired');
+     } else {
+        printlogin();
+        exit;
+    }
+  }
+
+  my $now = time();
+  my $founduser = 0;
+  open(SESSFILE, "$sessionfile") || error('open');
+  error('open') if -l "$tmpdir/adminsess.$$";
+  open(NEWSESS, ">$tmpdir/adminsess.$$") || error('open');
+  while (<SESSFILE>) {
+       chomp();
+       my ($user, $sess, $time, $pkgnum, $svcdomain, $domname) = split(/\s+/);
+       next if $now - $sessexpire > $time;
+       if ($username eq $user && !$founduser) {
+               if ($sess eq $sessionid) {
+                       $founduser = 1;
+                       print NEWSESS "$user $sess $time $current_package $current_account $current_domname\n";
+                       next;
+               }
+       }
+       print NEWSESS "$user $sess $time $pkgnum $svcdomain $domname\n";
+  }
+  close(SESSFILE);
+  close(NEWSESS);
+  system("mv $tmpdir/adminsess.$$ $sessionfile");
+  error('sess_expired') unless $founduser;
+
+  $printheader = 0;
+
+  return 0;
+
+}
+
diff --git a/fs_selfadmin/FS-MailAdminServer/fs_mailadmind b/fs_selfadmin/FS-MailAdminServer/fs_mailadmind
new file mode 100755 (executable)
index 0000000..746d782
--- /dev/null
@@ -0,0 +1,366 @@
+#!/usr/bin/perl -Tw
+
+eval 'exec /usr/bin/perl -Tw -S $0 ${1+"$@"}'
+    if 0; # not running under some shell
+#
+# fs_mailadmind
+#
+# This is run REMOTELY over ssh by fs_mailadmin_server.
+#
+
+use strict;
+use Socket;
+
+use vars qw( $Debug );
+
+$Debug = 0;
+
+my($fs_mailadmind_socket)="/usr/local/freeside/fs_mailadmind_socket";
+
+$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+$|=1;
+
+warn "[fs_mailadmind] Reading locales...\n" if $Debug;
+chomp( my $n_cust_main_county = <STDIN> );
+my @cust_main_county = map {
+  chomp( my $taxnum = <STDIN> );
+  chomp( my $state = <STDIN> );
+  chomp( my $county = <STDIN> );
+  chomp( my $country = <STDIN> );
+  {
+    'taxnum'  => $taxnum,
+    'state'   => $state,
+    'county'  => $county,
+    'country' => $country,
+  };
+} ( 1 .. $n_cust_main_county );
+
+warn "[fs_mailadmind] Reading package definitions...\n" if $Debug;
+chomp( my $n_part_pkg = <STDIN> );
+my @part_pkg = map {
+  chomp( my $pkgpart = <STDIN> );
+  chomp( my $pkg = <STDIN> );
+  {
+    'pkgpart' => $pkgpart,
+    'pkg'     => $pkg,
+  };
+} ( 1 .. $n_part_pkg );
+
+warn "[fs_mailadmind] Reading POPs...\n" if $Debug;
+chomp( my $n_svc_acct_pop = <STDIN> );
+my @svc_acct_pop = map {
+  chomp( my $popnum = <STDIN> );
+  chomp( my $city = <STDIN> );
+  chomp( my $state = <STDIN> );
+  chomp( my $ac = <STDIN> );
+  chomp( my $exch = <STDIN> );
+  chomp( my $loc = <STDIN> );
+  {
+    'popnum' => $popnum,
+    'city'   => $city,
+    'state'  => $state,
+    'ac'     => $ac,
+    'exch'   => $exch,
+    'loc'    => $loc,
+  };
+} ( 1 .. $n_svc_acct_pop );
+
+warn "[fs_mailadmind] Creating $fs_mailadmind_socket\n" if $Debug;
+my $uaddr = sockaddr_un($fs_mailadmind_socket);
+my $proto = getprotobyname('tcp');
+socket(Server,PF_UNIX,SOCK_STREAM,0) or die "socket: $!";
+unlink($fs_mailadmind_socket);
+bind(Server, $uaddr) or die "bind: $!";
+listen(Server,SOMAXCONN) or die "listen: $!";
+
+warn "[fs_mailadmind] Entering main loop...\n" if $Debug;
+my $paddr;
+for ( ; $paddr = accept(Client,Server); close Client) {
+
+  chop( my $command = <Client> );
+
+  if ( $command eq "signup_info" ) {
+    warn "[fs_mailadmind] sending signup info...\n" if $Debug; 
+    print Client join("\n", $n_cust_main_county,
+      map {
+        $_->{taxnum},
+        $_->{state},
+        $_->{county},
+        $_->{country},
+      } @cust_main_county
+    ), "\n";
+
+    print Client join("\n", $n_part_pkg,
+      map {
+        $_->{pkgpart},
+        $_->{pkg},
+      } @part_pkg
+    ), "\n";
+
+    print Client join("\n", $n_svc_acct_pop,
+      map {
+        $_->{popnum},
+        $_->{city},
+        $_->{state},
+        $_->{ac},
+        $_->{exch},
+        $_->{loc},
+      } @svc_acct_pop
+    ), "\n";
+
+  } elsif ( $command eq "new_customer" ) {
+    warn "[fs_mailadmind] reading customer signup...\n" if $Debug;
+    my(
+      $first, $last, $ss, $company, $address1, $address2, $city, $county,
+      $state, $zip, $country, $daytime, $night, $fax, $payby, $payinfo,
+      $paydate, $payname, $invoicing_list, $pkgpart, $username, $password,
+      $popnum,
+    ) = map { scalar(<Client>) } ( 1 .. 23 );
+
+    warn "[fs_mailadmind] sending customer data to remote server...\n" if $Debug;
+    print 
+      $first, $last, $ss, $company, $address1, $address2, $city, $county,
+      $state, $zip, $country, $daytime, $night, $fax, $payby, $payinfo,
+      $paydate, $payname, $invoicing_list, $pkgpart, $username, $password,
+      $popnum,
+    ;
+
+    warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+    my $error = <STDIN>;
+
+    warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+    print Client $error;
+
+  } elsif ( $command eq "authenticate" ) {
+    warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading authentication material...\n" if $Debug;
+    chop( my $password = <Client> );
+    warn "[fs_mailadmind] sending information to remote server...\n" if $Debug;
+    print "authenticate\n", $user, "\n", $password, "\n";
+
+    warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+    my $error = <STDIN>;
+
+    warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+    print Client $error;
+    
+  } elsif ( $command eq "list_packages" ) {
+    warn "[fs_mailadmind] reading user information to list_packages...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] sending user information to remote server...\n" if $Debug;
+    print "list_packages\n", $user, "\n";
+
+    warn "[fs_mailadmind] reading data from remote server...\n" if $Debug;
+    chomp( my $n_packages = <STDIN> );
+    my @packages = map {
+      chomp( my $pkgnum  = <STDIN> );
+      chomp( my $domain  = <STDIN> );
+      chomp( my $account = <STDIN> );
+      {
+        'pkgnum'  => $pkgnum,
+        'domain'  => $domain,
+        'account' => $account,
+      };
+    } ( 1 .. $n_packages );
+
+    warn "[fs_mailadmind] sending data to local client...\n" if $Debug;
+
+    print Client join("\n", $n_packages,
+      map {
+        $_->{pkgnum},
+        $_->{domain},
+        $_->{account},
+      } @packages
+    ), "\n";
+
+  } elsif ( $command eq "list_mailboxes" ) {
+    warn "[fs_mailadmind] reading user information to list_mailboxes...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading package number to list_mailboxes...\n" if $Debug;
+    chop( my $package = <Client> );
+    warn "[fs_mailadmind] sending user information to remote server...\n" if $Debug;
+    print "list_mailboxes\n", $user, "\n", $package, "\n";
+
+    warn "[fs_mailadmind] reading data from remote server...\n" if $Debug;
+    chomp( my $n_svc_acct = <STDIN> );
+    my @svc_acct = map {
+      chomp( my $svcnum = <STDIN> );
+      chomp( my $username = <STDIN> );
+      chomp( my $_password = <STDIN> );
+      {
+        'svcnum' => $svcnum,
+        'username' => $username,
+        '_password'     => $_password,
+      };
+    } ( 1 .. $n_svc_acct );
+
+    warn "[fs_mailadmind] sending data to local client...\n" if $Debug;
+
+    print Client join("\n", $n_svc_acct,
+      map {
+        $_->{svcnum},
+        $_->{username},
+        $_->{_password},
+      } @svc_acct
+    ), "\n";
+
+  } elsif ( $command eq "delete_mailbox" ) {
+    warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading account information to delete...\n" if $Debug;
+    chop( my $account = <Client> );
+    warn "[fs_mailadmind] sending information to remote server...\n" if $Debug;
+    print "delete_mailbox\n", $user, "\n", $account, "\n";
+
+    warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+    my $error = <STDIN>;
+
+    warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+    print Client $error;
+
+  } elsif ( $command eq "password_mailbox" ) {
+    warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading account information to password...\n" if $Debug;
+    my(
+      $account, $_password,
+    ) = map { scalar(<Client>) } ( 1 .. 2 );
+
+    warn "[fs_mailadmind] sending password data to remote server...\n" if $Debug;
+    print "password_mailbox", "\n";
+    print 
+      $user, "\n", $account, $_password,
+    ;
+
+    warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+    my $error = <STDIN>;
+
+    warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+    print Client $error;
+
+  } elsif ( $command eq "add_mailbox" ) {
+    warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading account information to create...\n" if $Debug;
+    my(
+      $package, $account, $_password,
+    ) = map { scalar(<Client>) } ( 1 .. 3 );
+
+    warn "[fs_mailadmind] sending service data to remote server...\n" if $Debug;
+    print "add_mailbox", "\n";
+    print 
+      $user, "\n", $package, $account, $_password,
+    ;
+
+    warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+    my $error = <STDIN>;
+
+    warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+    print Client $error;
+
+  } elsif ( $command eq "add_forward" ) {
+    warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading forward information to create...\n" if $Debug;
+    my(
+      $package, $source, $dest,
+    ) = map { scalar(<Client>) } ( 1 .. 3 );
+
+    warn "[fs_mailadmind] sending service data to remote server...\n" if $Debug;
+    print "add_forward", "\n";
+    print 
+      $user, "\n", $package, $source, $dest,
+    ;
+
+    warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+    my $error = <STDIN>;
+
+    warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+    print Client $error;
+
+  } elsif ( $command eq "delete_forward" ) {
+    warn "[fs_mailadmind] reading user information to auth...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading forward information to delete...\n" if $Debug;
+    chop( my $service = <Client> );
+    warn "[fs_mailadmind] sending information to remote server...\n" if $Debug;
+    print "delete_forward\n", $user, "\n", $service, "\n";
+
+    warn "[fs_mailadmind] reading error from remote server...\n" if $Debug;
+    my $error = <STDIN>;
+
+    warn "[fs_mailadmind] sending error to local client...\n" if $Debug;
+    print Client $error;
+
+  } elsif ( $command eq "list_forwards" ) {
+    warn "[fs_mailadmind] reading user information to list_forwards...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading service number to list_forwards...\n" if $Debug;
+    chop( my $service = <Client> );
+    warn "[fs_mailadmind] sending user information to remote server...\n" if $Debug;
+    print "list_forwards\n", $user, "\n", $service, "\n";
+
+    warn "[fs_mailadmind] reading data from remote server...\n" if $Debug;
+    chomp( my $n_svc_forward = <STDIN> );
+    my @svc_forward = map {
+      chomp( my $svcnum = <STDIN> );
+      chomp( my $dest = <STDIN> );
+      {
+        'svcnum' => $svcnum,
+        'dest' => $dest,
+      };
+    } ( 1 .. $n_svc_forward );
+
+    warn "[fs_mailadmind] sending data to local client...\n" if $Debug;
+
+    print Client join("\n", $n_svc_forward,
+      map {
+        $_->{svcnum},
+        $_->{dest},
+      } @svc_forward
+    ), "\n";
+
+  } elsif ( $command eq "list_pkg_forwards" ) {
+    warn "[fs_mailadmind] reading user information to list_pkg_forwards...\n" if $Debug;
+    chop( my $user = <Client> );
+    warn "[fs_mailadmind] reading service number to list_forwards...\n" if $Debug;
+    chop( my $package = <Client> );
+    warn "[fs_mailadmind] sending user information to remote server...\n" if $Debug;
+    print "list_pkg_forwards\n", $user, "\n", $package, "\n";
+
+    warn "[fs_mailadmind] reading data from remote server...\n" if $Debug;
+    chomp( my $n_svc_forward = <STDIN> );
+    my @svc_forward = map {
+      chomp( my $svcnum = <STDIN> );
+      chomp( my $srcsvc = <STDIN> );
+      chomp( my $dest = <STDIN> );
+      {
+        'svcnum' => $svcnum,
+        'srcsvc' => $srcsvc,
+        'dest' => $dest,
+      };
+    } ( 1 .. $n_svc_forward );
+
+    warn "[fs_mailadmind] sending data to local client...\n" if $Debug;
+
+    print Client join("\n", $n_svc_forward,
+      map {
+        $_->{svcnum},
+        $_->{srcsvc},
+        $_->{dest},
+      } @svc_forward
+    ), "\n";
+
+  } else {
+    die "unexpected command from client: $command";
+  }
+
+}
+
diff --git a/fs_selfadmin/README b/fs_selfadmin/README
new file mode 100644 (file)
index 0000000..d9857f0
--- /dev/null
@@ -0,0 +1,27 @@
+
+This collection of files implements a 'self-administered mail service.'
+Configuration is similar to fs_signupd
+
+Additionally you will need to modify the database:
+
+CREATE TABLE svc_acct_admin (
+  svcnum int primary key,
+  adminsvc int not null
+);
+
+creating both as keys might be good
+
+(and perform the dbdef-create)
+
+
+As it exists now, a package containing one svc_domain, at least one
+svc_acct_admin, and other services can have its svc_acct's and svc_forward's
+manipulated by the svc_acct referenced by a svc_acct_admin in the package.
+
+One svc_acct may be referenced as svc_acct_admin for multiple packages.
+
+fs_mailadmin_server contains hard coded references to service numbers which
+will require editing for your system.
+
+It's not a lot, but it might provide inspiration.
+
diff --git a/fs_selfadmin/fs_mailadmin_server b/fs_selfadmin/fs_mailadmin_server
new file mode 100755 (executable)
index 0000000..ef47885
--- /dev/null
@@ -0,0 +1,642 @@
+#!/usr/bin/perl -Tw
+#
+# fs_mailadmin_server
+#
+
+use strict;
+use IO::Handle;
+use FS::SSH qw(sshopen2);
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::Record qw( qsearch qsearchs );
+use FS::cust_main_county;
+use FS::cust_main;
+use FS::svc_acct_admin;
+
+use vars qw( $opt $Debug $conf $default_domain );
+
+$Debug = 1;
+
+#my @payby = qw(CARD PREPAY);
+
+my $user = shift or die &usage;
+&adminsuidsetup( $user ); 
+
+$conf = new FS::Conf;
+$default_domain = $conf->config('domain');
+
+my $machine = shift or die &usage;
+
+my $agentnum = shift or die &usage;
+my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } ) or die &usage;
+my $pkgpart = $agent->pkgpart_hashref;
+
+my $refnum = shift or die &usage;
+
+#causing trouble for some folks
+#$SIG{CHLD} = sub { wait() };
+
+my($fs_mailadmind)=$conf->config('fs_mailadmind');
+
+while (1) {
+  my($reader,$writer)=(new IO::Handle, new IO::Handle);
+  $writer->autoflush(1);
+  warn "[fs_mailadmin_server] Connecting to $machine...\n" if $Debug;
+  sshopen2($machine,$reader,$writer,$fs_mailadmind);
+
+  my $data;
+
+  warn "[fs_mailadmin_server] Sending locales...\n" if $Debug;
+  my @cust_main_county = qsearch('cust_main_county', {} );
+  print $writer $data = join("\n",
+    ( scalar(@cust_main_county) || die "no tax rates (cust_main_county records)" ),
+    map {
+      $_->taxnum,
+      $_->state,
+      $_->county,
+      $_->country,
+    } @cust_main_county
+  ),"\n";
+  warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+  warn "[fs_mailadmin_server] Sending package definitions...\n" if $Debug;
+  my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } }
+    qsearch( 'part_pkg', {} );
+  print $writer $data = join("\n",
+    ( scalar(@part_pkg) || die "no usable package definitions, agent $agentnum" ),
+    map {
+      $_->pkgpart,
+      $_->pkg,
+    } @part_pkg
+  ), "\n";
+  warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+  warn "[fs_mailadmin_server] Sending POPs...\n" if $Debug;
+  my @svc_acct_pop = qsearch ('svc_acct_pop',{} );
+  print $writer $data = join("\n",
+    ( scalar(@svc_acct_pop) || die "No points of presence (svc_acct_pop records)" ),
+    map {
+      $_->popnum,
+      $_->city,
+      $_->state,
+      $_->ac,
+      $_->exch,
+      $_->loc,
+    } @svc_acct_pop
+  ), "\n";
+  warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+  warn "[fs_mailadmin_server] Entering main loop...\n" if $Debug;
+COMMAND:  while (1) {
+    warn "[fs_mailadmin_server] Reading (waiting for) command...\n" if $Debug;
+    chop( my($command, $user) = map { scalar(<$reader>) } ( 1 .. 2 ) );
+    my $domain = $default_domain;
+    $user =~ /^([\w\.\-]+)\@(([\w\-]+\.)+\w+)$/;
+    ($user, $domain) = ($1, $2);
+
+    if ($command eq 'authenticate'){
+      warn "[fs_mailadmin_server] Processing authenticate command for $user \n" if $Debug;
+      chop( my($password) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+
+      my $error = '';
+
+      my @svc_domain = qsearchs('svc_domain', { 'domain'   => $domain });
+
+      if (scalar(@svc_domain) != 1) {
+        warn "Nonexistant or duplicate service account for \"$domain\"";
+        next COMMAND;
+      }
+
+      my @svc_acct = qsearchs('svc_acct', { 'username' => $user,
+                                            'domsvc'   => $svc_domain[0]->svcnum });
+      if (scalar(@svc_acct) != 1) {
+        die "Nonexistant or duplicate service account for \"$user\"";
+        next COMMAND;
+      }
+
+      if ($svc_acct[0]->_password eq $password) {
+        $error = "$user\@$domain OK";
+      }else{
+        $error = "$user\@$domain FAILED";
+      }
+      warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+      print $writer $error, "\n";
+    }
+    elsif ($command eq 'list_packages'){
+      warn "[fs_mailadmin_server] Processing list_packages command for $user \n" if $Debug;
+
+      my $error = '';
+
+      my @packages = eval {find_administrable_packages( $user, $domain )};
+      warn "$@" if $@; 
+
+      my %packages;
+      my %accounts;
+
+      foreach my $package (@packages) {
+        $packages{my $pkgnum = $package->getfield('pkgnum')} = $default_domain;
+        $accounts{$pkgnum} = 0;
+        my @services = qsearch('cust_svc', { 'pkgnum' => $pkgnum });
+        foreach my $service (@services) {
+          if ($service->getfield('svcpart') eq '4'){
+            my $account=qsearchs('svc_domain', { 'svcnum' => $service->getfield('svcnum') });
+            $packages{$pkgnum}=$account->getfield('domain');
+            $accounts{$pkgnum}=$account->getfield('svcnum');
+          }
+        }
+      }
+      
+      print $writer $data = join("\n",
+        ( scalar(keys(%packages)) ),
+        map {
+          $_,
+          $packages{$_},
+          $accounts{$_},
+        } keys(%packages)
+      ), "\n";
+      warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+    }elsif ($command eq 'list_mailboxes'){
+
+      warn "[fs_mailadmin_server] Processing list_mailboxes command for $user" if $Debug;
+      chop( my($pkgnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+      warn "package $pkgnum \n" if $Debug;
+
+      my $error = '';
+
+      my @packages = eval {find_administrable_packages( $user, $domain )};
+      warn "$@" if $@; 
+
+      my @accounts;
+
+      foreach my $package (@packages) {
+        next unless ($pkgnum eq $package->getfield('pkgnum'));
+        my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
+        foreach my $service (@services) {
+          if ($service->getfield('svcpart') eq '2'){
+            my $account=qsearchs('svc_acct', { 'svcnum' => $service->getfield('svcnum') });
+#           $accounts[$#accounts+1]=$account->getfield('username');
+            $accounts[$#accounts+1]=$account;
+          }
+        }
+      }
+      
+      print $writer $data = join("\n",
+#        ( scalar(@accounts) || die "No accounts (svc_acct records)" ),
+        ( scalar(@accounts) ),
+        map {
+          $_->svcnum,
+#          $_->username,
+          $_->email,
+#          $_->_password,
+          '*****',
+        } @accounts
+      ), "\n";
+      warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+      
+    } elsif ($command eq 'delete_mailbox'){
+      warn "[fs_mailadmin_server] Processing delete_mailbox command for $user " if $Debug;
+      chop( my($account) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+      warn "account $account \n" if $Debug;
+
+      my $error = '';
+
+      my @packages = eval { find_administrable_packages($user, $domain) };
+      warn "$@" if $@; 
+      $error ||= "$@" if $@; 
+
+      my @svc_acct = qsearchs('svc_acct', { 'svcnum' => $account }) unless $error;
+      if (scalar(@svc_acct) != 1) { $error ||= 'Nonexistant or duplicate service account for user.' };
+      if (! $error && check_administrator(\@packages, $svc_acct[0])){
+# not sure about the next three lines... do we delete? or return error
+        foreach my $svc_forward (qsearch('svc_forward', { 'dstsvc' => $svc_acct[0]->getfield('svcnum') })) {
+          $error ||= $svc_forward->delete;
+        }
+        foreach my $svc_forward (qsearch('svc_forward', { 'srcsvc' => $svc_acct[0]->getfield('svcnum') })) {
+          $error ||= $svc_forward->delete;
+        }
+        $error ||= $svc_acct[0]->delete;
+      } else {
+        $error ||= "Illegal attempt to remove service";
+      }
+
+      
+      warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+      print $writer $error, "\n";
+      
+    } elsif ($command eq 'password_mailbox'){
+      warn "[fs_mailadmin_server] Processing password_mailbox command for $user " if $Debug;
+      chop( my($account, $_password) = map { scalar(<$reader>) } ( 1 .. 2 ) );
+      warn "account $account with password $_password \n" if $Debug;
+
+      my $error = '';
+
+      my @packages = eval { find_administrable_packages($user, $domain) };
+      warn "$@" if $@; 
+      $error ||= "$@" if $@; 
+
+      my @svc_acct = qsearchs('svc_acct', { 'svcnum' => $account }) unless $error;
+      if (scalar(@svc_acct) != 1) { $error ||= 'Nonexistant or duplicate service account.' };
+
+      if (! $error && check_administrator(\@packages, $svc_acct[0])){
+        my $new = new FS::svc_acct ({$svc_acct[0]->hash});
+        $new->setfield('_password' => $_password);
+        $error ||= $new->replace($svc_acct[0]);
+      } else {
+        $error ||= "Illegal attempt to change password";
+      }
+
+      
+      warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+      print $writer $error, "\n";
+      
+    } elsif ($command eq 'add_mailbox'){
+      warn "[fs_mailadmin_server] Processing add_mailbox command for $user " if $Debug;
+      chop( my($target_package, $account, $_password) = map { scalar(<$reader>) } ( 1 .. 3 ) );
+      warn "in package $target_package account $account with password $_password \n" if $Debug;
+
+      my $found_package;
+      my $domainsvc=0;
+      my $svcpart=2;    # this is 'email box'
+      my $svcpartsm=3;  # this is 'domain alias'
+      my $error = '';
+      my $found = 0;
+
+      my @packages = eval { find_administrable_packages($user, $domain) };
+      warn "$@" if $@; 
+      $error ||= "$@" if $@; 
+
+      foreach my $package (@packages) {
+        if ($package->getfield('pkgnum') eq $target_package) {
+          $found = 1;
+          $found_package=$package;
+          my @services = qsearch('cust_svc', { 'pkgnum' => $target_package });
+          foreach my $service (@services) {
+            if ($service->getfield('svcpart') eq '4'){
+              my @svc_domain=qsearchs('svc_domain', { 'svcnum' => $service->getfield('svcnum') });
+              if (scalar(@svc_domain) eq 1) {
+                $domainsvc=$svc_domain[0]->getfield('svcnum');
+              }
+            }
+          }
+          last;
+        }
+      }
+      warn "User $user does not have administration rights to package $target_package\n" unless $found;
+      $error ||= "User $user does not have administration rights to package $target_package\n" unless $found;
+
+      my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$found_package->getfield('pkgpart')});
+
+      #list of services this pkgpart includes (although at the moment we only care
+      #  about $svcpart
+      my $pkg_svc;
+      my %pkg_svc = ();
+      foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $found_package->pkgpart }) ) {
+        $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity;
+      }
+
+      my @services = qsearch('cust_svc', {'pkgnum'  => $found_package->getfield('pkgnum'),
+                                          'svcpart' => $svcpart,
+                                         });
+
+      if (scalar(@services) >= $pkg_svc{$svcpart}) {
+        $error="Maximum allowed already reached.";
+      }
+      
+      my $svc_acct = new FS::svc_acct ( {
+        'pkgnum'    => $found_package->pkgnum,
+        'svcpart'   => $svcpart,
+        'username'  => $account,
+        'domsvc'    => $domainsvc,
+        '_password' => $_password,
+      } );
+
+      my $y = $svc_acct->setdefault; # arguably should be in new method
+      $error ||= $y unless ref($y);
+      #and just in case you were silly
+      $svc_acct->pkgnum($found_package->pkgnum);
+      $svc_acct->svcpart($svcpart);
+      $svc_acct->username($account);
+      $svc_acct->domsvc($domainsvc);
+      $svc_acct->_password($_password);
+
+      $error ||= $svc_acct->check;
+
+      if ( ! $error ) { #in this case, $cust_pkg should always
+                                     #be definied, but....
+        $error ||= $svc_acct->insert;
+        warn "WARNING: $error on pre-checked svc_acct record!" if $error;
+      }
+
+      warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+      print $writer $error, "\n";
+      
+    }elsif ($command eq 'list_forwards'){
+
+      warn "[fs_mailadmin_server] Processing list_forwards command for $user" if $Debug;
+      chop( my($svcnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+      warn "service $svcnum \n" if $Debug;
+
+      my $error = '';
+
+      my @packages = eval {find_administrable_packages( $user, $domain )};
+      warn "$@" if $@; 
+
+      my @forwards;
+
+      foreach my $package (@packages) {
+#        next unless ($pkgnum eq $package->getfield('pkgnum'));
+        my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
+        foreach my $service (@services) {
+          if ($service->getfield('svcpart') eq '10'){
+            my $forward=qsearchs('svc_forward', { 'svcnum' => $service->getfield('svcnum') });
+            $forwards[$#forwards+1]=$forward if ($forward->getfield('srcsvc') == $svcnum);
+          }
+        }
+      }
+      
+      print $writer $data = join("\n",
+        ( scalar(@forwards) ),
+        map {
+          $_->svcnum,
+          ($_->dstsvc ? qsearchs('svc_acct', {'svcnum' => $_->dstsvc})->email : $_->dst),
+        } @forwards
+      ), "\n";
+      warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+      
+    }elsif ($command eq 'list_pkg_forwards'){
+
+      warn "[fs_mailadmin_server] Processing list_pkg_forwards command for $user" if $Debug;
+      chop( my($pkgnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+      warn "package $pkgnum \n" if $Debug;
+
+      my $error = '';
+
+      my @packages = eval {find_administrable_packages( $user, $domain )};
+      warn "$@" if $@; 
+
+      my @forwards;
+
+      foreach my $package (@packages) {
+        next unless ($pkgnum eq $package->getfield('pkgnum'));
+        my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
+        foreach my $service (@services) {
+          if ($service->getfield('svcpart') eq '10'){
+            my $forward=qsearchs('svc_forward', { 'svcnum' => $service->getfield('svcnum') });
+            $forwards[$#forwards+1]=$forward;
+          }
+        }
+      }
+      
+      print $writer $data = join("\n",
+        ( scalar(@forwards) ),
+        map {
+          $_->svcnum,
+          $_->srcsvc,
+          ($_->dstsvc ? qsearchs('svc_acct', {'svcnum' => $_->dstsvc})->email : $_->dst),
+        } @forwards
+      ), "\n";
+      warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
+
+      
+    } elsif ($command eq 'delete_forward'){
+      warn "[fs_mailadmin_server] Processing delete_forward command for $user " if $Debug;
+      chop( my($forward) = map { scalar(<$reader>) } ( 1 .. 1 ) );
+      warn "forward $forward \n" if $Debug;
+
+      my $error = '';
+
+      my @packages = eval { find_administrable_packages($user, $domain) };
+      warn "$@" if $@; 
+      $error ||= "$@" if $@; 
+
+      my @svc_forward = qsearchs('svc_forward', { 'svcnum' => $forward }) unless $error;
+      if (scalar(@svc_forward) != 1) { $error ||= 'Nonexistant or duplicate service account for user.' };
+      if (! $error && check_administrator(\@packages, $svc_forward[0])){
+# not sure about the next three lines... do we delete? or return error
+        $error ||= $svc_forward[0]->delete;
+      } else {
+        $error ||= "Illegal attempt to remove service";
+      }
+
+      
+      warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+      print $writer $error, "\n";
+      
+    } elsif ($command eq 'add_forward'){
+      warn "[fs_mailadmin_server] Processing add_forward command for $user " if $Debug;
+      chop( my($target_package, $source, $dest) = map { scalar(<$reader>) } ( 1 .. 3 ) );
+      warn "in package $target_package source $source with destination $dest \n" if $Debug;
+
+      my $found_package;
+      my $domainsvc=0;
+      my $svcpart=10;   # this is 'forward service'
+      my $error = '';
+      my $found = 0;
+
+      my @packages = eval { find_administrable_packages($user, $domain) };
+      warn "$@" if $@; 
+      $error ||= "$@" if $@; 
+
+      foreach my $package (@packages) {
+        if ($package->getfield('pkgnum') eq $target_package) {
+          $found = 1;
+          $found_package=$package;
+          last;
+        }
+      }
+      warn "User $user does not have administration rights to package $target_package\n" unless $found;
+      $error ||= "User $user does not have administration rights to package $target_package\n" unless $found;
+
+      my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $source });
+      warn "Forwarding source $source does not exist.\n" unless $svc_acct;
+      $error ||= "Forwarding source $source does not exist.\n" unless $svc_acct;
+
+      my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $source });
+      warn "Forwarding source $source not attached to any account.\n" unless $cust_svc;
+      $error ||= "Forwarding source $source not attached to any account.\n" unless $cust_svc;
+
+      if ( ! $error ) {
+        warn "Forwarding source $source is not in package $target_package\n"
+          unless ($cust_svc->getfield('pkgnum') == $target_package);
+        $error ||= "Forwarding source $source is not in package $target_package\n"
+          unless ($cust_svc->getfield('pkgnum') == $target_package);
+      }
+
+      my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$found_package->getfield('pkgpart')});
+
+      #list of services this pkgpart includes (although at the moment we only care
+      #  about $svcpart
+      my $pkg_svc;
+      my %pkg_svc = ();
+      foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $found_package->pkgpart }) ) {
+        $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity;
+      }
+
+      my @services = qsearch('cust_svc', {'pkgnum'  => $found_package->getfield('pkgnum'),
+                                          'svcpart' => $svcpart,
+                                         });
+
+      if (scalar(@services) >= $pkg_svc{$svcpart}) {
+        $error="Maximum allowed already reached.";
+      }
+      
+      my $svc_forward = new FS::svc_forward ( {
+        'pkgnum'    => $found_package->pkgnum,
+        'svcpart'   => $svcpart,
+        'srcsvc'  => $source,
+        'dstsvc'    => 0,
+        'dst' => $dest,
+      } );
+
+      my $y = $svc_forward->setdefault; # arguably should be in new method
+      $error ||= $y unless ref($y);
+      #and just in case you were silly
+      $svc_forward->pkgnum($found_package->pkgnum);
+      $svc_forward->svcpart($svcpart);
+      $svc_forward->srcsvc($source);
+      $svc_forward->dstsvc(0);
+      $svc_forward->dst($dest);
+
+      $error ||= $svc_forward->check;
+
+      if ( ! $error ) { #in this case, $cust_pkg should always
+                                     #be definied, but....
+        $error ||= $svc_forward->insert;
+        warn "WARNING: $error on pre-checked svc_forward record!" if $error;
+      }
+
+      warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
+      print $writer $error, "\n";
+      
+    } else {
+      warn "[fs_mailadmin_server] Bad command: $command \n" if $Debug;
+      print $writer "Bad command \n";
+    }
+  }
+  close $writer;
+  close $reader;
+  warn "connection to $machine lost!  waiting 60 seconds...\n";
+  sleep 60;
+  warn "reconnecting...\n";
+}
+
+sub usage {
+  die "Usage:\n\n  fs_mailadmin_server user machine agentnum refnum\n";
+}
+
+#sub find_administrable_packages {
+#      my $user = shift;
+#
+#      my $error = '';
+#
+#      my @svc_acct = qsearchs('svc_acct', { 'username' => $user });
+#      if (scalar(@svc_acct) != 1) {
+#        die "Nonexistant or duplicate service account for \"$user\"";
+#      }
+#
+#      my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct[0]->getfield('svcnum') });
+#      if (scalar(@cust_svc) != 1 ) {
+#        die "Nonexistant or duplicate customer service for \"$user\"";
+#      }
+#
+#      my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
+#      if (scalar(@cust_pkg) != 1) {
+#        die "Nonexistant or duplicate customer package for \"$user\"";
+#      }
+#
+#      my @cust_main = qsearchs('cust_main', { 'custnum' => $cust_pkg[0]->getfield('custnum') });
+#      if (scalar(@cust_main) != 1 ) {
+#        die "Nonexistant or duplicate customer for \"$user\"";
+#      }
+#
+#      my @packages = $cust_main[0]->ncancelled_pkgs;
+#}
+
+sub find_administrable_packages {
+      my $user = shift;
+      my $domain = shift;
+
+      my @packages;
+      my $error = '';
+
+      my @svc_domain = qsearchs('svc_domain', { 'domain'   => $domain });
+
+      if (scalar(@svc_domain) != 1) {
+        die "Nonexistant or duplicate service account for \"$domain\"";
+      }
+
+      my @svc_acct = qsearchs('svc_acct', { 'username' => $user,
+                                            'domsvc'   => $svc_domain[0]->svcnum });
+      if (scalar(@svc_acct) != 1) {
+        die "Nonexistant or duplicate service account for \"$user\"";
+      }
+
+      my @svc_acct_admin = qsearch('svc_acct_admin', {'adminsvc' => $svc_acct[0]->getfield('svcnum') });
+      die "Nonexistant or duplicate customer service for \"$user\"" unless scalar(@svc_acct_admin);
+
+      foreach my $svc_acct_admin (@svc_acct_admin) {
+        my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct_admin->getfield('svcnum') });
+        if (scalar(@cust_svc) != 1 ) {
+          die "Nonexistant or duplicate customer service for admin \"$svc_acct_admin->getfield('svcnum')\"";
+        }
+
+        my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
+        if (scalar(@cust_pkg) != 1) {
+          die "Nonexistant or duplicate customer package for admin \"$user\"";
+        }
+
+        push @packages, $cust_pkg[0] unless $cust_pkg[0]->getfield('cancel');
+
+      }
+      (@packages);
+}
+
+sub check_administrator {
+      my ($allowed_packages_aref, $svc_acct_ref) = @_;
+
+      my $error = '';
+      my $found = 0;
+
+      {
+        my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct_ref->getfield('svcnum') });
+        if (scalar(@cust_svc) != 1 ) {
+          warn "Nonexistant or duplicate customer service for \"$svc_acct_ref->getfield('username')\"";
+          last;
+        }
+
+        my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
+        if (scalar(@cust_pkg) != 1) {
+          warn "Nonexistant or duplicate customer package for \"$svc_acct_ref->getfield('username')\"";
+          last;
+        }
+
+        foreach my $package (@$allowed_packages_aref) {
+          if ($package->getfield('pkgnum') eq $cust_pkg[0]->getfield('pkgnum')) {
+            $found = 1;
+            last;
+          }
+        }
+      }
+
+      $found;
+}
+
+sub check_add {
+      my ($allowed_packages_aref, $target_package) = @_;
+
+      my $error = '';
+      my $found = 0;
+
+      foreach my $package (@$allowed_packages_aref) {
+        if ($package->getfield('pkgnum') eq $target_package) {
+          $found = 1;
+          last;
+        }
+      }
+
+      $found;
+}
+
diff --git a/fs_selfservice/DEPLOY b/fs_selfservice/DEPLOY
new file mode 100755 (executable)
index 0000000..c93ed0f
--- /dev/null
@@ -0,0 +1,29 @@
+#!/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 )
+
+#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 (file)
index 0000000..b9e26b7
--- /dev/null
@@ -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 (file)
index 0000000..a619b2b
--- /dev/null
@@ -0,0 +1,8 @@
+Changes
+Makefile.PL
+MANIFEST
+SelfService.pm
+SelfService/XMLRPC.pm
+test.pl
+freeside-selfservice-clientd
+freeside-selfservice-xmlrpc-server
diff --git a/fs_selfservice/FS-SelfService/Makefile.PL b/fs_selfservice/FS-SelfService/Makefile.PL
new file mode 100644 (file)
index 0000000..c078f08
--- /dev/null
@@ -0,0 +1,20 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+    'NAME'             => 'FS::SelfService',
+    'VERSION_FROM'     => 'SelfService.pm', # finds $VERSION
+    'EXE_FILES'         => [ 'freeside-selfservice-clientd',
+                             'freeside-selfservice-xmlrpc-server',
+                           ],
+    'INSTALLSCRIPT'     => '/usr/local/sbin',
+    'INSTALLSITEBIN'    => '/usr/local/sbin',
+    '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 (file)
index 0000000..ec4668f
--- /dev/null
@@ -0,0 +1,1467 @@
+package FS::SelfService;
+
+use strict;
+use vars qw($VERSION @ISA @EXPORT_OK $DEBUG $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'                     => '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',
+  'process_payment'           => 'MyAccount/process_payment',
+  'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
+  'process_prepay'            => 'MyAccount/process_prepay',
+  '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_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',
+  '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',
+  'signup_info'               => 'Signup/signup_info',
+  'domain_select_hash'        => 'Signup/domain_select_hash',  # expose?
+  'new_customer'              => 'Signup/new_customer',
+  'agent_login'               => 'Agent/agent_login',
+  'agent_logout'              => 'Agent/agent_logout',
+  'agent_info'                => 'Agent/agent_info',
+  'agent_list_customers'      => 'Agent/agent_list_customers',
+);
+@EXPORT_OK = ( keys(%autoload), qw( regionselector expselect popselector domainselector) );
+
+$ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+my $freeside_uid = scalar(getpwnam('freeside'));
+die "not running as the freeside user\n" if $> != $freeside_uid;
+
+-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,
+                        'pkgpart'          => $pkgpart,
+                        'username'         => $username,
+                        '_password'        => $password,
+                        'popnum'           => $popnum,
+                        'agentnum'         => $agentnum,
+                      }
+                    );
+  
+  my $error = $rv->{'error'};
+  if ( $error eq '_decline' ) {
+    print_decline();
+  } elsif ( $error ) {
+    reprint_signup();
+  } else {
+    print_success();
+  }
+
+=head1 DESCRIPTION
+
+Use this API to implement your own client "self-service" module.
+
+If you just want to customize the look of the existing "self-service" module,
+see XXXX instead.
+
+=head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS
+
+=over 4
+
+=item passwd
+
+=item chfn
+
+=item chsh
+
+=back
+
+=head1 "MY ACCOUNT" FUNCTIONS
+
+=over 4
+
+=item login HASHREF
+
+Creates a user session.  Takes a hash reference as parameter with the
+following keys:
+
+=over 4
+
+=item username
+
+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
+
+=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 payinfo
+
+Card number
+
+=item month
+
+Card expiration month
+
+=item year
+
+Card expiration year
+
+=item paybatch
+
+Unique transaction identifier, returned from the payment_info function.
+Prevents multiple charges.
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors
+
+=item list_pkgs
+
+Returns package information for this customer.  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 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:
+
+=over 4
+
+=item Name of this service
+
+=item Meaningful user-specific identifier for the service (i.e. username, domain or mail alias)
+
+=item Table name of this service
+
+=back
+
+=item svcnum
+
+Primary key for this service
+
+=item svcpart
+
+Service definition (part_pkg)
+
+=item pkgnum
+
+Customer package (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 error
+
+Empty on success, or an error message on errors.
+
+=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:
+
+=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
+
+pkgpart of package to order
+
+=item svcpart
+
+optional svcpart, required only if the package definition does not contain
+one svc_acct service definition with quantity 1 (it may contain others with
+quantity >1)
+
+=item username
+
+Username
+
+=item _password
+
+Password
+
+=item sec_phrase
+
+Optional security phrase
+
+=item popnum
+
+Optional Access number number
+
+=back
+
+Returns a hash reference with a single key, B<error>, empty on success, or an
+error message on errors.  The special error '_decline' is returned for
+declined transactions.
+
+=item cancel_pkg
+
+Cancels a package for this customer.
+
+Takes a hash reference as parameter with the following keys:
+
+=over 4
+
+=item session_id
+
+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 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 agentnum
+
+Agent number
+
+=back
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item error
+
+Empty on success, or an error message on errors.  The special error '_decline' is returned for declined transactions; other error messages should be suitable for display to the user (and are customizable in under 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>';
+
+  $state_html .= '</SELECT>';
+
+  my $country_html = qq!<SELECT NAME="${prefix}country" !.
+                     qq!onChange="${prefix}country_changed(this); $param->{'onchange'}">!;
+  my $countrydefault = $param->{default_country} || 'US';
+  foreach my $country (
+    sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
+      keys %cust_main_county
+  ) {
+    my $selected = $country eq $param->{'selected_country'} ? ' SELECTED' : '';
+    $country_html .= "\n<OPTION$selected>$country</OPTION>"
+  }
+  $country_html .= '</SELECT>';
+
+  ($county_html, $state_html, $country_html);
+
+}
+
+#=item expselect HASHREF | LIST
+#
+#Takes as input a hashref or list of key/value pairs with the following keys:
+#
+#=over 4
+#
+#=item prefix - Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
+#
+#=item date - current date, in yyyy-mm-dd or m-d-yyyy format
+#
+#=back
+
+=item expselect PREFIX [ DATE ]
+
+Takes as input a unique prefix string and the current expiration date, in
+yyyy-mm-dd or m-d-yyyy format
+
+Returns an HTML fragments for expiration date selection.
+
+=cut
+
+sub expselect {
+  #my $param;
+  #if ( ref($_[0]) ) {
+  #  $param = shift;
+  #} else {
+  #  $param = { @_ };
+  #my $prefix = $param->{'prefix'};
+  #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
+  #my $date =   exists($param->{'date'})   ? $param->{'date'}   : '';
+  my $prefix = shift;
+  my $date = scalar(@_) ? shift : '';
+
+  my( $m, $y ) = ( 0, 0 );
+  if ( $date  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
+    ( $m, $y ) = ( $2, $1 );
+  } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+    ( $m, $y ) = ( $1, $3 );
+  }
+  my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
+  for ( 1 .. 12 ) {
+    $return .= 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";
+
+  $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;
+
+}
+
+=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.
+
+=over 4
+
+=item agent_login
+
+=item agent_info
+
+=item agent_list_customers
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<freeside-selfservice-clientd>, L<freeside-selfservice-server>
+
+=cut
+
+1;
+
diff --git a/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm b/fs_selfservice/FS-SelfService/SelfService/XMLRPC.pm
new file mode 100644 (file)
index 0000000..4e0d3e9
--- /dev/null
@@ -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 (file)
index 0000000..9fe400f
--- /dev/null
@@ -0,0 +1,16 @@
+<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>Payment results</FONT><BR><BR>
+<%= if ( $error ) {
+  $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error processing your payment: $error</FONT>!;
+} else {
+  $OUT .= 'Your payment was processed successfully.  Thank you.';
+} %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent.cgi b/fs_selfservice/FS-SelfService/cgi/agent.cgi
new file mode 100644 (file)
index 0000000..6e8de61
--- /dev/null
@@ -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 (file)
index 0000000..603fc0b
--- /dev/null
@@ -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 (file)
index 0000000..7a2b750
--- /dev/null
@@ -0,0 +1,19 @@
+<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>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_login.html b/fs_selfservice/FS-SelfService/cgi/agent_login.html
new file mode 100644 (file)
index 0000000..4b0778e
--- /dev/null
@@ -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 (file)
index 0000000..9809467
--- /dev/null
@@ -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 (file)
index 0000000..9dd3383
--- /dev/null
@@ -0,0 +1,37 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+
+<%= $message
+      ? "<FONT SIZE=\"+2\"><B>$message</B></FONT>"
+      : "Hello $agent!"
+%><BR><BR>
+
+<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">
+<TR><TH BGCOLOR="#cccccc">Customer summary</TH></TR>
+<TR><TD BGCOLOR="#dddddd">
+
+  <B><%= $num_prospect %></B>
+  <%= $num_prospect ? qq!<A HREF="${url}list_customers;prospect=1">! : '' %>prospects</A>
+
+  <BR><FONT COLOR="#00CC00"><B><%= $num_active %></B></FONT>
+  <%= $num_active ? qq!<A HREF="${url}list_customers;active=1">! : '' %>active</A>
+
+  <BR><FONT COLOR="#FF9900"><B><%= $num_susp %></B></FONT>
+  <%= $num_susp ? qq!<A HREF="${url}list_customers;susp=1">! : '' %>suspended</A>
+
+  <BR><FONT COLOR="#FF0000"><B><%= $num_cancel %></B></FONT>
+  <%= $num_cancel ? qq!<A HREF="${url}list_customers;cancel=1">! : '' %>cancelled</A>
+
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
+
+
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_menu.html b/fs_selfservice/FS-SelfService/cgi/agent_menu.html
new file mode 100644 (file)
index 0000000..84a2953
--- /dev/null
@@ -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 (file)
index 0000000..0a665c9
--- /dev/null
@@ -0,0 +1,19 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;custnum=$custnum;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+<%= $small_custview %>
+<BR>
+
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_customer_menu') %>
+<TD VALIGN="top">
+<%= include('order_pkg') %>
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_provision.html b/fs_selfservice/FS-SelfService/cgi/agent_provision.html
new file mode 100644 (file)
index 0000000..8770e2f
--- /dev/null
@@ -0,0 +1,25 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;custnum=$custnum;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+
+<%= $message
+      ? "<FONT SIZE=\"+2\"><B>$message</B></FONT><BR><BR>"
+      : ''
+%>
+
+<%= $small_custview %>
+<BR>
+
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_customer_menu') %>
+<TD VALIGN="top">
+<%= include('provision_list') %>
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html b/fs_selfservice/FS-SelfService/cgi/agent_provision_svc_acct.html
new file mode 100644 (file)
index 0000000..8d299cd
--- /dev/null
@@ -0,0 +1,19 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;custnum=$custnum;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+<%= $small_custview %>
+<BR>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_customer_menu') %>
+<TD VALIGN="top">
+<%= include('svc_acct') %>
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/bill.html b/fs_selfservice/FS-SelfService/cgi/bill.html
new file mode 100644 (file)
index 0000000..bbdf1f2
--- /dev/null
@@ -0,0 +1,7 @@
+<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>
diff --git a/fs_selfservice/FS-SelfService/cgi/card.html b/fs_selfservice/FS-SelfService/cgi/card.html
new file mode 100644 (file)
index 0000000..cf6d20d
--- /dev/null
@@ -0,0 +1,73 @@
+<TR>
+  <TD ALIGN="right">Card&nbsp;number</TD>
+  <TD>
+    <TABLE>
+      <TR>
+        <TD>
+          <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%=$payinfo%>"> </TD>
+        <TD>Exp.</TD>
+        <TD>
+          <SELECT NAME="month">
+            <%= for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) {
+                  $OUT .= '<OPTION'. ($_ == $month ? ' SELECTED' : ''). ">$_\n";
+            } %>
+          </SELECT>
+        </TD>
+        <TD> / </TD>
+        <TD>
+          <SELECT NAME="year">
+            <%= my @a = localtime; for ( $a[5]+1900 .. $a[5]+1915 ) {
+                  $OUT .= '<OPTION'. ($_ == $year ? ' SELECTED' : ''). ">$_\n";
+            } %>
+          </SELECT>
+        </TD>
+      </TR>
+    </TABLE>
+  </TD>
+</TR>
+<%= 
+  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>
+  <TD ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TD>
+  <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%=$payname%>"></TD>
+</TR><TR>
+  <TD ALIGN="right">Card&nbsp;billing&nbsp;address</TD>
+  <TD>
+    <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<%=$address1%>">
+  </TD>
+</TR><TR>
+  <TD ALIGN="right">Address&nbsp;line&nbsp;2</TD>
+  <TD>
+    <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<%=$address2%>">
+  </TD>
+</TR><TR>
+  <TD ALIGN="right">City</TD>
+  <TD>
+    <TABLE>
+      <TR>
+        <TD>
+          <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<%=$city%>">
+        </TD>
+        <TD>State</TD>
+        <TD>
+          <SELECT NAME="state">
+            <%= for ( @states ) {
+              $OUT .= '<OPTION'. ($_ eq $state ? ' SELECTED' : '' ). ">$_\n";
+            } %>
+          </SELECT>
+        </TD>
+        <TD>Zip</TD>
+        <TD>
+          <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<%=$zip%>">
+        </TD>
+      </TR>
+    </TABLE>
+  </TD>
+</TR>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_bill.html b/fs_selfservice/FS-SelfService/cgi/change_bill.html
new file mode 100755 (executable)
index 0000000..0bc47d0
--- /dev/null
@@ -0,0 +1,25 @@
+<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>Edit billing address</FONT><BR><BR>
+<%= 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>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_password.html b/fs_selfservice/FS-SelfService/cgi/change_password.html
new file mode 100644 (file)
index 0000000..af7b453
--- /dev/null
@@ -0,0 +1,53 @@
+<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>Change password</FONT><BR><BR>
+
+<%= 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>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_pay.html b/fs_selfservice/FS-SelfService/cgi/change_pay.html
new file mode 100644 (file)
index 0000000..d26abfa
--- /dev/null
@@ -0,0 +1,75 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<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>
+<FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<FONT SIZE=4>Change payment information</FONT><BR><BR>
+<%= 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/,
+                      'PREPAY' => qq/Prepaid Card/,
+                    );
+  tie my %options, 'Tie::IxHash', ();
+  foreach my $payby_option ( @paybys ) {
+    $options{$payby_option} = $payby_index{$payby_option};
+  }
+  $options{$payby} = $payby_index{$payby}
+    unless exists($options{$payby});
+
+  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;
+
+%>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/change_pkg.html b/fs_selfservice/FS-SelfService/cgi/change_pkg.html
new file mode 100644 (file)
index 0000000..a841308
--- /dev/null
@@ -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 (executable)
index 0000000..1a3b85d
--- /dev/null
@@ -0,0 +1,104 @@
+<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>Edit service address</FONT><BR><BR>
+<%= 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>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/check.html b/fs_selfservice/FS-SelfService/cgi/check.html
new file mode 100644 (file)
index 0000000..68753fe
--- /dev/null
@@ -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 (file)
index 0000000..20c15df
--- /dev/null
@@ -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 (file)
index 0000000..5f344a3
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+
+use strict;
+use CGI;
+use FS::SelfService qw( invoice_logo );
+
+my $cgi = new CGI;
+
+my($query) = $cgi->keywords;
+$query =~ /^([^\.\/]*)$/ or '' =~ /^()$/;
+my $templatename = $1;
+my $hashref = invoice_logo('templatename' => $templatename);
+
+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 (file)
index 0000000..d08ab96
--- /dev/null
@@ -0,0 +1,10 @@
+<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">
+<%= include('change_pkg') %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html b/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html
new file mode 100755 (executable)
index 0000000..c01b6b3
--- /dev/null
@@ -0,0 +1,10 @@
+<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">
+<%= include('order_pkg') %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/cvv2.html b/fs_selfservice/FS-SelfService/cgi/cvv2.html
new file mode 100644 (file)
index 0000000..b178c85
--- /dev/null
@@ -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 (file)
index 0000000..4610dcb
Binary files /dev/null and b/fs_selfservice/FS-SelfService/cgi/cvv2.png 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 (file)
index 0000000..21c36a0
Binary files /dev/null and b/fs_selfservice/FS-SelfService/cgi/cvv2_amex.png differ
diff --git a/fs_selfservice/FS-SelfService/cgi/decline.html b/fs_selfservice/FS-SelfService/cgi/decline.html
new file mode 100644 (file)
index 0000000..a37ba3a
--- /dev/null
@@ -0,0 +1,5 @@
+<HTML><HEAD><TITLE>Processing error</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Processing error</FONT><BR><BR>
+There has been an error processing your account.  Please contact customer
+support.
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/delete_svc.html b/fs_selfservice/FS-SelfService/cgi/delete_svc.html
new file mode 100644 (file)
index 0000000..8468deb
--- /dev/null
@@ -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">
+
+<%= if ( $error ) {
+  $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>!;
+} else {
+  $OUT .= "<FONT SIZE=4>$svc removed.</FONT>";
+} %>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/list_customers.html b/fs_selfservice/FS-SelfService/cgi/list_customers.html
new file mode 100644 (file)
index 0000000..858e5e9
--- /dev/null
@@ -0,0 +1,39 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+
+<%=
+  if ( @customers ) {
+    $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+            '<TR><TH BGCOLOR="#cccccc" COLSPAN=3>Customers</TH><TD>';
+    my $col1 = "ffffff";
+    my $col2 = "dddddd";
+    my $col = $col1;
+
+    foreach my $customer ( @customers ) {
+      my $td = qq!<TD BGCOLOR="#$col">!;
+      my $a = qq!<A HREF="${url}view_customer;custnum=!. 
+              $customer->{'custnum'}. '">';
+      $OUT .=
+        '<TR>'.
+        "$td<FONT COLOR=\"". $customer->{'statuscolor'}. '">'.
+          ucfirst($customer->{'status'}). "</TD>". "$td</TD>".
+        "$td$a". $customer->{'name'}. "</A></TD>".
+        '</TR>';
+        #"$td</TD>".
+      $col = $col eq $col1 ? $col2 : $col1;
+    }
+    $OUT .= '</TABLE>';
+  } else {
+    $OUT .= 'No customers.<BR><BR>';
+  }
+%>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/login.html b/fs_selfservice/FS-SelfService/cgi/login.html
new file mode 100644 (file)
index 0000000..5607de7
--- /dev/null
@@ -0,0 +1,29 @@
+<HTML><HEAD><TITLE>Login</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=5>Login</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM ACTION="<%= $self_url %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="session" VALUE="login">
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=2 CELLPADDING=0>
+<TR>
+  <TH ALIGN="right">Username </TH>
+  <TD>
+    <INPUT TYPE="text" NAME="username" VALUE="<%= $username %>">
+  </TD>
+</TR>
+<TR>
+  <TH ALIGN="right">Domain </TH>
+  <TD>
+    <INPUT TYPE="text" NAME="domain" VALUE="<%= $domain %>">
+  </TD>
+</TR>
+<!--<INPUT TYPE="hidden" NAME="domain" VALUE="myisp.com">-->
+<TR>
+  <TH ALIGN="right">Password </TH>
+  <TD>
+    <INPUT TYPE="password" NAME="password">
+  </TD>
+</TR>
+</TABLE>
+<BR><BR><INPUT TYPE="submit" VALUE="Login">
+</FORM></BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/logout.html b/fs_selfservice/FS-SelfService/cgi/logout.html
new file mode 100644 (file)
index 0000000..0e774e9
--- /dev/null
@@ -0,0 +1,5 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+You have been logged out.
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/make_ach_payment.html b/fs_selfservice/FS-SelfService/cgi/make_ach_payment.html
new file mode 100644 (file)
index 0000000..f801423
--- /dev/null
@@ -0,0 +1,61 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<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>
+<FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<FONT SIZE=4>Make a payment</FONT><BR><BR>
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="<%=$selfurl%>" onSubmit="document.OneTrueForm.process.disabled=true">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%=$session_id%>">
+<INPUT TYPE="hidden" NAME="action" VALUE="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" 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>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/make_payment.html b/fs_selfservice/FS-SelfService/cgi/make_payment.html
new file mode 100644 (file)
index 0000000..89239c0
--- /dev/null
@@ -0,0 +1,71 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<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>
+<FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<FONT SIZE=4>Make a payment</FONT><BR><BR>
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="<%=$selfurl%>" onSubmit="document.OneTrueForm.process.disabled=true">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%=$session_id%>">
+<INPUT TYPE="hidden" NAME="action" VALUE="payment_results">
+<TABLE BGCOLOR="#cccccc">
+<TR>
+  <TD ALIGN="right">Amount&nbsp;Due</TD>
+  <TD>
+    <TABLE><TR><TD BGCOLOR="#ffffff">
+      $<%=sprintf("%.2f",$balance)%>
+    </TD></TR></TABLE>
+  </TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Payment&nbsp;amount</TD>
+  <TD>
+    <TABLE><TR><TD BGCOLOR="#ffffff">
+      $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<%=sprintf("%.2f",$balance)%>">
+    </TD></TR></TABLE>
+  </TD>
+</TR><TR>
+  <TD ALIGN="right">Card&nbsp;type</TD>
+  <TD>
+    <SELECT NAME="card_type"><OPTION></OPTION>
+      <%= foreach ( keys %card_types ) {
+            $selected = $card_type eq $card_types{$_} ? ' SELECTED' : '';
+            $OUT .= qq(<OPTION$selected VALUE="). $card_types{$_}. qq(">$_\n);
+      } %>
+    </SELECT>
+  </TD>
+</TR>
+<%= include('card') %>
+<TR>
+  <TD COLSPAN=2>
+    <INPUT TYPE="checkbox" CHECKED NAME="save" VALUE="1">
+    Remember this information
+  </TD>
+</TR><TR>
+  <TD COLSPAN=2>
+    <INPUT TYPE="checkbox"<%= $payby eq 'CARD' ? ' CHECKED' : '' %> NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }">
+    Charge future payments to this card automatically
+  </TD>
+</TR>
+</TABLE>
+<BR>
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="<%=$paybatch%>">
+<INPUT TYPE="submit" NAME="process" VALUE="Process payment"> <!-- onClick="this.disabled=true"> -->
+</FORM>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/map.gif b/fs_selfservice/FS-SelfService/cgi/map.gif
new file mode 100644 (file)
index 0000000..ef884d8
Binary files /dev/null and b/fs_selfservice/FS-SelfService/cgi/map.gif differ
diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount.html b/fs_selfservice/FS-SelfService/cgi/myaccount.html
new file mode 100644 (file)
index 0000000..d1f5671
--- /dev/null
@@ -0,0 +1,99 @@
+<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">
+
+Hello <%= $name %>!<BR><BR>
+<%= $small_custview %>
+<BR>
+<%= if ( $balance > 0 ) {
+  $OUT .= qq! <B><A HREF="${url}make_payment">Make a payment</A></B><BR><BR>!;
+} %>
+<%=
+  if ( @open_invoices ) {
+    $OUT .= '<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=2 BGCOLOR="#eeeeee">'.
+            '<TR><TH BGCOLOR="#ff6666" COLSPAN=5>Open Invoices</TH></TR>';
+    my $link = qq!<A HREF="<%= $url %>myaccount!;
+    my $col1 = "ffffff";
+    my $col2 = "dddddd";
+    my $col = $col1;
+
+    foreach my $invoice ( @open_invoices ) {
+      my $td = qq!<TD BGCOLOR="#$col">!;
+      my $a=qq!<A HREF="${url}view_invoice;invnum=!. $invoice->{'invnum'}. '">';
+      $OUT .=
+        "<TR>$td${a}Invoice #". $invoice->{'invnum'}. "</A></TD>$td</TD>".
+        "$td$a". $invoice->{'date'}. "</A></TD>$td</TD>".
+        qq!<TD BGCOLOR="#$col" ALIGN="right">$a\$!. $invoice->{'owed'}.
+          '</A></TD>'.
+        '</TR>';
+      $col = $col eq $col1 ? $col2 : $col1;
+    }
+    $OUT .= '</TABLE><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 .= '';
+  }
+%>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
+
+
diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html
new file mode 100644 (file)
index 0000000..ec5a8fa
--- /dev/null
@@ -0,0 +1,94 @@
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0><TR>
+<TD VALIGN="top" HEIGHT="100%" BGCOLOR="#dddddd">
+
+<TABLE CELLSPACING=0 BORDER=0 HEIGHT="100%">
+
+<%= 
+
+my @menu = (
+{ title=>' ' },
+{ title=>'Overview', url=>'myaccount', size=>'+1', },
+{ title=>' ' },
+
+{ title=>'Purchase', size=>'+1', },
+  { 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
+
+  push @menu, (
+    { title=>'Recharge my account with a credit card',
+      url=>'make_payment', indent=>2 },
+    { title=>'Recharge my account with a check',
+      url=>'make_ach_payment', indent=>2 },
+    { title=>'Recharge my account with a prepaid card',
+      url=>'recharge_prepay', indent=>2 },
+  );
+
+}
+
+push @menu, (
+
+{ title=>' ' },
+
+{ title=>'View my usage', url=>'view_usage', size=>'+1', },
+{ title=>'Setup my services', url=>'provision', size=>'+1', },
+
+{ title=>' ' },
+
+{ title=>'Change my information', size=>'+1', },
+  { 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 },
+  { title=>'Change password(s)',          url=>'change_password', indent=>2 },
+
+{ title=>' ' },
+
+{ title=>'Logout',   url=>'logout', size=>'+1', },
+
+);
+
+foreach my $item ( @menu ) {
+
+  $OUT .= '<TR><TD'; 
+  if ( exists $item->{'url'} && $action eq $item->{'url'} ) {
+    $OUT .= ' 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.='>';
+
+  $OUT .= '<FONT SIZE="'. $item->{'size'}. '">'
+    if exists $item->{'size'};
+
+  $OUT .= '&nbsp;' x $item->{'indent'}
+    if exists $item->{'indent'};
+
+  $OUT .= '<A HREF="'. $url. $item->{'url'}. '">'
+    if exists $item->{'url'} && $action ne $item->{'url'};
+
+  $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>';
+
+}
+
+%>
+
+<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 (file)
index 0000000..9cdd4cd
--- /dev/null
@@ -0,0 +1,75 @@
+<SCRIPT TYPE="text/javascript">
+function enable_order_pkg () {
+  if ( document.OrderPkgForm.pkgpart.selectedIndex > 0 ) {
+    document.OrderPkgForm.submit.disabled = false;
+  } else {
+    document.OrderPkgForm.submit.disabled = true;
+  }
+}
+</SCRIPT>
+<FONT SIZE=4>Purchase additional package</FONT><BR><BR>
+<%= if ( $error ) {
+  $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+<FORM NAME="OrderPkgForm" ACTION="<%= $selfurl %>" METHOD=POST>
+<INPUT TYPE="hidden" NAME="session" VALUE="<%= $session_id %>">
+<INPUT TYPE="hidden" NAME="action" VALUE="process_order_pkg">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<%= $custnum %>">
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+<TR>
+  <TD COLSPAN=2><SELECT NAME="pkgpart" onChange="enable_order_pkg()">
+  <OPTION VALUE="">
+
+  <%=
+    foreach my $part_pkg ( @part_pkg ) {
+      $OUT .= '<OPTION VALUE="'. $part_pkg->{'pkgpart'}. '"';
+      $OUT .= ' SELECTED' if $pkgpart && $part_pkg->{'pkgpart'} == $pkgpart;
+      $OUT .= '>'. $part_pkg->{'pkg'};
+    }
+  %>
+
+  </SELECT></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Username</TD>
+  <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Password</TD>
+  <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $_password %>"></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Re-enter Password</TD>
+  <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $_password2 %>"></TD>
+</TR>
+<%=
+  if ( $security_phrase ) {
+    $OUT .= <<ENDOUT;
+<TR>
+  <TD ALIGN="right">Security Phrase</TD>
+  <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+  </TD>
+</TR>
+ENDOUT
+  } else {
+    $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+  }
+%>
+<%=
+  if ( @svc_acct_pop ) {
+    $OUT .= '<TR><TD ALIGN="right">Access number</TD><TD>'.
+            popselector( 'popnum'        => $popnum,
+                         'pops'          => \@svc_acct_pop,
+                         'init_popstate' => $init_popstate,
+                         'popac'         => $popac,
+                         'acstate'       => $acstate,
+                       ).
+            '</TD></TR>';
+  } else {
+    $OUT .= popselector(popnum=>$popnum, pops=>\@svc_acct_pop);
+  }
+%>
+</TABLE>
+<INPUT NAME="submit" TYPE="submit" VALUE="Purchase" disabled>
+</FORM>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/passwd.cgi b/fs_selfservice/FS-SelfService/cgi/passwd.cgi
new file mode 100755 (executable)
index 0000000..87e5e68
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+
+use strict;
+use Getopt::Std;
+use FS::SelfService qw(passwd);
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+
+my $freeside_uid = scalar(getpwnam('freeside'));
+
+$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+die "passwd.cgi isn't running as freeside user\n" if $> != $freeside_uid;
+
+my $cgi = new CGI;
+
+$cgi->param('username') =~ /^([^\n]{0,255}$)/ or die "Illegal username";
+my $me = $1;
+
+$cgi->param('domain') =~ /^([^\n]{0,255}$)/ or die "Illegal domain";
+my $domain = $1;
+
+$cgi->param('old_password') =~ /^([^\n]{0,255}$)/ or die "Illegal old_password";
+my $old_password = $1;
+
+$cgi->param('new_password') =~ /^([^\n]{0,255}$)/ or die "Illegal new_password";
+my $new_password = $1;
+
+die "New passwords don't match"
+  unless $new_password eq $cgi->param('new_password2');
+
+my $rv = passwd(
+  'username'     => $me,
+  'domain'       => $domain,
+  'old_password' => $old_password,
+  'new_password' => $new_password,
+);
+
+my $error = $rv->{error};
+
+if ($error) {
+  die $error;
+} else {
+  print $cgi->header(), <<END;
+<html>
+  <head>
+    <title>Password changed</title>
+  </head>
+  <body bgcolor="#e8e8e8">
+    <h3>Password changed</h3>
+<br>Your password has been changed.
+  </body>
+</html>
+END
+}
diff --git a/fs_selfservice/FS-SelfService/cgi/passwd.html b/fs_selfservice/FS-SelfService/cgi/passwd.html
new file mode 100644 (file)
index 0000000..459c96a
--- /dev/null
@@ -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 (file)
index 0000000..9fe400f
--- /dev/null
@@ -0,0 +1,16 @@
+<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>Payment results</FONT><BR><BR>
+<%= if ( $error ) {
+  $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">Error processing your payment: $error</FONT>!;
+} else {
+  $OUT .= 'Your payment was processed successfully.  Thank you.';
+} %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_bill.html b/fs_selfservice/FS-SelfService/cgi/process_change_bill.html
new file mode 100644 (file)
index 0000000..66a71e6
--- /dev/null
@@ -0,0 +1,13 @@
+<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>Information updated successfully.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_password.html b/fs_selfservice/FS-SelfService/cgi/process_change_password.html
new file mode 100644 (file)
index 0000000..4fdee79
--- /dev/null
@@ -0,0 +1,13 @@
+<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>Password changed for <%= $value %> <%= $label %>.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_pay.html b/fs_selfservice/FS-SelfService/cgi/process_change_pay.html
new file mode 100644 (file)
index 0000000..66a71e6
--- /dev/null
@@ -0,0 +1,13 @@
+<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>Information updated successfully.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_pkg.html b/fs_selfservice/FS-SelfService/cgi/process_change_pkg.html
new file mode 100644 (file)
index 0000000..9347434
--- /dev/null
@@ -0,0 +1,13 @@
+<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>Package change successful.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_ship.html b/fs_selfservice/FS-SelfService/cgi/process_change_ship.html
new file mode 100644 (file)
index 0000000..66a71e6
--- /dev/null
@@ -0,0 +1,13 @@
+<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>Information updated successfully.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_order_pkg.html b/fs_selfservice/FS-SelfService/cgi/process_order_pkg.html
new file mode 100755 (executable)
index 0000000..79be5eb
--- /dev/null
@@ -0,0 +1,13 @@
+<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>Package order successful.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_order_recharge.html b/fs_selfservice/FS-SelfService/cgi/process_order_recharge.html
new file mode 100644 (file)
index 0000000..851bbed
--- /dev/null
@@ -0,0 +1,13 @@
+<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><%= $svc %> recharged successfully.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html b/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html
new file mode 100644 (file)
index 0000000..3b81291
--- /dev/null
@@ -0,0 +1,13 @@
+<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><%= $svc %> setup successfully.</FONT>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/process_svc_external.html b/fs_selfservice/FS-SelfService/cgi/process_svc_external.html
new file mode 100644 (file)
index 0000000..19fec73
--- /dev/null
@@ -0,0 +1,15 @@
+<HTML><HEAD><TITLE><%= $error ? 'MyAccount' : sprintf("Your serial number is %010d-$title", $id) %></TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+
+<FONT SIZE=4><%= $svc %> setup successfully.</FONT>
+
+<BR><BR>Your serial number is <%= sprintf("%010d-$title", $id) %>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/promocode.html b/fs_selfservice/FS-SelfService/cgi/promocode.html
new file mode 100644 (file)
index 0000000..f8ee7f6
--- /dev/null
@@ -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 (file)
index 0000000..d31e607
--- /dev/null
@@ -0,0 +1,10 @@
+<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">
+<%= include('provision_list') %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/provision_list.html b/fs_selfservice/FS-SelfService/cgi/provision_list.html
new file mode 100644 (file)
index 0000000..88d1c84
--- /dev/null
@@ -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 (file)
index 0000000..5054074
--- /dev/null
@@ -0,0 +1,11 @@
+<HTML><HEAD><TITLE>MyAccount</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<%= include('svc_acct') %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/recharge_prepay.html b/fs_selfservice/FS-SelfService/cgi/recharge_prepay.html
new file mode 100644 (file)
index 0000000..f858459
--- /dev/null
@@ -0,0 +1,36 @@
+<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>Recharge with prepaid card</FONT><BR><BR>
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="<%=$selfurl%>" onSubmit="document.OneTrueForm.process.disabled=true">
+<INPUT TYPE="hidden" NAME="session" VALUE="<%=$session_id%>">
+<INPUT TYPE="hidden" NAME="action" VALUE="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>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/recharge_results.html b/fs_selfservice/FS-SelfService/cgi/recharge_results.html
new file mode 100644 (file)
index 0000000..b1eb7cb
--- /dev/null
@@ -0,0 +1,24 @@
+<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>Recharge results</FONT><BR><BR>
+<%= 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.';
+} %>
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
diff --git a/fs_selfservice/FS-SelfService/cgi/regcode.html b/fs_selfservice/FS-SelfService/cgi/regcode.html
new file mode 100644 (file)
index 0000000..e639b9b
--- /dev/null
@@ -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 (file)
index 0000000..36557b6
--- /dev/null
@@ -0,0 +1,647 @@
+#!/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( login customer_info edit_info invoice
+                        payment_info process_payment 
+                        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_support_usage
+                        myaccount_passwd
+                      );
+
+$template_dir = '.';
+
+$DEBUG = 1;
+
+$form_max = 255;
+
+$cgi = new CGI;
+
+unless ( defined $cgi->param('session') ) {
+  do_template('login',{});
+  exit;
+}
+
+if ( $cgi->param('session') eq 'login' ) {
+
+  $cgi->param('username') =~ /^\s*([a-z0-9_\-\.\&]{0,$form_max})\s*$/i
+    or die "illegal username";
+  my $username = $1;
+
+  $cgi->param('domain') =~ /^\s*([\w\-\.]{0,$form_max})\s*$/
+    or die "illegal domain";
+  my $domain = $1;
+
+  $cgi->param('password') =~ /^(.{0,$form_max})$/
+    or die "illegal password";
+  my $password = $1;
+
+  my $rv = login(
+    'username' => $username,
+    'domain'   => $domain,
+    'password' => $password,
+  );
+  if ( $rv->{error} ) {
+    do_template('login', {
+      'error'    => $rv->{error},
+      'username' => $username,
+      'domain'   => $domain,
+    } );
+    exit;
+  } else {
+    $cgi->param('session' => $rv->{session_id} );
+    $cgi->param('action'  => 'myaccount' );
+  }
+}
+
+$session_id = $cgi->param('session');
+
+#order|pw_list XXX ???
+$cgi->param('action') =~
+    /^(myaccount|view_invoice|make_payment|make_ach_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_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
+
+  do_template('login',{});
+  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...
+  %{$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 state 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 {
+        _process_change_info( 'change_pay', 
+          qw( payby payinfo payinfo1 payinfo2 month year payname
+              address1 address2 city county state zip country auto paytype
+              paystate ss stateid stateid_state )
+        );
+}
+
+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'} );
+
+  return {
+    ( map { $_ => $init_data->{$_} }
+          qw( part_pkg security_phrase svc_acct_pop ),
+    ),
+    %$customer_info,
+  };
+}
+
+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 = '';
+
+  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', '');
+  }
+
+  $results ||= order_pkg (
+    'session_id' => $session_id,
+    map { $_ => $cgi->param($_) }
+        qw( custnum pkgpart username _password _password2 sec_phrase popnum )
+  );
+
+
+  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;
+
+  #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/\D//g;
+  $payinfo =~ /^(\d{13,16})$/
+    #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+    or die "illegal card"; #!!!
+  $payinfo = $1;
+  validate($payinfo)
+    #or $error ||= $init_data->{msgcat}{invalid_card}; #. $self->payinfo;
+    or die "invalid card"; #!!!
+
+  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') =~ /^(.{2})$/ or die "illegal state";
+  my $state = $1;
+
+  $cgi->param('zip') =~ /^(.{0,10})$/ or die "illegal zip";
+  my $zip = $1;
+
+  my $save = 0;
+  $save = 1 if $cgi->param('save');
+
+  my $auto = 0;
+  $auto = 1 if $cgi->param('auto');
+
+  $cgi->param('paybatch') =~ /^([\w\-\.]+)$/ or die "illegal paybatch";
+  my $paybatch = $1;
+
+  process_payment(
+    'session_id' => $session_id,
+    'payby'      => 'CARD',
+    'amount'     => $amount,
+    'payinfo'    => $payinfo,
+    'paycvv'     => $paycvv,
+    'month'      => $month,
+    'year'       => $year,
+    'payname'    => $payname,
+    'address1'   => $address1,
+    'address2'   => $address2,
+    'city'       => $city,
+    'state'      => $state,
+    'zip'        => $zip,
+    '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=~ /^(\d+)$/
+    or die "illegal account"; #!!!
+  $payinfo1= $1;
+
+  my $payinfo2 = $cgi->param('payinfo2');
+  $payinfo2=~ /^(\d+)$/
+    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 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',
+    '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_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 $template = new Text::Template( TYPE    => 'FILE',
+                                     SOURCE  => "$template_dir/$name.html",
+                                     DELIMITERS => [ '<%=', '%>' ],
+                                     UNTAINT => 1,                    )
+    or die $Text::Template::ERROR;
+
+  print $cgi->header( '-expires' => 'now' ),
+        $template->fill_in( PACKAGE => 'FS::SelfService::_selfservicecgi',
+                            HASH    => $fill_in
+                          );
+}
+
+#*FS::SelfService::_selfservicecgi::include = \&Text::Template::fill_in_file;
+
+package FS::SelfService::_selfservicecgi;
+
+#use FS::SelfService qw(regionselector expselect popselector);
+use HTML::Entities;
+use FS::SelfService qw(regionselector popselector domainselector);
+
+#false laziness w/agent.cgi
+sub include {
+  my $name = shift;
+  my $template = new Text::Template( TYPE   => 'FILE',
+                                     SOURCE => "$main::template_dir/$name.html",
+                                     DELIMITERS => [ '<%=', '%>' ],
+                                     UNTAINT => 1,                   
+                                   )
+    or die $Text::Template::ERROR;
+
+  $template->fill_in( PACKAGE => 'FS::SelfService::_selfservicecgi',
+                      #HASH    => $fill_in
+                    );
+
+}
+
diff --git a/fs_selfservice/FS-SelfService/cgi/signup-agentselect.html b/fs_selfservice/FS-SelfService/cgi/signup-agentselect.html
new file mode 100755 (executable)
index 0000000..7851c56
--- /dev/null
@@ -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 (executable)
index 0000000..490cefa
--- /dev/null
@@ -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 (executable)
index 0000000..3cf9d25
--- /dev/null
@@ -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 (executable)
index 0000000..40ad03c
--- /dev/null
@@ -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 (executable)
index 0000000..d167efb
--- /dev/null
@@ -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 (executable)
index 0000000..e07b6ee
--- /dev/null
@@ -0,0 +1,349 @@
+#!/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
+             $decline_html $decline_template
+           );
+
+use subs qw( print_form print_okay print_decline
+             success_default decline_default
+           );
+use CGI;
+#use CGI::Carp qw(fatalsToBrowser);
+use Text::Template;
+use Business::CreditCard;
+use HTTP::BrowserDetect;
+use FS::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';
+$decline_html = -e 'decline.html'
+                  ? 'decline.html'
+                  : '/usr/local/freeside/decline.html';
+
+
+if ( -e $ieak_file ) {
+  my $ieak_txt = Text::Template::_load_text($ieak_file)
+    or die $Text::Template::ERROR;
+  $ieak_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+  $ieak_txt = $1;
+  $ieak_txt =~ s/\r//g; # don't double \r on old templates
+  $ieak_txt =~ s/\n/\r\n/g;
+  $ieak_template = new Text::Template ( TYPE => 'STRING', SOURCE => $ieak_txt )
+    or die $Text::Template::ERROR;
+} else {
+  $ieak_template = '';
+}
+
+$agentnum = '';
+if ( -e $signup_html ) {
+  my $signup_txt = Text::Template::_load_text($signup_html)
+    or die $Text::Template::ERROR;
+  $signup_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+  $signup_txt = $1;
+  $signup_template = new Text::Template ( TYPE => 'STRING',
+                                          SOURCE => $signup_txt,
+                                          DELIMITERS => [ '<%=', '%>' ]
+                                        )
+    or die $Text::Template::ERROR;
+  if ( $signup_txt =~
+         /<\s*INPUT TYPE="?hidden"?\s+NAME="?agentnum"?\s+VALUE="?(\d+)"?\s*>/si
+  ) {
+    $agentnum = $1;
+  }
+} else {
+  #too much maintenance hassle to keep in this file
+  die "can't find ./signup.html or /usr/local/freeside/signup.html";
+  #$signup_template = new Text::Template ( TYPE => 'STRING',
+  #                                        SOURCE => &signup_default,
+  #                                        DELIMITERS => [ '<%=', '%>' ]
+  #                                      )
+  #  or die $Text::Template::ERROR;
+}
+
+if ( -e $success_html ) {
+  my $success_txt = Text::Template::_load_text($success_html)
+    or die $Text::Template::ERROR;
+  $success_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+  $success_txt = $1;
+  $success_template = new Text::Template ( TYPE => 'STRING',
+                                           SOURCE => $success_txt,
+                                           DELIMITERS => [ '<%=', '%>' ],
+                                         )
+    or die $Text::Template::ERROR;
+} else {
+  $success_template = new Text::Template ( TYPE => 'STRING',
+                                           SOURCE => &success_default,
+                                           DELIMITERS => [ '<%=', '%>' ],
+                                         )
+    or die $Text::Template::ERROR;
+}
+
+if ( -e $decline_html ) {
+  my $decline_txt = Text::Template::_load_text($decline_html)
+    or die $Text::Template::ERROR;
+  $decline_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+  $decline_txt = $1;
+  $decline_template = new Text::Template ( TYPE => 'STRING',
+                                           SOURCE => $decline_txt,
+                                           DELIMITERS => [ '<%=', '%>' ],
+                                         )
+    or die $Text::Template::ERROR;
+} else {
+  $decline_template = new Text::Template ( TYPE => 'STRING',
+                                           SOURCE => &decline_default,
+                                           DELIMITERS => [ '<%=', '%>' ],
+                                         )
+    or die $Text::Template::ERROR;
+}
+
+$cgi = new CGI;
+
+$init_data = signup_info( 'agentnum'   => $agentnum,
+                          'promo_code' => scalar($cgi->param('promo_code')),
+                          'reg_code'   => uc(scalar($cgi->param('reg_code'))),
+                        );
+
+if (    ( defined($cgi->param('magic')) && $cgi->param('magic') eq 'process' )
+     || ( defined($cgi->param('action')) && $cgi->param('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 ( $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};
+    }
+
+    unless ( $error ) {
+      my $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 username sec_phrase _password popnum refnum
+                agentnum
+              ),
+            grep { /^snarf_/ } $cgi->param
+        ),
+        'payip' => $cgi->remote_host(),
+      } );
+      $error = $rv->{'error'};
+    }
+    #eslaf
+    
+    if ( $error eq '_decline' ) {
+      print_decline();
+    } elsif ( $error ) {
+      #fudge the snarf info
+      no strict 'refs';
+      ${$_} = $cgi->param($_) foreach grep { /^snarf_/ } $cgi->param;
+      print_form();
+    } else {
+      print_okay(
+        'pkgpart' => scalar($cgi->param('pkgpart')),
+      );
+    }
+
+} 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_decline {
+  print $cgi->header( '-expires' => 'now' ),
+        $decline_template->fill_in();
+}
+
+sub print_okay {
+  my %param = @_;
+  my $user_agent = new HTTP::BrowserDetect $ENV{HTTP_USER_AGENT};
+
+  $cgi->param('username') =~ /^(.+)$/
+    or die "fatal: invalid username got past FS::SelfService::new_customer";
+  my $username = $1;
+  $cgi->param('_password') =~ /^(.+)$/
+    or die "fatal: invalid password got past FS::SelfService::new_customer";
+  my $password = $1;
+  ( $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 => {
+            username   => $username,
+            password   => $password,
+            _password  => $password,
+            email_name => $email_name,
+            ac         => $ac,
+            exch       => $exch,
+            loc        => $loc,
+            pkg        => $pkg,
+            part_pkg   => \$part_pkg,
+          });
+  }
+}
+
+sub success_default { #html to use if you don't specify a success file
+  <<'END';
+<HTML><HEAD><TITLE>Signup successful</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Signup successful</FONT><BR><BR>
+Thanks for signing up!
+<BR><BR>
+Signup information for <%= $email_name %>:
+<BR><BR>
+Username: <%= $username %><BR>
+Password: <%= $password %><BR>
+Access number: (<%= $ac %>) / <%= $exch %> - <%= $local %><BR>
+Package: <%= $pkg %><BR>
+</BODY></HTML>
+END
+}
+
+sub decline_default { #html to use if there is a decline
+  <<'END';
+<HTML><HEAD><TITLE>Processing error</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Processing error</FONT><BR><BR>
+There has been an error processing your account.  Please contact customer
+support.
+</BODY></HTML>
+END
+}
+
+# subs for the templates...
+
+package FS::SelfService::_signupcgi;
+use HTML::Entities;
+use FS::SelfService qw(regionselector expselect popselector);
+
diff --git a/fs_selfservice/FS-SelfService/cgi/signup.html b/fs_selfservice/FS-SelfService/cgi/signup.html
new file mode 100755 (executable)
index 0000000..42334ea
--- /dev/null
@@ -0,0 +1,382 @@
+<HTML><HEAD><TITLE><%= $agent || 'ISP' %> Signup form</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8" onUnload="myclose()">
+<script language="JavaScript"><!--
+  var mywindow = -1;
+  function myopen(filename,windowname,properties) {
+    myclose();
+    mywindow = window.open(filename,windowname,properties);
+  }
+  function myclose() {
+    if ( mywindow != -1 )
+      mywindow.close();
+    mywindow = -1
+  }
+//--></script>
+<FONT SIZE=7><%= $agent || 'ISP' %> Signup form</FONT><BR><BR>
+<FONT SIZE="+1" COLOR="#ff0000"><%= $error %></FONT>
+<FORM NAME="OneTrueForm" ACTION="<%= $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="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 / );
+%>
+
+Where did you hear about our service? <SELECT NAME="refnum">
+<%=
+  $OUT .= '<OPTION VALUE="">' unless $refnum;
+  foreach my $part_referral ( @part_referral ) {
+    $OUT .= '<OPTION VALUE="'. $part_referral->{'refnum'}. '"';
+    $OUT .= ' SELECTED' if $part_referral->{'refnum'} eq $refnum;
+    $OUT .= '>'. $part_referral->{'referral'};
+  }
+%>
+</SELECT><BR><BR>
+Contact Information
+<TABLE BGCOLOR="#c0c0c0" BORDER=0 CELLSPACING=0 WIDTH="100%">
+<TR>
+  <TH ALIGN="right"><font color="#ff0000">*</font>Contact name<BR>(last, first)</TH>
+  <TD COLSPAN=5><INPUT TYPE="text" NAME="last" VALUE="<%= $last %>">,
+                <INPUT TYPE="text" NAME="first" VALUE="<%= $first %>"></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Company</TD>
+  <TD COLSPAN=5><INPUT TYPE="text" NAME="company" SIZE=70 VALUE="<%= $company %>"></TD>
+</TR>
+<TR>
+  <TH ALIGN="right"><font color="#ff0000">*</font>Address</TH>
+  <TD COLSPAN=5><INPUT TYPE="text" NAME="address1" SIZE=70 VALUE="<%= $address1 %>"></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">&nbsp;</TD>
+  <TD COLSPAN=5><INPUT TYPE="text" NAME="address2" SIZE=70 VALUE="<%= $address2 %>"></TD>
+</TR>
+<TR>
+  <TH ALIGN="right"><font color="#ff0000">*</font>City</TH>
+  <TD><INPUT TYPE="text" NAME="city" VALUE="<%= $city %>"></TD>
+  <TH ALIGN="right"><font color="#ff0000">*</font>State/Country</TH>
+  <TD>
+    <%=
+        ($county_html, $state_html, $country_html) =
+          regionselector( {
+            selected_county  => $county,
+            selected_state   => $state,
+            selected_country => $country,
+            default_state    => $statedefault,
+            default_country  => $countrydefault,
+            locales          => \@cust_main_county,
+          } );
+        "$county_html $state_html";
+    %>
+  </TD>
+  <TH><font color="#ff0000">*</font>Zip</TH>
+  <TD><INPUT TYPE="text" NAME="zip" SIZE=10 VALUE="<%= $zip %>"></TD>
+</TR>
+<TR>
+  <TH ALIGN="right"><font color="#ff0000">*</font>Country</TH>
+  <TD><%= $country_html %></TD>
+<TR>
+  <TD ALIGN="right">Day Phone</TD>
+  <TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="<%= $daytime %>" SIZE=18></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Night Phone</TD>
+  <TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="<%= $night %>" SIZE=18></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Fax</TD>
+  <TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="<%= $fax %>" SIZE=12></TD>
+</TR>
+<%=
+  $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="#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><font color="#ff0000">*</font>Exp !. expselect("BILL", "12-2037"). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="Accounts Payable">!,
+      'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR><font color="#ff0000">*</font>Exp !. expselect("COMP"),
+      'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="" MAXLENGTH=80>!,
+    );
+
+    if ( $cvv_enabled ) {
+      foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5
+        $payby{$payby} .= qq!<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' => qq!<TABLE BGCOLOR="#c0c0c0" 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><font color="#ff0000">*</font>Exp !. expselect("BILL", $paydate). qq!<BR><font color="#ff0000">*</font>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="$payname">!,
+      'COMP' => qq!Complimentary<BR><font color="#ff0000">*</font>Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR><font color="#ff0000">*</font>Exp !. expselect("COMP", $paydate),
+      'PREPAY' => qq!Prepaid card<BR><font color="#ff0000">*</font><INPUT TYPE="text" NAME="PREPAY_payinfo" VALUE="$payinfo" MAXLENGTH=80>!,
+    );
+
+    if ( $cvv_enabled ) {
+      foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5
+        $paybychecked{$payby} .= qq!<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";
+      }
+    }
+
+use Tie::IxHash;
+use HTML::Widgets::SelectLayers;
+
+  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};
+}
+
+HTML::Widgets::SelectLayers->new(
+  options => \%options,
+  selected_layer => 'CARD',
+  form_name => 'dummy',
+  html_between => '</td></tr></table>',
+  form_action => 'dummy.cgi',
+  layer_callback => sub { my $layer = shift; return $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="#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>
+<TR>
+  <TD ALIGN="right">Username</TD>
+  <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $username %>"></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Password</TD>
+  <TD><INPUT TYPE="password" NAME="_password" VALUE="<%= $_password %>"></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Re-enter Password</TD>
+  <TD><INPUT TYPE="password" NAME="_password2" VALUE="<%= $_password2 %>"></TD>
+</TR>
+<%=
+  if ( $security_phrase ) {
+    $OUT .= <<ENDOUT;
+<TR>
+  <TD ALIGN="right">Security Phrase</TD>
+  <TD><INPUT TYPE="text" NAME="sec_phrase" VALUE="$sec_phrase">
+  </TD>
+</TR>
+ENDOUT
+  } else {
+    $OUT .= '<INPUT TYPE="hidden" NAME="sec_phrase" VALUE="">';
+  }
+%>
+<%=
+  if ( @svc_acct_pop ) {
+    $OUT .= '<TR><TD ALIGN="right">Access number</TD><TD>'.
+            popselector( 'popnum'        => $popnum,
+                         'pops'          => \@svc_acct_pop,
+                         'init_popstate' => $init_popstate,
+                         'popac'         => $popac,
+                         'acstate'       => $acstate,
+                       ).
+            '</TD></TR>';
+  } else {
+    $OUT .= popselector(popnum=>$popnum, pops=>\@svc_acct_pop);
+  }
+%>
+</TABLE>
+
+<%= 
+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' );
+
+    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></BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/stateselect.html b/fs_selfservice/FS-SelfService/cgi/stateselect.html
new file mode 100644 (file)
index 0000000..ba55bff
--- /dev/null
@@ -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 (file)
index 0000000..5eeed59
--- /dev/null
@@ -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 (file)
index 0000000..397cc6c
--- /dev/null
@@ -0,0 +1,11 @@
+<HTML><HEAD><TITLE>Signup successful</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Signup successful</FONT><BR><BR>
+Thanks for signing up!
+<BR><BR>
+Signup information for <%= $email_name %>:
+<BR><BR>
+Username: <%= $username %><BR>
+Password: <%= $password %><BR>
+Access number: (<%= $ac %>) / <%= $exch %> - <%= $local %><BR>
+Package: <%= $pkg %><BR>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/svc_acct.html b/fs_selfservice/FS-SelfService/cgi/svc_acct.html
new file mode 100644 (file)
index 0000000..0024438
--- /dev/null
@@ -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/view_customer.html b/fs_selfservice/FS-SelfService/cgi/view_customer.html
new file mode 100644 (file)
index 0000000..11e4432
--- /dev/null
@@ -0,0 +1,29 @@
+<HTML><HEAD><TITLE>Reseller</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>Reseller</FONT><BR><BR>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_menu') %>
+<TD VALIGN="top">
+
+<%= $message
+      ? "<FONT SIZE=\"+2\"><B>$message</B></FONT><BR><BR>"
+      : ''
+%>
+
+<%= $small_custview %>
+
+<BR>
+
+<TABLE BORDER=0 CELLPADDING=4><TR>
+<%= include('agent_customer_menu') %>
+<TD VALIGN="top">
+
+</TD></TR></TABLE>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
+
+
diff --git a/fs_selfservice/FS-SelfService/cgi/view_invoice.html b/fs_selfservice/FS-SelfService/cgi/view_invoice.html
new file mode 100644 (file)
index 0000000..ad2f4f4
--- /dev/null
@@ -0,0 +1,15 @@
+<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">
+
+<%= $invoice_html %>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
+
+
+
diff --git a/fs_selfservice/FS-SelfService/cgi/view_support_details.html b/fs_selfservice/FS-SelfService/cgi/view_support_details.html
new file mode 100644 (file)
index 0000000..270f9a8
--- /dev/null
@@ -0,0 +1,80 @@
+<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>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage.html b/fs_selfservice/FS-SelfService/cgi/view_usage.html
new file mode 100644 (file)
index 0000000..79d07d4
--- /dev/null
@@ -0,0 +1,59 @@
+<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>Service usage</FONT><BR><BR>
+
+<%= if ( $error ) {
+  $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+} ''; %>
+
+<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>
+<%= foreach my $svc ( @svcs ) {
+      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>';
+    }
+  } %>
+
+</TABLE>
+<BR>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/view_usage_details.html b/fs_selfservice/FS-SelfService/cgi/view_usage_details.html
new file mode 100644 (file)
index 0000000..74a4c3d
--- /dev/null
@@ -0,0 +1,86 @@
+<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>Service 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_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>
+
+</TD></TR></TABLE>
+<HR>
+<FONT SIZE="-2">powered by <a href="http://www.sisd.com/freeside">freeside</a></FONT>
+</BODY></HTML>
diff --git a/fs_selfservice/FS-SelfService/cgi/xmlrpc.cgi b/fs_selfservice/FS-SelfService/cgi/xmlrpc.cgi
new file mode 100644 (file)
index 0000000..559ae04
--- /dev/null
@@ -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 (file)
index 0000000..bdc8e15
--- /dev/null
@@ -0,0 +1,272 @@
+#!/usr/bin/perl -w
+#
+# freeside-selfservice-clientd
+#
+# This is run REMOTELY over ssh by freeside-selfservice-server
+
+use strict;
+use subs qw(spawn logmsg lock_write unlock_write);
+use Fcntl qw(:flock);
+use POSIX qw(:sys_wait_h);
+use Socket;
+use Storable 2.09 qw(nstore_fd fd_retrieve);
+use IO::Handle qw(_IONBF);
+use IO::Select;
+use IO::File;
+
+#STDOUT->setbuf('');
+
+my $tag = scalar(@ARGV) ? '.'.shift : '';
+
+use vars qw( $Debug );
+$Debug = 2; #2 will turn on child logging
+            #3 will log packet contents,#including passwords
+            #4 will log receipts of all packets from server including
+            #  keepalives (big!)
+
+my $socket = "/usr/local/freeside/selfservice_socket$tag";
+my $pid_file = "$socket.pid";
+
+my $log_file = "/usr/local/freeside/selfservice$tag.log";
+
+my $lock_file = "/usr/local/freeside/selfservice$tag.writelock";
+
+#my $me = '[client]';
+
+$|=1;
+
+$SIG{__WARN__} = \&_logmsg;
+
+#read data to be cached or something
+#warn "$me Reading init data\n" if $Debug;
+#my $signup_init = 
+
+warn "Creating $lock_file\n" if $Debug;
+open(LOCKFILE,">$lock_file") or die "can't open $lock_file: $!";
+close LOCKFILE;
+
+warn "Creating $socket\n" if $Debug;
+my $uaddr = sockaddr_un($socket);
+my $proto = getprotobyname('tcp');
+socket(Server,PF_UNIX,SOCK_STREAM,0) or die "socket: $!";
+unlink($socket);
+bind(Server, $uaddr) or die "bind: $!";
+listen(Server,SOMAXCONN) or die "listen: $!";
+
+if ( -e $pid_file ) {
+  open(PIDFILE,"<$pid_file");
+  my $old_pid = <PIDFILE>;
+  close PIDFILE;
+  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 $s = new IO::Select;
+$s->add(\*STDIN);
+$s->add(\*Server);
+
+#for ( $waitedpid = 0;
+#      accept(Client,Server) || $waitedpid;
+#      $waitedpid = 0, close Client)
+#{
+#  next if $waitedpid;
+
+#$SIG{PIPE} = sub { warn "SIGPIPE received" };
+#$SIG{CHLD} = sub { warn "SIGCHLD received" };
+
+#sub REAPER { warn "SIGCHLD received"; my $pid = wait; $SIG{CHLD} = \&REAPER; }
+#sub REAPER { my $pid = wait; $SIG{CHLD} = \&REAPER; }
+#sub REAPER { my $pid = wait; delete $kids{$pid}; $SIG{CHLD} = \&REAPER; }
+#$SIG{CHLD} =  \&REAPER;
+
+my $undisp = 0;
+while (1) {
+
+  &reap_kids;
+
+  warn "waiting for connection\n" if $Debug && !$undisp;
+
+  #my @handles = $s->can_read();
+  my @handles = $s->can_read(5);
+  $undisp = !scalar(@handles);
+  foreach my $handle ( @handles ) {
+
+    if ( $handle == \*STDIN ) {
+
+      warn "receiving packet from server\n" if $Debug > 3;
+
+      my $packet = fd_retrieve(\*STDIN);
+      my $token = $packet->{'_token'};
+
+      if ( $token eq '_keepalive' ) {
+        $undisp = 1;
+        next;
+      }
+
+      warn "received packet from server with token $token\n".
+           ( $Debug > 2
+             ? join('', map { " $_=>$packet->{$_}\n" } keys %$packet )
+             : '' )
+        if $Debug;
+
+     if ( exists($kids{$token}) ) {
+        warn "sending return packet to $token via $kids{$token}\n"
+          if $Debug;
+        nstore_fd($packet, $kids{$token});
+        warn "flushing to $token\n" if $Debug;
+        until ( $kids{$token}->flush ) {
+          warn "WARNING: error flushing: $!";
+          sleep 1;
+        }
+        #no close or delete here - will block waiting for child
+        warn "done with $token\n" if $Debug;
+      } else {
+        warn "WARNING: unknown token $token, discarding message";
+      }
+
+    } elsif ( $handle == \*Server ) {
+
+      until ( accept(Client, Server) ) {
+        warn "WARNING: accept failed: $!";
+        next;
+      }
+
+      warn "received local connection; forking\n" if $Debug;
+
+      spawn sub { #child
+        warn "[child-$$] reading packet from local client" if $Debug > 1;
+        my $packet = fd_retrieve(\*Client);
+        warn "[child-$$] packet received:\n".
+             join('', map { " $_=>$packet->{$_}\n" } keys %$packet )
+          if $Debug > 2;
+        my $command = $packet->{'command'};
+        #handle some commands weirdly?
+        $packet->{_token}=$$;
+
+        warn "[child-$$] locking write stream\n" if $Debug > 1;
+        lock_write;
+
+        warn "[child-$$] sending packet to remote server\n" if $Debug > 1;
+        nstore_fd($packet, \*STDOUT) or die "FATAL: can't send response: $!";
+        
+        warn "[child-$$] flushing write stream\n" if $Debug > 1;
+        STDOUT->flush or die "FATAL: can't flush: $!";
+        
+        warn "[child-$$] releasing write lock\n" if $Debug > 1;
+        unlock_write;
+
+        warn "[child-$$] closing write stream\n" if $Debug > 1;
+        close STDOUT or die "FATAL: can't close write stream: $!"; #??!
+
+        warn "[child-$$] waiting for response from parent\n" if $Debug > 1;
+        my $w = new IO::Select;
+        $w->add(\*STDIN);
+        until ( $w->can_read ) {
+          warn "[child-$$] WARNING: interrupted select: $!\n";
+        }
+        my $rv = fd_retrieve(\*STDIN);
+
+        #close STDIN;
+
+        warn "[child-$$] sending response to local client" if $Debug > 1;
+        nstore_fd($rv, \*Client);
+        Client->flush or die "FATAL: can't flush to local client: $!";
+        close Client or die "FATAL: can't close connection to local client: $!";
+
+        warn "[child-$$] child exiting" if $Debug > 1;
+        exit;
+
+      }; #eo child
+
+      #close Client;
+
+    } else {
+      die "wtf?  $handle";
+    }
+
+  }
+  
+}
+
+sub reap_kids {
+  #warn "reaping kids\n";
+  foreach my $pid ( keys %kids ) {
+    my $kid = waitpid($pid, WNOHANG);
+    if ( $kid > 0 ) {
+      close $kids{$kid};
+      delete $kids{$kid};
+    }
+  }
+  #warn "done reaping\n";
+}
+
+sub spawn {
+    my $coderef = shift;
+
+    unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
+        use Carp;
+        confess "usage: spawn CODEREF";
+    }
+
+    my $pid;
+    #if (!defined($pid = fork)) {
+    my $kid = new IO::Handle;
+    if (!defined($pid = open($kid, '|-'))) {
+        warn "WARNING: cannot fork: $!";
+        return;
+    } elsif ($pid) {
+        warn "begat $pid" if $Debug;
+        $kids{$pid} = $kid;
+        #$kids{$pid}->autoflush;
+        return; # I'm the parent
+    }
+    # else I'm the child -- go spawn
+
+#    open(STDIN,  "<&Client")   || die "can't dup client to stdin";
+#    open(STDOUT, ">&Client")   || die "can't dup client to stdout";
+#     open(STDERR, ">&STDOUT") || die "can't dup stdout to stderr";
+    exit &$coderef();
+}
+
+sub _logmsg {
+  chomp( my $msg = shift );
+  my $log = new IO::File ">>$log_file";
+  die "can't open $log_file: $!" unless defined($log);
+  flock($log, LOCK_EX);
+  seek($log, 0, 2);
+  print $log "[client] [". scalar(localtime). "] [$$] $msg\n";
+  flock($log, LOCK_UN);
+  close $log;
+}
+
+sub lock_write {
+  #broken on freebsd?
+  #flock(STDOUT, LOCK_EX) or die "FATAL: can't lock write stream: $!";
+
+  #open a new one for each kid to get a unique lock
+  open(LOCKFILE,">$lock_file") or die "can't open $lock_file: $!";
+
+  flock(LOCKFILE, LOCK_EX) or die "FATAL: can't lock $lock_file: $!";
+}
+
+sub unlock_write {
+  #broken on freebsd?
+  #flock(STDOUT, LOCK_UN) or die "FATAL: can't release write lock: $!";
+
+  flock(LOCKFILE, LOCK_UN) or die "FATAL: can't unlock $lock_file: $!";
+}
diff --git a/fs_selfservice/FS-SelfService/freeside-selfservice-xmlrpc-server b/fs_selfservice/FS-SelfService/freeside-selfservice-xmlrpc-server
new file mode 100644 (file)
index 0000000..bd4f83b
--- /dev/null
@@ -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/ieak.template b/fs_selfservice/FS-SelfService/ieak.template
new file mode 100755 (executable)
index 0000000..52edaa9
--- /dev/null
@@ -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 (file)
index 0000000..7468ea4
--- /dev/null
@@ -0,0 +1,17 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+
+#########################
+
+# change 'tests => 1' to 'tests => last_test_to_print';
+
+use Test;
+BEGIN { plan tests => 1 };
+use FS::SelfService;
+ok(1); # If we made it this far, we're ok.
+
+#########################
+
+# Insert your test code below, the Test module is use()ed here so read
+# its man page ( perldoc Test ) for help writing this test script.
+
diff --git a/fs_selfservice/fs_passwd_test b/fs_selfservice/fs_passwd_test
new file mode 100755 (executable)
index 0000000..4f8b8a8
--- /dev/null
@@ -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/php/freeside.class.php b/fs_selfservice/php/freeside.class.php
new file mode 100644 (file)
index 0000000..21e89b4
--- /dev/null
@@ -0,0 +1,33 @@
+<?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';
+
+    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 (file)
index 0000000..69174a4
--- /dev/null
@@ -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 (file)
index 0000000..8b1dc19
--- /dev/null
@@ -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_sesmon/FS-SessionClient/Changes b/fs_sesmon/FS-SessionClient/Changes
new file mode 100644 (file)
index 0000000..390a7b9
--- /dev/null
@@ -0,0 +1,5 @@
+Revision history for Perl extension FS::SessionClient
+
+0.01  Wed Oct 18 16:34:36 1999
+        - original version; created by ivan 1.0
+
diff --git a/fs_sesmon/FS-SessionClient/MANIFEST b/fs_sesmon/FS-SessionClient/MANIFEST
new file mode 100644 (file)
index 0000000..162d4e4
--- /dev/null
@@ -0,0 +1,11 @@
+Changes
+MANIFEST
+MANIFEST.SKIP
+Makefile.PL
+SessionClient.pm
+test.pl
+fs_sessiond
+cgi/login.cgi
+cgi/logout.cgi
+bin/freeside-login
+bin/freeside-logout
diff --git a/fs_sesmon/FS-SessionClient/MANIFEST.SKIP b/fs_sesmon/FS-SessionClient/MANIFEST.SKIP
new file mode 100644 (file)
index 0000000..ae335e7
--- /dev/null
@@ -0,0 +1 @@
+CVS/
diff --git a/fs_sesmon/FS-SessionClient/Makefile.PL b/fs_sesmon/FS-SessionClient/Makefile.PL
new file mode 100644 (file)
index 0000000..137b6b8
--- /dev/null
@@ -0,0 +1,10 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+    'NAME'          => 'FS::SessionClient',
+    'VERSION_FROM'  => 'SessionClient.pm', # finds $VERSION
+    'EXE_FILES'     => [ qw(fs_sessiond bin/freeside-login bin/freeside-logout) ],
+    'INSTALLSCRIPT' => '/usr/local/sbin',
+    'PERM_RWX'      => '750',
+);
diff --git a/fs_sesmon/FS-SessionClient/SessionClient.pm b/fs_sesmon/FS-SessionClient/SessionClient.pm
new file mode 100644 (file)
index 0000000..0d3f86b
--- /dev/null
@@ -0,0 +1,118 @@
+package FS::SessionClient;
+
+use strict;
+use vars qw($AUTOLOAD $VERSION @ISA @EXPORT_OK $fs_sessiond_socket);
+use Exporter;
+use Socket;
+use FileHandle;
+use IO::Handle;
+
+$VERSION = '0.01';
+
+@ISA = qw( Exporter );
+@EXPORT_OK = qw( login logout portnum );
+
+$fs_sessiond_socket = "/usr/local/freeside/fs_sessiond_socket";
+
+$ENV{'PATH'} ='/usr/bin:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+my $freeside_uid = scalar(getpwnam('freeside'));
+die "not running as the freeside user\n" if $> != $freeside_uid;
+
+=head1 NAME
+
+FS::SessionClient - Freeside session client API
+
+=head1 SYNOPSIS
+
+  use FS::SessionClient qw( login portnum logout );
+
+  $error = login ( {
+    'username' => $username,
+    'password' => $password,
+    'login'    => $timestamp,
+    'portnum'  => $portnum,
+  } );
+
+  $portnum = portnum( { 'ip' => $ip } ) or die "unknown ip!"
+  $portnum = portnum( { 'nasnum' => $nasnum, 'nasport' => $nasport } )
+    or die "unknown nasnum/nasport";
+
+  $error = logout ( {
+    'username' => $username,
+    'password' => $password,
+    'logout'   => $timestamp,
+    'portnum'  => $portnum,
+  } );
+
+=head1 DESCRIPTION
+
+This modules provides an API for a remote session application.
+
+It needs to be run as the freeside user.  Because of this, the program which
+calls these subroutines should be written very carefully.
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item login HASHREF
+
+HASHREF should have the following keys: username, password, login and portnum.
+login is a UNIX timestamp; if not specified, will default to the current time.
+Starts a new session for the specified user and portnum.  The password is
+optional, but must be correct if specified.
+
+Returns a scalar error message, or the empty string for success.
+
+=item portnum
+
+HASHREF should contain a single key: ip, or the two keys: nasnum and nasport.
+Returns a portnum suitable for the login and logout subroutines, or false
+on error.
+
+=item logout HASHREF
+
+HASHREF should have the following keys: usrename, password, logout and portnum.
+logout is a UNIX timestamp; if not specified, will default to the current time.
+Starts a new session for the specified user and portnum.  The password is
+optional, but must be correct if specified.
+
+Returns a scalar error message, or the empty string for success.
+
+=cut
+
+sub AUTOLOAD {
+  my $hashref = shift;
+  my $method = $AUTOLOAD;
+  $method =~ s/^.*:://;
+  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
+  connect(SOCK, sockaddr_un($fs_sessiond_socket)) or die "connect: $!";
+  print SOCK "$method\n";
+
+  print SOCK join("\n", %{$hashref}, 'END' ), "\n";
+  SOCK->flush;
+
+  chomp( my $r = <SOCK> );
+  $r;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<fs_sessiond>
+
+=cut
+
+1;
+
+
+
diff --git a/fs_sesmon/FS-SessionClient/bin/freeside-login b/fs_sesmon/FS-SessionClient/bin/freeside-login
new file mode 100644 (file)
index 0000000..a6d4751
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/perl -Tw
+
+#false-laziness hack w freeside-logout
+
+use strict;
+use FS::SessionClient qw( login portnum );
+
+my $username = shift;
+
+my $portnum;
+if ( scalar(@ARGV) == 1 ) {
+  my $arg = shift;
+  if ( $arg =~ /^(\d+)$/ ) {
+    $portnum = $1;
+  } elsif ( $arg =~ /^([\d\.]+)$/ ) {
+    $portnum = portnum( { 'ip' => $1 } ) or die "unknown ip!"
+  } else {
+    &usage;
+  }
+} elsif ( scalar(@ARGV) == 2 ) {
+  $portnum = portnum( { 'nasnum' => shift, 'nasport' => shift } )
+    or die "unknown nasnum/nasport";
+} else {
+  &usage;
+}
+
+my $error = login ( {
+  'username' => $username,
+  'portnum'  => $portnum,
+} );
+
+warn $error if $error;
+
+sub usage {
+  die "Usage:\n\n  freeside-login username ( portnum | ip | nasnum nasport )";
+}
diff --git a/fs_sesmon/FS-SessionClient/bin/freeside-logout b/fs_sesmon/FS-SessionClient/bin/freeside-logout
new file mode 100644 (file)
index 0000000..9b4ecfe
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/perl -Tw
+
+#false-laziness hack w freeside-login
+
+use strict;
+use FS::SessionClient qw( logout portnum );
+
+my $username = shift;
+
+my $portnum;
+if ( scalar(@ARGV) == 1 ) {
+  my $arg = shift;
+  if ( $arg =~ /^(\d+)$/ ) {
+    $portnum = $1;
+  } elsif ( $arg =~ /^([\d\.]+)$/ ) {
+    $portnum = portnum( { 'ip' => $1 } ) or die "unknown ip!"
+  } else {
+    &usage;
+  }
+} elsif ( scalar(@ARGV) == 2 ) {
+  $portnum = portnum( { 'nasnum' => shift, 'nasport' => shift } )
+    or die "unknown nasnum/nasport";
+} else {
+  &usage;
+}
+
+my $error = logout ( {
+  'username' => $username,
+  'portnum'  => $portnum,
+} );
+
+warn $error if $error;
+
+sub usage {
+  die "Usage:\n\n  freeside-logout username ( portnum | ip | nasnum nasport )";
+}
diff --git a/fs_sesmon/FS-SessionClient/cgi/login.cgi b/fs_sesmon/FS-SessionClient/cgi/login.cgi
new file mode 100644 (file)
index 0000000..0307c5a
--- /dev/null
@@ -0,0 +1,108 @@
+#!/usr/bin/perl -Tw
+
+#false-laziness hack w logout.cgi
+
+use strict;
+use vars qw( $cgi $username $password $error $ip $portnum );
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+use FS::SessionClient qw( login portnum );
+
+$cgi = new CGI;
+
+if ( defined $cgi->param('magic') ) {
+  $cgi->param('username') =~ /^\s*(\w{1,255})\s*$/ or do {
+    $error = "Illegal username";
+    &print_form;
+    exit;
+  };
+  $username = $1;
+  $cgi->param('password') =~ /^([^\n]{0,255})$/ or die "guru meditation #420";
+  $password = $1;
+  #$ip = $cgi->remote_host;
+  $ip = $ENV{REMOTE_ADDR};
+  $ip =~ /^([\d\.]+)$/ or die "illegal ip: $ip";
+  $ip = $1;
+  $portnum = portnum( { 'ip' => $1 } ) or do {
+    $error = "You appear to be coming from an unknown IP address.  Verify ".
+             "that your computer is set to obtain an IP address automatically ".
+             "via DHCP.";
+    &print_form;
+    exit;
+  };
+
+  ( $error = login ( {
+    'username' => $username,
+    'portnum'  => $portnum,
+    'password' => $password,
+  } ) )
+    ? &print_form()
+    : &print_okay();
+
+} else {
+  $username = '';
+  $password = '';
+  $error = '';
+  &print_form;
+}
+
+sub print_form {
+  my $self_url = $cgi->self_url;
+
+  print $cgi->header( '-expires' => 'now' ), <<END;
+<HTML><HEAD><TITLE>login</TITLE></HEAD>
+<BODY BGCOLOR="#FFFFFF">
+END
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>! if $error;
+
+print <<END;
+<FORM ACTION="$self_url" METHOD="POST">
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+<TABLE BORDER="0" CELLSPACING="0" CELLPADDING="4" ALIGN="center">
+<TR>
+        <TD ALIGN="center" COLSPAN="2">
+      <STRONG>Welcome</STRONG>
+      </TD>
+</TR>
+<TR>
+      <TD ALIGN="right">
+      Username
+      </TD>
+      <TD ALIGN="left">
+      <INPUT TYPE="text" NAME="username" VALUE="$username">
+      </TD>
+</TR>
+<TR>
+      <TD ALIGN="right">
+      Password
+      </TD>
+      <TD ALIGN="left">
+      <INPUT TYPE="password" NAME="password">
+      </TD>
+</TR>
+<TR>
+      <TD ALIGN="center" COLSPAN="2">
+      <INPUT TYPE="submit" VALUE=" Login ">
+      </TD>
+</TR>
+</TABLE>
+</FORM>
+</BODY>
+</HTML>
+END
+
+}
+
+sub print_okay {
+  print $cgi->header( '-expires' => 'now' ), <<END;
+<HTML><HEAD><TITLE>login sucessful</TITLE></HEAD>
+<BODY>login successful, etc.
+</BODY>
+</HTML>
+END
+}
+
+sub usage {
+  die "Usage:\n\n  freeside-login username ( portnum | ip | nasnum nasport )";
+}
diff --git a/fs_sesmon/FS-SessionClient/cgi/logout.cgi b/fs_sesmon/FS-SessionClient/cgi/logout.cgi
new file mode 100644 (file)
index 0000000..95cef98
--- /dev/null
@@ -0,0 +1,83 @@
+#!/usr/bin/perl -Tw
+
+#false-laziness hack w login.cgi
+
+use strict;
+use vars qw( $cgi $username $password $error $ip $portnum );
+use CGI;
+use CGI::Carp qw(fatalsToBrowser);
+use FS::SessionClient qw( logout portnum );
+
+$cgi = new CGI;
+
+if ( defined $cgi->param('magic') ) {
+  $cgi->param('username') =~ /^\s*(\w{1,255})\s*$/ or do {
+    $error = "Illegal username";
+    &print_form;
+    exit;
+  };
+  $username = $1;
+  $cgi->param('password') =~ /^([^\n]{0,255})$/ or die "guru meditation #420";
+  $password = $1;
+  #$ip = $cgi->remote_host;
+  $ip = $ENV{REMOTE_ADDR};
+  $ip =~ /^([\d\.]+)$/ or die "illegal ip: $ip";
+  $ip = $1;
+  $portnum = portnum( { 'ip' => $1 } ) or do {
+    $error = "You appear to be coming from an unknown IP address.  Verify ".
+             "that your computer is set to obtain an IP address automatically ".
+             "via DHCP.";
+    &print_form;
+    exit;
+  };
+
+  ( $error = logout ( {
+    'username' => $username,
+    'portnum'  => $portnum,
+    'password' => $password,
+  } ) )
+    ? &print_form()
+    : &print_okay();
+
+} else {
+  $username = '';
+  $password = '';
+  $error = '';
+  &print_form;
+}
+
+sub print_form {
+  my $self_url = $cgi->self_url;
+
+  print $cgi->header( '-expires' => 'now' ), <<END;
+<HTML><HEAD><TITLE>logout</TITLE></HEAD>
+<BODY>
+END
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>! if $error;
+
+print <<END;
+<FORM ACTION="$self_url" METHOD=POST>
+<INPUT TYPE="hidden" NAME="magic" VALUE="process">
+Username <INPUT TYPE="text" NAME="username" VALUE="$username"><BR>
+Password <INPUT TYPE="password" NAME="password"><BR>
+<INPUT TYPE="submit">
+</FORM>
+</BODY>
+</HTML>
+END
+
+}
+
+sub print_okay {
+  print $cgi->header( '-expires' => 'now' ), <<END;
+<HTML><HEAD><TITLE>logout sucessful</TITLE></HEAD>
+<BODY>logout successful, etc.
+</BODY>
+</HTML>
+END
+}
+
+sub usage {
+  die "Usage:\n\n  freeside-logout username ( portnum | ip | nasnum nasport )";
+}
diff --git a/fs_sesmon/FS-SessionClient/fs_sessiond b/fs_sesmon/FS-SessionClient/fs_sessiond
new file mode 100644 (file)
index 0000000..bfdb20a
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/perl -Tw
+#
+# fs_sessiond
+#
+# This is run REMOTELY over ssh by fs_session_server
+#
+
+use strict;
+use Socket;
+
+use vars qw( $Debug );
+
+$Debug = 1;
+
+my $fs_sessiond_socket = "/usr/local/freeside/fs_sessiond_socket";
+
+$ENV{'PATH'} ='/usr/local/bin:/usr/bin:/usr/ucb:/bin';
+$ENV{'SHELL'} = '/bin/sh';
+$ENV{'IFS'} = " \t\n";
+$ENV{'CDPATH'} = '';
+$ENV{'ENV'} = '';
+$ENV{'BASH_ENV'} = '';
+
+$|=1;
+
+my $me = "[fs_sessiond]";
+
+warn "$me starting\n" if $Debug;
+#nothing to read from server
+
+warn "$me creating $fs_sessiond_socket\n" if $Debug;
+my $uaddr = sockaddr_un($fs_sessiond_socket);
+my $proto = getprotobyname('tcp');
+socket(Server,PF_UNIX,SOCK_STREAM,0) or die "socket: $!";
+unlink($fs_sessiond_socket);
+bind(Server, $uaddr) or die "bind: $!";
+listen(Server,SOMAXCONN) or die "listen: $!";
+
+warn "$me entering main loop\n" if $Debug;
+my $paddr;
+for ( ; $paddr = accept(Client,Server); close Client) {
+
+  chomp( my $command = <Client> );
+
+  if ( $command eq 'login' || $command eq 'logout' || $command eq 'portnum' ) {
+    warn "$me reading data from local client\n" if $Debug;
+    my @data;
+    my $dos = 0;
+    push @data, scalar(<Client>) until $dos++ == 99 || $data[$#data] eq "END\n";
+    if ( $dos == 99 ) { 
+      warn "$me WARNING: DoS attempt!" 
+    } else {
+      warn "$me sending data to remote server\n" if $Debug;
+      print "$command\n", @data;
+      warn "$me reading result from remote server\n" if $Debug;
+      my $error = <STDIN>;
+      warn "$me sending error to local client\n" if $Debug;
+      print Client $error;
+    }
+  } else {
+    warn "$me WARNING: unexpected command from client: $command";
+  }
+
+}
+
diff --git a/fs_sesmon/FS-SessionClient/test.pl b/fs_sesmon/FS-SessionClient/test.pl
new file mode 100644 (file)
index 0000000..4b9ae17
--- /dev/null
@@ -0,0 +1,21 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+
+######################### We start with some black magic to print on failure.
+
+# Change 1..1 below to 1..last_test_to_print .
+# (It may become useful if the test is moved to ./t subdirectory.)
+
+BEGIN { $| = 1; print "1..1\n"; }
+END {print "not ok 1\n" unless $loaded;}
+#use FS::SessionClient;
+#sigh, "not running as the freeside user"
+$loaded = 1;
+print "ok 1\n";
+
+######################### End of black magic.
+
+# Insert your test code below (better if it prints "ok 13"
+# (correspondingly "not ok 13") depending on the success of chunk 13
+# of the test code):
+
diff --git a/fs_sesmon/fs_session_server b/fs_sesmon/fs_session_server
new file mode 100644 (file)
index 0000000..00229f8
--- /dev/null
@@ -0,0 +1,140 @@
+#!/usr/bin/perl -Tw
+#
+# fs_session_server
+#
+
+use strict;
+use vars qw( $opt $Debug );
+use IO::Handle;
+use Net::SSH qw(sshopen2);
+use FS::UID qw(adminsuidsetup dbh);
+use FS::Record qw( qsearchs ); #qsearch );
+#use FS::cust_main_county;
+#use FS::cust_main;
+use FS::session;
+use FS::port;
+use FS::svc_acct;
+
+#require "configfile";
+$Debug = 1;
+
+my $user = shift or die &usage;
+&adminsuidsetup( $user ); 
+
+my $machine = shift or die &usage;
+
+my $fs_sessiond = "/usr/local/sbin/fs_sessiond";
+
+my $me = "[fs_session_server]";
+
+while (1) {
+  my($reader, $writer) = (new IO::Handle, new IO::Handle);
+  $writer->autoflush(1);
+  warn "$me Connecting to $machine\n" if $Debug;
+  sshopen2($machine,$reader,$writer,$fs_sessiond);
+
+  warn "$me Entering main loop\n" if $Debug;
+  while (1) {
+    warn "$me Reading (waiting for) data\n" if $Debug;
+    my $command = scalar(<$reader>);
+    chomp $command;
+    #DoS protection here too, to protect against a compromised client?  *sigh*
+    my %hash;
+    while ( ( my $key = scalar(<$reader>) ) ne "END\n" ) {
+      chomp $key;
+      chomp( $hash{$key} = scalar(<$reader>) );
+    }
+
+    if ( $command eq 'login' ) {
+      my $error = &login(\%hash);
+      print $writer "$error\n";
+    } elsif ( $command eq 'logout' ) {
+      my $error = &logout(\%hash);
+      print $writer "$error\n";
+    } elsif ( $command eq 'portnum' ) {
+      my $port;
+      if ( exists $hash{'ip'} ) {
+        $hash{'ip'} =~ /^([\d\.]+)$/ or $1='nomatch';
+        $port = qsearchs('port', { 'ip' => $1 } );
+      } else {
+        $hash{'nasnum'} =~ /^(\d+)$/ and my $nasnum = $1;
+        $hash{'nasport'} =~ /^(\d+)$/ and my $nasport = $1;
+        $port = qsearchs('port', { 'nasnum'=>$nasnum, 'nasport'=>$nasport } );
+      }
+      print $writer ( $port ? $port->portnum : '' ), "\n";
+    } else {
+      warn "$me WARNING: unrecognized command: $command";
+    }
+  }
+  #won't ever reach without code above to throw out of loop, but...
+  close $writer;
+  close $reader;
+  warn "connection to $machine lost!\n";
+  sleep 5;
+  warn "reconnecting...\n";
+}
+
+sub login {
+  my $href = shift;
+  $href->{'username'} =~ /^([a-z0-9_\-\.]+)$/ or return "Illegal username";
+  my $username = $1;
+  my $svc_acct = qsearchs('svc_acct', { 'username' => $username } )
+    or return "Unknown user";
+  return "Incorrect password"
+    if exists($href->{'password'})
+       && $href->{'password'} ne $svc_acct->_password;
+  return "Time limit exceeded" unless $svc_acct->seconds;
+  my $session = new FS::session {
+    'portnum' => $href->{'portnum'},
+    'svcnum'  => $svc_acct->svcnum,
+    'login'   => $href->{'login'},
+  };
+  $session->insert;
+}
+
+sub logout {
+  my $href = shift;
+  $href->{'username'} =~ /^([a-z0-9_\-\.]+)$/ or return "Illegal username";
+  my $username = $1;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+  my $svc_acct =
+    qsearchs('svc_acct', { 'username' => $username }, '', 'FOR UPDATE' )
+    or return "Unknown user";
+  return "Incorrect password"
+    if exists($href->{'password'})
+       && $href->{'password'} ne $svc_acct->_password;
+  my $session = qsearchs( 'session', {
+                                       'portnum' => $href->{'portnum'},
+                                       'svcnum'  => $svc_acct->svcnum,
+                                       'logout'  => '',
+                                     },
+                          '', 'FOR UPDATE'
+  );
+  unless ( $session ) {
+    $dbh->rollback;
+    return "No currently open sessions found for that user/port!";
+  }
+  my $nsession = new FS::session ( { $session->hash } );
+  warn "$nsession replacing $session";
+  my $error = $nsession->replace($session);
+  if ( $error ) {
+    $dbh->rollback;
+    return "can't logout: $error";
+  }
+  my $time = $nsession->logout - $nsession->login;
+  my $new_svc_acct = new FS::svc_acct ( { $svc_acct->hash } );
+  my $seconds = $new_svc_acct->seconds;
+  $seconds -= $time;
+  $seconds = 0 if $seconds < 0;
+  $new_svc_acct->seconds( $seconds );
+  $error = $new_svc_acct->replace( $svc_acct );
+  warn "can't debit time: $error\n"; #don't want to rollback, though
+  $dbh->commit or die $dbh->errstr;
+  ''
+}
+
+sub usage {
+  die "Usage:\n\n  fs_session_server user machine\n";
+}
+
diff --git a/htetc/freeside-base1.99.conf b/htetc/freeside-base1.99.conf
new file mode 100644 (file)
index 0000000..c1c187c
--- /dev/null
@@ -0,0 +1,21 @@
+PerlModule Apache::compat
+
+#PerlModule Apache::DBI
+
+PerlModule HTML::Mason
+PerlSetVar MasonArgsMethod CGI
+PerlModule HTML::Mason::ApacheHandler
+
+PerlRequire "%%%MASON_HANDLER%%%"
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%>
+AuthName Freeside
+AuthType Basic
+AuthUserFile /usr/local/etc/freeside/htpasswd
+require valid-user
+<Files ~ (\.cgi|\.html)>
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Files>
+</Directory>
+
diff --git a/htetc/freeside-base1.conf b/htetc/freeside-base1.conf
new file mode 100644 (file)
index 0000000..3f6bd0e
--- /dev/null
@@ -0,0 +1,18 @@
+#PerlModule Apache::DBI
+
+PerlModule HTML::Mason
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%>
+AuthName Freeside
+AuthType Basic
+AuthUserFile /usr/local/etc/freeside/htpasswd
+require valid-user
+<Files ~ (\.cgi|\.html)>
+AddHandler perl-script .cgi .html
+PerlHandler HTML::Mason
+</Files>
+<Perl>
+require "%%%MASON_HANDLER%%%";
+</Perl>
+</Directory>
+
diff --git a/htetc/freeside-base2.conf b/htetc/freeside-base2.conf
new file mode 100644 (file)
index 0000000..38f7840
--- /dev/null
@@ -0,0 +1,21 @@
+PerlModule Apache2::compat
+
+#PerlModule Apache::DBI
+
+PerlModule HTML::Mason
+PerlSetVar MasonArgsMethod CGI
+PerlModule HTML::Mason::ApacheHandler
+
+PerlRequire "%%%MASON_HANDLER%%%"
+
+<Directory %%%FREESIDE_DOCUMENT_ROOT%%%>
+AuthName Freeside
+AuthType Basic
+AuthUserFile /usr/local/etc/freeside/htpasswd
+require valid-user
+<Files ~ (\.cgi|\.html)>
+SetHandler perl-script
+PerlHandler HTML::Mason
+</Files>
+</Directory>
+
diff --git a/htetc/freeside-rt.conf b/htetc/freeside-rt.conf
new file mode 100644 (file)
index 0000000..9b5ccf8
--- /dev/null
@@ -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 (file)
index 0000000..caa266d
--- /dev/null
@@ -0,0 +1,390 @@
+#!/usr/bin/perl
+
+package HTML::Mason;
+
+use strict;
+use vars qw($r);
+use HTML::Mason 1.27; #http://www.masonhq.com/?ApacheModPerl2Redirect
+use HTML::Mason::Interp;
+use HTML::Mason::Compiler::ToObject;
+
+# 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;###
+
+# List of modules that you want to use from components (see Admin
+# manual for details)
+#{  package HTML::Mason::Commands;
+#   use CGI;
+#}
+
+if ( %%%RT_ENABLED%%% ) {
+ eval '
+   use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
+   use RT;
+   use vars qw($Nobody $SystemUser);
+   RT::LoadConfig();
+ ';
+ die $@ if $@;
+}
+
+# Create Mason objects
+
+my %interp = (
+  request_class        => 'HTML::Mason::Request::ApacheHandler',
+  data_dir             => '%%%MASONDATA%%%',
+  error_mode           => 'output',
+  error_format         => 'html',
+  ignore_warnings_expr => '.',
+  comp_root            => [
+                            [ 'freeside' => '%%%FREESIDE_DOCUMENT_ROOT%%%'    ],
+                            [ 'rt'       => '%%%FREESIDE_DOCUMENT_ROOT%%%/rt' ],
+                          ],
+);
+
+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]}. "'";
+                    }
+                  },
+);
+
+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)],
+                  ),
+);
+
+my $ah = new HTML::Mason::ApacheHandler (
+  interp      => $fs_interp,
+  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) = @_;
+
+    # If you plan to intermix images in the same directory as
+    # components, activate the following to prevent Mason from
+    # evaluating image files as components.
+    #
+    #return -1 if $r->content_type && $r->content_type !~ m|^text/|i;
+
+    #rar
+    { package HTML::Mason::Commands;
+      use strict;
+      use vars qw( $cgi $p $fsurl);
+      use vars qw( %session );
+      use CGI 2.47 qw(-private_tempfiles);
+      #use CGI::Carp qw(fatalsToBrowser);
+      use CGI::Cookie;
+      use List::Util qw( max min );
+      use Data::Dumper;
+      use Date::Format;
+      use Date::Parse;
+      use Time::Local;
+      use Time::Duration;
+      use DateTime;
+      use DateTime::Format::Strptime;
+      use Lingua::EN::Inflect qw(PL);
+      use Tie::IxHash;
+      use URI::Escape;
+      use HTML::Entities;
+      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 String::Approx qw(amatch);
+      use Chart::LinesPoints;
+      use Chart::Mountain;
+      use Color::Scheme;
+      use HTML::Widgets::SelectLayers 0.07;
+      use Locale::Country;
+      use FS;
+      use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name);
+      use FS::Record qw(qsearch qsearchs fields dbdef str2time_sql);
+      use FS::Conf;
+      use FS::CGI qw(header menubar popurl rooturl table itable ntable idiot
+                     eidiot small_custview myexit http_header);
+      use FS::UI::Web qw(svc_url);
+      use FS::UI::bytecount;
+      use FS::Msgcat qw(gettext geterror);
+      use FS::Misc qw( send_email send_fax states_hash counties state_label );
+      use FS::Report::Table::Monthly;
+      use FS::TicketSystem;
+
+      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_county;
+      use FS::part_pkg_taxclass;
+      use FS::cust_pay;
+      use FS::cust_pkg;
+      use FS::cust_pkg_reason;
+      use FS::cust_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::msgcat;
+      use FS::rate;
+      use FS::rate_region;
+      use FS::rate_prefix;
+      use FS::payment_gateway;
+      use FS::agent_payment_gateway;
+      use FS::XMLRPC;
+      use FS::payby;
+      use FS::cdr;
+      use FS::inventory_class;
+      use FS::inventory_item;
+      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::reason_type;
+      use FS::reason;
+      use FS::cust_main_note;
+
+      if ( %%%RT_ENABLED%%% ) {
+        eval '
+          use RT::Tickets;
+          use RT::Transactions;
+          use RT::Users;
+          use RT::CurrentUser;
+          use RT::Templates;
+          use RT::Queues;
+          use RT::ScripActions;
+          use RT::ScripConditions;
+          use RT::Scrips;
+          use RT::Groups;
+          use RT::GroupMembers;
+          use RT::CustomFields;
+          use RT::CustomFieldValues;
+          use RT::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;
+
+          #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) ) { #profiling redirect
+
+          my $page =
+            qq!<HTML><BODY>Redirect to <A HREF="$location">$location</A>!.
+            '<BR><BR><PRE>'.
+              ( UNIVERSAL::can(dbh, 'sprintProfile')
+                  ? encode_entities(dbh->sprintProfile())
+                  : 'DBIx::Profile missing sprintProfile method;'.
+                    'unpatched or too old?'                        ).
+            #"\n\n". &sprintAutoProfile().  '</PRE>'.
+            "\n\n".                         '</PRE>'.
+            '</BODY></HTML>';
+          dbh->{'private_profile'} = {};
+          return $page;
+
+        } else { #normal redirect
+
+          $m->redirect($location);
+          '';
+
+        }
+
+      };
+      
+      unless ( $HTML::Mason::r->filename =~ /\/rt\/.*NoAuth/ ) { #RT
+        $cgi = new CGI;
+        &cgisuidsetup($cgi);
+        #&cgisuidsetup($r);
+        $p = popurl(2);
+        $fsurl = rooturl();
+      }
+
+      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) ) { #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'} = {};
+
+          #whew.  removing this is all that's needed to fix the annoying
+          #blank-page-instead-of-profiling-redirect-when-called-from-an-include
+          #bug triggered by mason 1.32
+          #my $rv = $m->abort(200);
+
+        } else { #normal redirect
+
+          $m->redirect($location);
+
+        }
+
+      }
+
+    } # end package HTML::Mason::Commands;
+
+    ###Module::Refresh->refresh;###
+
+    $r->content_type('text/html');
+    #eorar
+
+    my $headers = $r->headers_out;
+    $headers->{'Cache-control'} = 'no-cache';
+    #$r->no_cache(1);
+    $headers->{'Expires'} = '0';
+
+#    $r->send_http_header;
+
+    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 {
+
+      $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/httemplate/.htaccess b/httemplate/.htaccess
new file mode 100755 (executable)
index 0000000..f8c6b9c
--- /dev/null
@@ -0,0 +1,3 @@
+AuthName        Freeside
+AuthType        Basic
+require valid-user
diff --git a/httemplate/autohandler b/httemplate/autohandler
new file mode 100644 (file)
index 0000000..bdea505
--- /dev/null
@@ -0,0 +1,35 @@
+% $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' ) {
+
+    ## barely worth it, just in case someone tries to use profiling on a
+    ## non-RT install
+    #eval "use Text::Wrapper;";
+    #die $@ if $@;
+
+    my $wrapper = new Text::Wrapper( columns => 80 );
+    my $text = dbh->sprintProfile();
+    #my $text = $wrapper->wrap( dbh->sprintProfile() );
+    $text =~ s/^/                                                          /mg;
+    
+    $profile = '<PRE>'.
+               encode_entities( $text ).
+               #"\n\n". &sprintAutoProfile(). '</PRE>';
+               "\n\n".                        '</PRE>';
+  } 
+
+  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 (file)
index 0000000..736ab9c
--- /dev/null
@@ -0,0 +1,108 @@
+<% include( 'elements/browse.html',
+                 'title'       => 'Internal Access Groups',
+                 'menubar'     => [ 'Internal users' => $p.'browse/access_user.html', ],
+                 'html_init'   => $html_init,
+                 'name'        => 'internal access 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 = 
+  "Internal access groups control access to the back-office interface.<BR><BR>".
+  qq!<A HREF="${p}edit/access_group.html"><I>Add an internal access 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 { warn "$access_group->access_right($_): ".
+                              $access_group->access_right($_). "\n";
+                         $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 (file)
index 0000000..2aa752b
--- /dev/null
@@ -0,0 +1,61 @@
+<% include( 'elements/browse.html',
+                 'title'              => 'Internal Users',
+                 'menubar'            => [ 'Internal access groups' => $p.'browse/access_group.html', ],
+                 'html_init'          => $html_init,
+                 'name'               => 'internal users',
+                 '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 = 
+  "Internal users have access to the back-office interface.  Typically, this is your employees and contractors.  In a VISP 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 internal user</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 $count_query = 'SELECT COUNT(*) FROM access_user';
+
+my $link = [ $p.'edit/access_user.html?', 'usernum' ];
+
+my @header = ( '#',       'Username', 'Full name', 'Groups'    );
+my @fields = ( 'usernum', 'username', 'name',      $groups_sub );
+my $align = 'rlll';
+my @links = ( $link, $link, $link, '' );
+
+</%init>
diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi
new file mode 100644 (file)
index 0000000..eac7cf7
--- /dev/null
@@ -0,0 +1,80 @@
+<% include('/elements/header.html', 'Address Blocks') %>
+
+<% include('/elements/error.html') %>
+
+<%table()%>
+% foreach $block (sort {$a->NetAddr cmp $b->NetAddr} @addr_block) { 
+
+  <TR>
+    <TD><%$block->NetAddr%></TD>
+% if (my $router = $block->router) { 
+% if (scalar($block->svc_broadband) == 0) { 
+
+    <TD>
+      <%$router->routername%>
+    </TD>
+    <TD>
+      <FORM ACTION="<%$path%>/deallocate.cgi" METHOD="POST">
+        <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>">
+        <INPUT TYPE="submit" NAME="submit" VALUE="Deallocate">
+      </FORM>
+    </TD>
+% } else { 
+
+    <TD COLSPAN="2">
+    <%$router->routername%>
+    </TD>
+% } 
+% } else { 
+
+    <TD>
+      <FORM ACTION="<%$path%>/allocate.cgi" METHOD="POST">
+        <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>">
+        <SELECT NAME="routernum" SIZE="1">
+% foreach (@router) { 
+
+          <OPTION VALUE="<%$_->routernum %>"><%$_->routername%></OPTION>
+% } 
+
+        </SELECT>
+        <INPUT TYPE="submit" NAME="submit" VALUE="Allocate">
+      </FORM>
+    </TD>
+    <TD>
+      <FORM ACTION="<%$path%>/split.cgi" METHOD="POST">
+        <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$block->blocknum%>">
+        <INPUT TYPE="submit" NAME="submit" VALUE="Split">
+      </FORM>
+    </TD>
+  </TR>
+% }
+% } 
+
+  <TR><TD COLSPAN="3"><BR></TD></TR>
+  <TR>
+    <FORM ACTION="<%$path%>/add.cgi" METHOD="POST">
+    <TD>Gateway/Netmask</TD>
+    <TD>
+      <INPUT TYPE="text" NAME="ip_gateway" SIZE="15">/<INPUT TYPE="text" NAME="ip_netmask" SIZE="2">
+    </TD>
+    <TD>
+      <INPUT TYPE="submit" NAME="submit" VALUE="Add">
+    </TD>
+    </FORM>
+  </TR>
+</TABLE>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @addr_block = qsearch('addr_block', {});
+my @router = qsearch('router', {});
+my $block;
+my $p2 = popurl(2);
+my $path = $p2 . "edit/process/addr_block";
+
+</%init>
diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi
new file mode 100755 (executable)
index 0000000..234bfa7
--- /dev/null
@@ -0,0 +1,401 @@
+<% 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"><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="grid" BGCOLOR="<% $bgcolor %>">
+          <% $agent->invoice_template || '(Default)' %>
+        </TD>
+
+        <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+          <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="<%$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 %>
+                  <FONT SIZE=-1><A HREF="<%$p%>config/config-delete.cgi?<% $override->confnum %>">(delete)</A></FONT>
+                </TD>
+              </TR>
+% } 
+
+            <TR>
+              <TD><FONT SIZE=-1><A HREF="<%$p%>config/config-view.cgi?agentnum=<% $agent->agentnum %>">(add override)</A></FONT></TD>
+            </TR>
+          </TABLE>
+        </TD>
+
+      </TR>
+% } 
+
+
+    </TABLE>
+  </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 (executable)
index 0000000..d64ff18
--- /dev/null
@@ -0,0 +1,61 @@
+<% 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->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/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi
new file mode 100755 (executable)
index 0000000..12bdeb3
--- /dev/null
@@ -0,0 +1,288 @@
+<% include( 'elements/browse.html',
+     'title'          => "Tax Rates $title",
+     'name_singular'  => 'tax rate',
+     'menubar'        => \@menubar,
+     'html_init'      => $html_init,
+     'html_posttotal' => $html_posttotal,
+     '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,
+  )
+%>
+%
+% #         <FONT SIZE=-1><A HREF="<% $p %>edit/process/cust_main_county-collapse.cgi?<% $hashref->{taxnum} %>">collapse state</A></FONT>
+% # % } 
+%
+<%once>
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my @manual_countries = ( 'US', 'CA', 'AU', 'NZ', 'GB' ); #some manual ordering
+my @all_countries = ( @manual_countries, 
+                      grep { my $c = $_; ! grep { $c eq $_ } @manual_countries }
+                      map { $_->country } 
+                          qsearch({
+                                    'select'    => 'country',
+                                    'table'     => 'cust_main_county',
+                                    'hashref'   => {},
+                                    'extra_sql' => 'GROUP BY country',
+                                 })
+                    );
+
+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 $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 $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;
+  my $color = '#333399';
+  qq!overlib( OLiframeContent('${p}edit/cust_main_county.html?$taxnum', 540, 420, 'edit_cust_main_county_popup' ), CAPTION, 'Edit tax rate', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '$color', CGCOLOR, '$color' ); return false;!;
+};
+
+sub expand_link {
+  my( $row, $desc ) = @_;
+  my $taxnum = $row->taxnum;
+  my $url = "${p}edit/cust_main_county-expand.cgi?$taxnum";
+  my $color = '#333399';
+
+  qq!<FONT SIZE="-1"><A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('$url', 540, 420, 'edit_cust_main_county_popup' ), CAPTION, '$desc', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '$color', CGCOLOR, '$color' ); return false;">!;
+}
+
+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">!;
+}
+
+</%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 =
+  "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.";
+$html_init .= "<BR>Click on <u>separate taxclasses</u> to specify taxes per taxclass."
+  if $enable_taxclasses;
+$html_init .= '<BR><BR>';
+
+$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>
+);
+
+my $title = '';
+
+my $country = '';
+if ( $cgi->param('country') =~ /^(\w\w)$/ ) {
+  $country = $1;
+  $title = $country;
+}
+$cgi->delete('country');
+
+my $state = '';
+if ( $cgi->param('state') =~ /^([\w \-\'\[\]]+)$/ ) {
+  $state = $1;
+  $title = "$state, $title";
+}
+$cgi->delete('state');
+
+my $county = '';
+if ( $cgi->param('county') =~ /^([\w \-\'\[\]]+)$/ ) {
+  $county = $1;
+  $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 $country_filter_change =
+  "window.location = '".
+  $cgi->self_url. ";country=' + this.options[this.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 =
+  '(show country: '.
+  qq(<SELECT NAME="country" onChange="$country_filter_change">).
+  qq(<OPTION VALUE="">(all)\n).
+  join("\n", map qq[<OPTION VALUE="$_"].
+                   ( $_ eq $country ? 'SELECTED' : '' ).
+                   '>'. code2country($_). " ($_)",
+                 @all_countries
+      ).
+  '</SELECT>)';
+
+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 ) {
+  $hashref->{'country'} = $country;
+  $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',);
+my @header2       = ( '', '', '', );
+my @links         = ( '', '', '', );
+my @link_onclicks = ( '', '', '', );
+my $align = 'lll';
+
+my @fields = (
+  sub { my $country = shift->country;
+        code2country($country). " ($country)";
+      },
+  sub { state_label($_[0]->state, $_[0]->country).
+        ( $_[0]->state
+            ? ''
+            : '&nbsp'. expand_link($_[0], 'Add States').
+                       'add&nbsp;states</A></FONT>'
+        )
+      },
+  sub { $_[0]->county || '(all)&nbsp'.
+                         expand_link($_[0], 'Add Counties').
+                         'add&nbsp;counties</A></FONT>'
+      },
+);
+
+my @color = (
+  '000000',
+  sub { shift->state  ? '000000' : '999999' },
+  sub { shift->county ? '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, 'Tax name',
+              'Rate', #'Tax',
+              'Exemptions',
+              ;
+
+push @header2, '(printed on invoices)',
+               '',
+               '',
+               ;
+
+push @fields, 
+  sub { shift->taxname || 'Tax' },
+  sub { shift->tax. '%&nbsp;<FONT SIZE="-1">(edit)</FONT>' },
+  $exempt_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,         '', $edit_link,    '';
+push @link_onclicks, '', $edit_onclick, '';
+
+</%init>
diff --git a/httemplate/browse/elements/browse.html b/httemplate/browse/elements/browse.html
new file mode 100644 (file)
index 0000000..513c2c4
--- /dev/null
@@ -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 (file)
index 0000000..8ce131a
--- /dev/null
@@ -0,0 +1,93 @@
+<% include( 'elements/browse.html',
+                 'title'       => 'Inventory Classes',
+                 'name'        => 'inventory classes',
+                 'menubar'     => [ 'Add a new inventory class' =>
+                                      $p.'edit/inventory_class.html',
+                                  ],
+                 'query'       => { 'table' => 'inventory_class', },
+                 'count_query' => 'SELECT COUNT(*) FROM inventory_class',
+                 'header'      => [ '#', 'Inventory class', 'Inventory' ],
+                 'fields'      => [ 'classnum',
+                                    'classname',
+                                    sub {
+                                          #my $inventory_class = shift;
+                                          my $i_c = shift;
+
+                                          my $link =
+                                            $p. 'search/inventory_item.html?'.
+                                            'classnum='. $i_c->classnum;
+
+                                          my %actioncol = ();
+                                          foreach ( keys %inv_action_link ) {
+                                            my($label, $baseurl, $method) =
+                                              @{ $inv_action_link{$_} };
+                                            my $url = $baseurl. $i_c->$method();
+                                            $actioncol{$_} =
+                                              '<FONT SIZE="-1">'.
+                                              '('.
+                                              '<A HREF="'.$url.'">'.
+                                              $label.
+                                              '</A>'.
+                                              ')'.
+                                              '</FONT>';
+                                          }
+
+                                          my %num = map { 
+                                            $_ => $i_c->$_();
+                                          } keys %labels;
+
+                                          [ map {
+                                                  [
+                                                    {
+                                                      'data'  => '<B>'. $num{$_}. '</B>',
+                                                      'align' => 'right',
+                                                    },
+                                                    {
+                                                      'data'  => $labels{$_},
+                                                      'align' => 'left',
+                                                      'link'  => ( $num{$_}
+                                                                     ? $link.$link{$_}
+                                                                     : ''
+                                                                 ),
+                                                    },
+                                                    { 'data'  => $actioncol{$_},
+                                                      'align'  => 'left',
+                                                    },
+                                                  ]
+                                                } keys %labels
+                                          ];
+                                        },
+                                  ],
+                 'links'       => [ $link,
+                                    $link,
+                                    '',
+                                  ],
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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'
+                 ],
+);
+
+my $link = [ "${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 (file)
index 0000000..0bbfb24
--- /dev/null
@@ -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 (executable)
index 0000000..2c916dc
--- /dev/null
@@ -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 (executable)
index 0000000..b5e0ef8
--- /dev/null
@@ -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 (executable)
index 0000000..11bc14e
--- /dev/null
@@ -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_event.html b/httemplate/browse/part_event.html
new file mode 100644 (file)
index 0000000..4a05826
--- /dev/null
@@ -0,0 +1,157 @@
+<% 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!<A HREF="${p}edit/part_event.html"><I>Add a new event</I></A><BR><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 (executable)
index 0000000..1cd2013
--- /dev/null
@@ -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 (executable)
index 0000000..78cb77d
--- /dev/null
@@ -0,0 +1,231 @@
+<% include( 'elements/browse.html',
+                 'title'              => 'Package Definitions',
+                 'html_init'          => $html_init,
+                 'name'               => 'package definitions',
+                 'disableable'        => 1,
+                 'disabled_statuspos' => 3,
+                 'agent_virt'         => 1,
+                 'agent_null_right'   => 'Edit global package definitions',
+                 'agent_pos'          => 4,
+                 'query'              => { 'select'   => $select,
+                                           'table'    => 'part_pkg',
+                                           'hashref'  => {},
+                                           'order_by' => "ORDER BY $orderby",
+                                         },
+                 'count_query'        => $count_query,
+                 'header'             => \@header,
+                 'fields'             => \@fields,
+                 'links'              => \@links,
+                 'align'              => $align,
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Edit package definitions')
+      || $FS::CurrentUser::CurrentUser->access_right('Edit global package definitions');
+
+my $select = '*';
+my $orderby = 'pkgpart';
+if ( $cgi->param('active') ) {
+
+  $orderby = 'num_active DESC';
+}
+  $select = "
+
+    *,
+
+    ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart
+       AND ( cancel IS NULL OR cancel = 0 )
+       AND ( susp IS NULL OR susp = 0 )
+    ) AS num_active,
+
+    ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart
+        AND ( cancel IS NULL OR cancel = 0 )
+        AND susp IS NOT NULL AND susp != 0
+    ) AS num_suspended,
+
+    ( SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.pkgpart = part_pkg.pkgpart
+        AND cancel IS NOT NULL AND cancel != 0
+    ) AS num_cancelled
+
+  ";
+
+#}
+
+my $conf = new FS::Conf;
+my $taxclasses = $conf->exists('enable_taxclasses');
+
+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>
+    <A HREF="${p}edit/part_pkg.cgi"><I>Add a new package definition</I></A>
+    <BR><BR>
+  !;
+#}
+
+# ------
+
+my $link = [ $p.'edit/part_pkg.cgi?', 'pkgpart' ];
+
+my @header = ( '#', 'Package', 'Comment' );
+my @fields = ( 'pkgpart', 'pkg', 'comment' );
+my $align = 'rll';
+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 ( $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';
+#}
+
+push @header, 'Frequency';
+push @fields, sub { shift->freq_pretty; };
+$align .= 'l';
+
+if ( $taxclasses ) {
+  push @header, 'Taxclass';
+  push @fields, sub { shift->taxclass() || '&nbsp;'; };
+  $align .= 'l';
+}
+
+push @header, 'Plan',
+              'Data',
+              'Services';
+              #'Service', 'Quan', 'Primary';
+
+push @fields, sub { shift->plan || '(legacy)' }, 
+
+              sub {
+                    my $part_pkg = shift;
+                    if ( $part_pkg->plan ) {
+
+                      [ map { 
+                              /^(\w+)=(.*)$/; #or something;
+                              [
+                                { 'data'  => $1,
+                                  'align' => 'right',
+                                },
+                                { 'data'  => $part_pkg->format($1,$2),
+                                  'align' => 'left',
+                                },
+                              ];
+                            }
+                        split(/\n/, $part_pkg->plandata)
+                      ];
+
+                    } 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'  => $p. 'edit/part_svc.cgi?'.
+                                            $part_svc->svcpart,
+                               },
+                             ];
+                           }
+                      sort {     $b->primary_svc =~ /^Y/i
+                             <=> $a->primary_svc =~ /^Y/i
+                           }
+                           $part_pkg->pkg_svc
+
+                    ];
+
+                  };
+
+$align .= 'lrl'; #rr';
+
+# --------
+
+my $count_query = 'SELECT COUNT(*) FROM part_pkg WHERE '.
+                    $FS::CurrentUser::CurrentUser->agentnums_sql(
+                      'null_right' => 'Edit global package definitions',
+                    );
+
+</%init>
diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html
new file mode 100755 (executable)
index 0000000..9cc32c4
--- /dev/null
@@ -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 (executable)
index 0000000..f1b2836
--- /dev/null
@@ -0,0 +1,215 @@
+<% 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 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 { $svc_x->pvf($_)
+%           or $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag }
+%            @dfields ;
+%     my $rowspan = scalar(@fields) || 1;
+%     my $url = "${p}edit/part_svc.cgi?". $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..3 ) {  
+         <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"</TD>
+%       }
+%     }
+%   
+%     my($n1)='';
+%     foreach my $field ( @fields ) {
+%       my $formatter =
+%            FS::part_svc->svc_table_fields($svcdb)->{$field}->{format}
+%            || sub { shift };
+%       my $flag = $part_svc->part_svc_column($field)->columnflag;
+%
+
+     <% $n1 %>
+     <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $field %></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 (file)
index 0000000..b184400
--- /dev/null
@@ -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 (file)
index 0000000..848c58a
--- /dev/null
@@ -0,0 +1,94 @@
+<% 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'             => [ '#',
+                                          'Gateway',
+                                          'Username',
+                                          'Password',
+                                          'Action',
+                                          'Options',
+                                        ],
+                'fields'             => [ 'gatewaynum',
+                                          $gateway_sub,
+                                          'gateway_username',
+                                          sub { ' - '; },
+                                          'gateway_action',
+                                          $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_class.html b/httemplate/browse/pkg_class.html
new file mode 100644 (file)
index 0000000..4f02ca2
--- /dev/null
@@ -0,0 +1,31 @@
+<% 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'      => [ '#', 'Class', ],
+                 'fields'      => [ 'classnum', 'classname' ],
+                 'links'       => [ $link, $link ],
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init = 
+  'Package classes define groups of packages, for reporting and '.
+  'convenience purposes.<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' ];
+
+</%init>
diff --git a/httemplate/browse/rate.cgi b/httemplate/browse/rate.cgi
new file mode 100644 (file)
index 0000000..b20c45c
--- /dev/null
@@ -0,0 +1,67 @@
+<% 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 $sth = dbh->prepare("SELECT DISTINCT(countrycode) FROM rate_prefix")
+  or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my @all_countrycodes = map $_->[0], @{ $sth->fetchall_arrayref };
+my $all_countrycodes = join("\n", map qq(<OPTION VALUE="$_">$_),
+                                      @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>
+    );
+
+
+};
+
+</%once>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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>!.
+  '<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' ];
+
+</%init>
diff --git a/httemplate/browse/rate_detail.html b/httemplate/browse/rate_detail.html
new file mode 100644 (file)
index 0000000..5dde85f
--- /dev/null
@@ -0,0 +1,95 @@
+<% 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',
+                           'Charge per<BR>minute',
+                           'Granularity',
+                         ],
+     'fields'         => [
+                           'regionname',
+                           sub { shift->dest_region->prefixes_short },
+                           sub { shift->min_included.
+                                 '&nbsp;<FONT SIZE="-1">(edit)</FONT>';
+                               },
+                           sub { $money_char. shift->min_charge.
+                                 '&nbsp;<FONT SIZE="-1">(edit)</FONT>';
+                               },
+                           sub { $granularity{ shift->sec_granularity } },
+                         ],
+     'links'          => [ '', '', $edit_link,    $edit_link,    '' ],
+     'link_onclicks'  => [ '', '', $edit_onclick, $edit_onclick, '' ],
+     'align'          => 'llrrc',
+   )
+%>
+<%once>
+
+my %granularity = (
+  '1', => '1 second',
+  '6'  => '6 second',
+  '30' => '30 second', # '1/2 minute',
+  '60' => 'minute',
+);
+
+my $conf = new FS::Conf;
+my $money_char = $conf->config('money_char') || '$';
+
+my $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>
+);
+
+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;
+  my $color = '#333399';
+  qq!overlib( OLiframeContent('${p}edit/rate_detail.html?$ratedetailnum', 540, 420, 'edit_rate_detail_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');
+
+$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 (file)
index 0000000..1d04b64
--- /dev/null
@@ -0,0 +1,53 @@
+<% 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,
+     'query'          => {
+                           'select'    => $select,
+                           'table'     => 'rate_region',
+                           'addl_from' => $join,
+                           'extra_sql' => $extra_sql, 
+                           'order_by'  => 'ORDER BY LOWER(regionname)',
+                         },
+     'count_query'    => 'SELECT COUNT(*) FROM rate_region',
+     'header'         => [ '#',         'Region',  'Country code', 'Prefixes' ],
+     'fields'         => [ 'regionnum', 'regionname',   'ccode',   'prefixes' ],
+     'links'          => [ $link, $link, $link, $link ],
+   )
+%>
+<%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 $extra_sql = '';
+if ( driver_name =~ /^Pg/ ) {
+  my $fromwhere = 'FROM rate_prefix'.
+                  ' WHERE rate_prefix.regionnum = rate_region.regionnum';
+  $select .= "( SELECT countrycode $fromwhere LIMIT 1 ) AS ccode, 
+              ARRAY_TO_STRING( ARRAY(SELECT npa $fromwhere), ',' ) 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     ) AS prefixes ";
+  $extra_sql = 'GROUP BY regionnum, regionname';
+} else {
+  die 'unknown database '. driver_name;
+}
+
+</%once>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/browse/reason.html b/httemplate/browse/reason.html
new file mode 100644 (file)
index 0000000..fe285be
--- /dev/null
@@ -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 (file)
index 0000000..6b444ba
--- /dev/null
@@ -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 (file)
index 0000000..acddf56
--- /dev/null
@@ -0,0 +1,59 @@
+<% include('/elements/header.html', 'Routers') %>
+
+<% include('/elements/error.html') %>
+
+%my $hidecustomerrouters = 0;
+%my $hideurl = '';
+%if ($cgi->param('hidecustomerrouters') eq '1') {
+%  $hidecustomerrouters = 1;
+%  $cgi->param('hidecustomerrouters', 0);
+%  $hideurl = '<A HREF="' . $cgi->self_url() . '">Show customer routers</A>';
+%} else {
+%  $hidecustomerrouters = 0;
+%  $cgi->param('hidecustomerrouters', 1);
+%  $hideurl = '<A HREF="' . $cgi->self_url() . '">Hide customer routers</A>';
+%}
+
+<A HREF="<%$p2%>edit/router.cgi">Add a new router</A>&nbsp;|&nbsp;<%$hideurl%>
+
+<%table()%>
+  <TR>
+    <TD><B>Router name</B></TD>
+    <TD><B>Address block(s)</B></TD>
+  </TR>
+% foreach my $router (sort {$a->routernum <=> $b->routernum} @router) {
+%     next if $hidecustomerrouters && $router->svcnum;
+%     my @addr_block = $router->addr_block;
+%     if (scalar(@addr_block) == 0) {
+%       push @addr_block, '&nbsp;';
+%     }
+%
+
+  <TR>
+    <TD ROWSPAN="<%scalar(@addr_block)+1%>">
+      <A HREF="<%$p2%>edit/router.cgi?<%$router->routernum%>"><%$router->routername%></A>
+    </TD>
+  </TR>
+% foreach my $block ( @addr_block ) { 
+
+  <TR>
+    <TD><%UNIVERSAL::isa($block, 'FS::addr_block') ? $block->NetAddr : '&nbsp;'%></TD>
+  </TR>
+% } 
+
+  </TR>
+% } 
+
+</TABLE>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @router = qsearch('router', {});
+my $p2 = popurl(2);
+
+</%init>
diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi
new file mode 100755 (executable)
index 0000000..44bc651
--- /dev/null
@@ -0,0 +1,74 @@
+<% 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',
+          )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('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/config/config-delete.cgi b/httemplate/config/config-delete.cgi
new file mode 100644 (file)
index 0000000..cdac434
--- /dev/null
@@ -0,0 +1,15 @@
+<%init>
+die "access denied\n"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+die "No configuration item specified (bad URL)!" unless $cgi->keywords;
+my ($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $confnum = $1;
+
+my $conf = qsearchs('conf', {'confnum' => $confnum});
+die "Configuration not found!" unless $conf;
+$conf->delete;
+
+</%init>
+<% $cgi->redirect(popurl(2) . "browse/agent.cgi") %>
diff --git a/httemplate/config/config-download.cgi b/httemplate/config/config-download.cgi
new file mode 100644 (file)
index 0000000..6979246
--- /dev/null
@@ -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-process.cgi b/httemplate/config/config-process.cgi
new file mode 100644 (file)
index 0000000..b0c8a74
--- /dev/null
@@ -0,0 +1,68 @@
+<%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' ) {
+    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 eq 'text' || $type eq 'select' || $type eq 'select-sub' )  {
+    if ( $cgi->param($i->key.$n) ne '' ) {
+      $conf->set($i->key, $cgi->param($i->key.$n), $agentnum);
+    } else {
+      $conf->delete($i->key, $agentnum);
+    }
+  } elsif ( $type eq 'editlist' || $type eq 'selectmultiple' )  {
+    if ( scalar(@{[ $cgi->param($i->key.$n) ]}) ) {
+      $conf->set($i->key, join("\n", @{[ $cgi->param($i->key.$n) ]} ), $agentnum);
+    } else {
+      $conf->delete($i->key, $agentnum);
+    }
+  }
+  $n++;
+}
+# warn @touch;
+$conf->touch($_, $agentnum) foreach @touch;
+$conf->delete($_, $agentnum) foreach @delete;
+
+</%init>
+<% header('Configuration set') %>
+  <SCRIPT TYPE="text/javascript">
+    window.top.location.reload();
+  </SCRIPT>
+  </BODY></HTML>
diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi
new file mode 100644 (file)
index 0000000..f5cead5
--- /dev/null
@@ -0,0 +1,159 @@
+<% include("/elements/header.html",
+     $title,
+     menubar(
+       'View all agents' => $p.'browse/agent.cgi',
+     )
+   )
+%>
+
+Click on a configuration value to change it.
+<BR><BR>
+
+<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>
+
+% if ($FS::UID::use_confcompat) {
+
+  <FONT SIZE="+1" COLOR="#ff0000">CONFIGURATION NOT STORED IN DATABASE -- USING COMPATIBILITY MODE</FONT><BR><BR>
+%}
+%
+% foreach my $section ( qw(required billing username password UI session
+%                            shell BIND
+%                           ),
+%                         '', 'deprecated') { 
+
+  <A NAME="<% $section || 'unclassified' %>"></A>
+  <FONT SIZE="-2">
+% foreach my $nav_section ( qw(required billing username password UI session
+%                                  shell BIND
+%                                 ),
+%                               '', 'deprecated') { 
+% if ( $section eq $nav_section ) { 
+
+      [<A NAME="not<% $nav_section || 'unclassified' %>" style="background-color: #cccccc"><% ucfirst($nav_section || 'unclassified') %></A>]
+% } else { 
+
+      [<A HREF="#<% $nav_section || 'unclassified' %>"><% ucfirst($nav_section || 'unclassified') %></A>]
+% } 
+% } 
+
+  </FONT><BR>
+  <TABLE BGCOLOR="#cccccc" BORDER=1 CELLSPACING=0 CELLPADDING=0 BORDERCOLOR="#999999">
+  <tr>
+    <th colspan="2" bgcolor="#dcdcdc">
+      <% ucfirst($section || 'unclassified') %> configuration options
+    </th>
+  </tr>
+% foreach my $i (grep $_->section eq $section, @config_items) { 
+%   my @types = ref($i->type) ? @{$i->type} : ($i->type);
+%   my( $width, $height ) = ( 522, 336 );
+%   if ( grep $_ eq 'textarea', @types ) {
+%     #800x600
+%     $width = 763;
+%     $height = 408;
+%     #1024x768
+%     #$width =
+%     #$height = 
+%   }
+
+    <tr>
+      <td><a href="javascript:void(0);" onClick="overlib( OLiframeContent('config.cgi?key=<% $i->key %>;agentnum=<% $agentnum %>', <% $width %>, <% $height %>, 'config_popup' ), CAPTION, 'Enter configuration value', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;" name="<% $i->key %>">
+%#        <b><% $i->key %></b></a>&nbsp;-&nbsp;<% $i->description %>
+        <b><% $i->key %></b></a>: <% $i->description %>
+      </td>
+      <td><table border=0>
+% foreach my $type (@types) {
+%             my $n = 0; 
+% if ( $type eq '' ) { 
+
+            <tr>
+              <td><font color="#ff0000">no type</font></td>
+            </tr>
+% } elsif (   $type eq 'binary' ) {
+
+            <tr>
+              <% $conf->exists($i->key, $agentnum)
+                   ? qq!<a href="config-download.cgi?key=!. $i->key. ';agentnum='. $agentnum. qq!">download</a>!
+                   : 'empty'
+              %>
+            </tr>
+% } elsif (   $type eq 'textarea'
+%                      || $type eq 'editlist'
+%                      || $type eq 'selectmultiple' ) { 
+
+            <tr>
+              <td 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 bgcolor="#<% $conf->exists($i->key, $agentnum) ? '00ff00">YES' : 'ff0000">NO' %></td>
+            </tr>
+% } elsif ( $type eq 'text' || $type eq 'select' )  { 
+
+            <tr>
+              <td bgcolor="#ffffff">
+                <% $conf->exists($i->key, $agentnum) ? $conf->config($i->key, $agentnum) : '' %>
+              </td></tr>
+% } elsif ( $type eq 'select-sub' ) { 
+
+            <tr>
+              <td bgcolor="#ffffff">
+                <% $conf->config($i->key, $agentnum) %>: 
+                <% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) %>
+              </td>
+            </tr>
+% } else { 
+
+            <tr><td>
+              <font color="#ff0000">unknown type <% $type %></font>
+            </td></tr>
+% } 
+% $n++; } 
+
+      </table></td>
+    </tr>
+% } 
+
+  </table><br><br>
+% } 
+
+
+</body></html>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agentnum = '';
+if ($cgi->param('agentnum') =~ /^(\d+)$/) {
+  $agentnum = $1;
+}
+
+my $title;
+if ($agentnum) {
+  my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+  die "Agent $agentnum not found!" unless $agent;
+
+  $title = "Configuration for ". $agent->agent;
+} else {
+  $title = 'Global Configuration';
+}
+
+my $conf = new FS::Conf;
+my @config_items = grep { $agentnum ? $_->per_agent : 1 }
+                   grep { $_->key != ~/^invoice_(html|latex|template)/ }
+                        $conf->config_items; 
+</%init>
diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi
new file mode 100644 (file)
index 0000000..d58c2f8
--- /dev/null
@@ -0,0 +1,319 @@
+<% 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 'binary' ) { 
+
+  Filename <input type="file" name="<% "$key$n" %>">
+
+%   } elsif ( $type eq 'textarea' ) { 
+
+  <textarea name="<% "$key$n" %>" rows=18 cols=88 wrap="off"><% join("\n", $conf->config($key, $agentnum)) %></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) : '' %>">
+
+%   } 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>
+
+%   } 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 = grep { $_->key != ~/^invoice_(html|latex|template)/ }
+                        $conf->config_items; 
+my %confitems = map { $_->key => $_ } @config_items;
+
+</%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/ach.html b/httemplate/docs/ach.html
new file mode 100644 (file)
index 0000000..b8a17c8
--- /dev/null
@@ -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 (executable)
index 0000000..2aa9348
--- /dev/null
@@ -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/cvv2.html b/httemplate/docs/cvv2.html
new file mode 100644 (file)
index 0000000..7670985
--- /dev/null
@@ -0,0 +1,24 @@
+<HTML>
+  <HEAD>
+    <TITLE>
+      CVV2 information
+    </TITLE>
+  </HEAD>
+  <BODY BGCOLOR="#e8e8e8">
+  The CVV2 number (also called CVC2 or CID) is a three- or four-digit
+  security code used to reduce credit card fraud.<BR><BR>
+  <TABLE BORDER=0 CELLSPACING=4>
+    <TR>
+      <TH>Visa / MasterCard / Discover</TH>
+      <TH>American Express</TH>
+    </TR>
+    <TR>
+      <TD>
+        <IMG BORDER=0 ALT="Visa/MasterCard/Discover" SRC="../images/cvv2.png">
+      </TD>
+      <TD>
+        <IMG BORDER=0 ALT="American Express" SRC="../images/cvv2_amex.png">
+      </TD>
+  </TABLE>
+  </BODY>
+</HTML>
diff --git a/httemplate/docs/ieak.html b/httemplate/docs/ieak.html
new file mode 100644 (file)
index 0000000..00c5342
--- /dev/null
@@ -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 (file)
index 0000000..3b419de
--- /dev/null
@@ -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 (executable)
index 0000000..94efe53
--- /dev/null
@@ -0,0 +1,39 @@
+<head>
+  <title>Importing legacy data</title>
+</head>
+<body>
+  <h1>Importing legacy data</h1>
+<font size="+2">In almost all cases, legacy data import will require writing custom code to deal with your particular legacy data.  The example scripts here will probably <b>not</b> work "out-of-the-box", and are provided <b>as a starting point only</b>.</font>
+<br><br><i>Some import scripts may require installation of the <a href="http://search.cpan.org/search?dist=Array-PrintCols">Array-PrintCols</a> and <a href="http://search.cpan.org/search?dist=Term-Query">Term-Query</a> (make test broken; install manually) modules.</i><br>
+<ul>
+  <li><a name="bind">bin/bind.import</a> - Import domain information from BIND named
+  <li><a name="passwd">bin/passwd.import</a> - Just import `passwd' and `shadow' or `master.passwd', no RADIUS import.
+  <li><a name="svc_acct">bin/svc_acct.import</a> - Import `passwd', ( `shadow' or `master.passwd' ) and RADIUS `users'.  Before running bin/svc_acct.import, you need <a href="../browse/part_svc.cgi">services</a> (with table svc_acct) as follows:
+    <ul>
+      <li>Most accounts probably have entries in passwd and users (with Port-Limit nonexistant or 1)
+      <li>Some accounts have entries in passwd and users, but with Port-Limit 2 (or more)
+      <li>Some accounts might have entries in users only (Port-Limit 1)
+      <li>Some accounts might have entries in users only (Port-Limit >= 2)
+      <li>POP mail accounts have entries in passwd only, and have a particular shell.
+      <li>Everything else in passwd is a shell account.
+    </ul>
+<!--  <li><a name="svc_acct_sm">bin/svc_acct_sm.import</a> - Import qmail ( `virtualdomains' and `rcpthosts' ), or sendmail ( `virtusertable' and `sendmail.cw' ) files.  Before running bin/svc_acct_sm.import, you need <a href="../browse/part_svc.cgi">services</a> as follows:
+    <ul>
+      <li>Domain (table svc_acct)
+      <li>Mail alias (table svc_acct_sm)
+    </ul>
+-->
+  <li><a name="cust_main">Importing customer data</a>
+    <ul>
+      <li>Manually
+        <ul>
+          <li>Add a <a href="../edit/cust_main.cgi">new customer</a>
+          <li>Add one or more packages for this customer
+          <li>Enter a package by clicking on the package number
+          <li>Pick the `Link to existing' option
+        </ul>
+      <li>Batch - You will need to write a script to import your particular legacy data.  You can use eg/TEMPLATE_cust_main.import as a starting point.
+    </ul>
+</ul>
+</body>
+
diff --git a/httemplate/docs/man/FS/part_export/.cvs_is_on_crack b/httemplate/docs/man/FS/part_export/.cvs_is_on_crack
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/httemplate/docs/overview-new.dia b/httemplate/docs/overview-new.dia
new file mode 100644 (file)
index 0000000..d9989a3
Binary files /dev/null and b/httemplate/docs/overview-new.dia differ
diff --git a/httemplate/docs/overview-new.png b/httemplate/docs/overview-new.png
new file mode 100644 (file)
index 0000000..bf81546
Binary files /dev/null and b/httemplate/docs/overview-new.png differ
diff --git a/httemplate/docs/overview.dia b/httemplate/docs/overview.dia
new file mode 100644 (file)
index 0000000..a0e34c3
Binary files /dev/null and b/httemplate/docs/overview.dia differ
diff --git a/httemplate/docs/overview.png b/httemplate/docs/overview.png
new file mode 100644 (file)
index 0000000..bf2dbc2
Binary files /dev/null and b/httemplate/docs/overview.png differ
diff --git a/httemplate/docs/passwd.html b/httemplate/docs/passwd.html
new file mode 100755 (executable)
index 0000000..fc1dde9
--- /dev/null
@@ -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 (file)
index 0000000..e00f59c
Binary files /dev/null and b/httemplate/docs/schema.dia differ
diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html
new file mode 100644 (file)
index 0000000..cd4914a
--- /dev/null
@@ -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 (file)
index 0000000..d0392e7
Binary files /dev/null and b/httemplate/docs/schema.png differ
diff --git a/httemplate/docs/session.html b/httemplate/docs/session.html
new file mode 100644 (file)
index 0000000..72e1642
--- /dev/null
@@ -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 (file)
index 0000000..97d7aa7
--- /dev/null
@@ -0,0 +1,54 @@
+<head>
+  <title>Signup server</title>
+</head>
+<body>
+  <h1>Signup server</h1>
+For security reasons, the signup server should run on an external public
+webserver.  On this machine, install:
+<ul>
+  <li>A web server, such as <a href="http://www.apache-ssl.org">Apache-SSL</a> or <a href="http://www.apache.org">Apache</a>
+  <li><a href="ftp://ftp.cs.hut.fi/pub/ssh/">SSH</a>
+  <li><a href="http://www.perl.com/CPAN/doc/relinfo/INSTALL.html">Perl</a> (at least 5.004_05 for the 5.004 series or 5.005_03 for the 5.005 series.  Don't enable experimental features like threads or the PerlIO abstraction layer.)
+  <li><a href="http://search.cpan.org/search?dist=Text-Template">Text::Template</a>
+  <li><a href="http://search.cpan.org/search?dist=Storable">Storable</a>
+  <li><a href="http://search.cpan.org/search?dist=Business-CreditCard">Business-CreditCard</a>
+  <li><a href="http://search.cpan.org/search?dist=HTTP-BrowserDetect">HTTP::BrowserDetect</a>
+
+  <li><a href="man/FS/SignupClient.html">FS::SignupClient</a> (copy the fs_signup/FS-SignupClient directory to the external machine, then: perl Makefile.PL; make; make install)
+</ul>
+Then:
+<ul>
+  <li>Add the user `freeside' to the the external machine.
+  <li>Copy or symlink fs_signup/FS-SignupClient/cgi/signup.cgi into the web server's document space.
+  <li>When linking to signup.cgi, you can include a referring custnum in the URL as follows: <code>http://public.web.server/path/signup.cgi?ref=1542</code>
+  <li>Enable CGI execution for files with the `.cgi' extension.  (with <a href="http://www.apache.org/docs/mod/mod_mime.html#addhandler">Apache</a>)
+  <li>Create the /usr/local/freeside directory on the external machine (owned by the freeside user).
+  <li>touch /usr/local/freeside/fs_signupd_socket; chown freeside /usr/local/freeside/fs_signupd_socket; chmod 600 /usr/local/freeside/fs_signupd_socket
+  <li>Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perl.com/CPAN-local/doc/manual/html/pod/perlsec.html#Security_Bugs">setuid</a> (see <a href="install.html">install.html</a> for details) to run signup.cgi as the freeside user.
+  <li>Append the identity.pub from the freeside user on your freeside machine to the authorized_keys file of the newly created freeside user on the external machine(s).
+  <li>Run <pre>fs_signup_server <i>user</i> <i>machine</i> <i>agentnum</i> <i>refnum</i></pre> on the Freeside machine.
+  <ul>
+    <li><i>user</i> is a user from the mapsecrets file.
+    <li><i>machine</i> is the name of the external machine.
+    <li><i>agentnum</i> and <i>refnum</i> are the <a href="schema.html#agent">agent</a> and <a href="schema.html#part_referral">referral</a>, respectively, to use for customers who sign up via this signup server.
+  </ul>
+</ul>
+Optional:
+<ul>
+  <li>If you create a <b>/usr/local/freeside/ieak.template</b> file on the external machine, it will be sent to IE users with MIME type <i>application/x-Internet-signup</i>.  This file will be processed with <a href="http://search.cpan.org/doc/MJD/Text-Template-1.23/Template.pm">Text::Template</a> with the variables listed below available.
+  (an example file is included as <b>fs_signup/ieak.template</b>)  See the section on <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/INS.HTM">internet settings files</a> in the <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/toc.asp">IEAK documentation</a> for more information.
+  <li>If you create a <b>/usr/local/freeside/success.html</b> file on the external machine, it will be used as the success HTML page.  Although template substiutions are available, a regular HTML file will work fine here, unlike signup.html.  An example file is included as <b>fs_signup/FS-SignupClient/cgi/success.html</b>
+  <li>Variable substitutions available in <b>ieak.template</b>, <b>cck.template</b> and <b>success.html</b>:
+    <ul>
+      <li>$ac - area code of selected POP
+      <li>$exch - exchange of selected POP
+      <li>$loc - local part of selected POP
+      <li>$username
+      <li>$password
+      <li>$email_name - first and last name
+      <li>$pkg - package name
+    </ul>
+  <li>If you create a <b>/usr/local/freeside/signup.html</b> file on the external machine, it will be used as a template for the form HTML.  This requires the template to be constructed appropriately; probably best to start with the example file included as <b>fs_signup/FS-SignupClient/cgi/signup.html</b>.
+  <li>If there are any entries in the <i>prepay_credit</i> table, a user can enter a string matching the <b>identifier</i> column to receive the credit specified in the <b>amount</b> column, and/or the time specified in the <b>seconds</b> column (for use with the <a href="session.html">session monitor</a>), after which that <b>identifier</b> is no longer valid.  This can be used to implement pre-paid "calling card" type signups.  The <i>bin/generate-prepay</i> script can be used to populate the <i>prepay_credit</i> table.
+</ul>
+</body>
diff --git a/httemplate/docs/ssh.html b/httemplate/docs/ssh.html
new file mode 100755 (executable)
index 0000000..d2c501e
--- /dev/null
@@ -0,0 +1,16 @@
+<head>
+  <title>Unattended SSH</title>
+</head>
+<body>
+  <h1>Unattended SSH</h1>
+  <br><a name=ssh>Unattended remote login</a> - Freeside can login to remote machines unattended using SSH.  This can pose a security risk if not configured correctly, and will allow an intruder who breaks into your freeside machine full access to your remote machines.  <b>Do not use this feature unless you understand what you are doing!</b>
+    <ul>
+      <li>As the freeside user (on your freeside machine), generate an authentication key using <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>.  Since this is for unattended operation, use a blank passphrase.
+      <li>Append the newly-created <code>identity.pub</code> file to <code>~root/.ssh/authorized_keys</code> (or the appopriate <code>~username/.ssh/authorized_keys</code>) on the remote machine(s).
+      <li>Some new SSH v2 implementation accept v2 style keys only.  Use the <code>-t</code> option to <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>, and append the created <code>id_dsa.pub</code> or <code>id_rsa.pub</code> to <code>~root/.ssh/authorized_keys2</code> (or the appopriate <code>~username/.ssh/authorized_keys</code>) on the remote machine(s).
+      <li>You may need to set <code>PermitRootLogin without-password</code> (meaning with keys only) in your <code>sshd_config</code> file on the remote machine(s).
+      <li>You may want to set <code>ForwardX11 = no</code> in <code>~root/.ssh/config</code> to prevent spurious errors if your distribution turns on X11 forwarding by default.
+    </ul>
+
+</body>
+
diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi
new file mode 100755 (executable)
index 0000000..fea8545
--- /dev/null
@@ -0,0 +1,181 @@
+<% 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"><% $cust_pkg->pkg %></TD>
+  </TR>
+
+  <TR>
+    <TD ALIGN="right">Comment</TD>
+    <TD BGCOLOR="#ffffff"><% $cust_pkg->comment %></TD>
+  </TR>
+
+  <TR>
+    <TD ALIGN="right">Order taker</TD>
+    <TD BGCOLOR="#ffffff"><% $cust_pkg->otaker %></TD>
+  </TR>
+
+  <& .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_edit, 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_edit, 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:   "%m/%d/%Y",
+      button:     "<% $column %>_button",
+      align:      "BR"
+    });
+  </SCRIPT>
+
+</%def>
+
+<%def .row_display>
+<%args>
+  $cust_pkg
+  $column
+  $label
+</%args>
+% if ( $cust_pkg->get($column) ) { 
+    <TR>
+      <TD ALIGN="right"><% $label %> date</TD>
+      <TD BGCOLOR="#ffffff"><% time2str($format,$cust_pkg->get($column)) %></TD>
+    </TR>
+% } 
+</%def>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Apply Changes">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+
+#my $format = "%c %z (%Z)";
+my $format = "%m/%d/%Y %T %z (%Z)";
+
+#false laziness w/view/cust_main/packages.html
+#my( $billed_or_prepaid,
+
+</%once>
+<%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') eq '_bill_areyousure' ) {
+    if ( $cgi->param('bill') =~ /^([\s\d\/\:\-\(\w\)]*)$/ ) {
+      my $bill = $1;
+      $cgi->param('error', '');
+      $error = "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">';
+    }
+  }
+
+  #get package record
+  $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+  die "No package!" unless $cust_pkg;
+
+  foreach my $col (qw( setup last_bill bill adjourn expire )) {
+    my $value = $cgi->param($col);
+    $cust_pkg->set( $col, $value ? str2time($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 (file)
index 0000000..4686a62
--- /dev/null
@@ -0,0 +1,80 @@
+<% include( 'elements/edit.html',
+              'name'   => 'Internal Access 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 (file)
index 0000000..224d8d7
--- /dev/null
@@ -0,0 +1,50 @@
+<% include( 'elements/edit.html',
+                 'name'   => 'Internal User',
+                 'table'  => 'access_user',
+                 'fields' => [
+                               'username',
+                               { field=>'_password', type=>'password' },
+                               { field=>'_password2', type=>'password' },
+                               'last',
+                               'first',
+                               { field=>'disabled', type=>'checkbox', value=>'Y' },
+                             ],
+                 'labels' => { 
+                               'usernum'   => 'User number',
+                               'username'  => 'Username',
+                               '_password' => 'Password',
+                               '_password2'=> 'Re-enter Password',
+                               'last'      => 'Last name',
+                               'first'     => 'First name',
+                               'disabled'  => 'Disable employee',
+                             },
+                 'edit_callback' => sub { my( $c, $o ) = @_; 
+                                          $o->set('_password', '');
+                                        },
+                 'viewall_dir' => 'browse',
+                 'html_bottom' =>
+                   sub {
+                     my $access_user = shift;
+
+                     '<BR>Internal Access 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>'
+                     ;
+                   },
+           )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi
new file mode 100755 (executable)
index 0000000..11bfc59
--- /dev/null
@@ -0,0 +1,98 @@
+<% include("/elements/header.html","$action Agent", menubar(
+  'View all agents' => $p. 'browse/agent.cgi',
+)) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/agent.cgi" METHOD=POST>
+<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>
+    <TD ALIGN="right">Disable</TD>
+    <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $agent->disabled eq 'Y' ? ' CHECKED' : '' %>></TD>
+  </TR>
+
+  <% include('/elements/tr-select-invoice_template.html',
+               'label'      => 'Invoice template',
+               'field'      => 'invoice_template',
+               'curr_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>
+% } 
+
+</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 (file)
index 0000000..4a7cedf
--- /dev/null
@@ -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 (executable)
index 0000000..abf4bf8
--- /dev/null
@@ -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. ' - '. $_[0]->comment; },
+              '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/bulk-cust_svc.html b/httemplate/edit/bulk-cust_svc.html
new file mode 100644 (file)
index 0000000..6f6e3f8
--- /dev/null
@@ -0,0 +1,97 @@
+<% include('/elements/header.html', 'Bulk customer service change') %>
+
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT>
+
+<% 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/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi
new file mode 100755 (executable)
index 0000000..b6a0647
--- /dev/null
@@ -0,0 +1,86 @@
+<% include('/elements/header-popup.html', 'Apply Payment') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_bill_pay.cgi" METHOD=POST>
+
+Payment #<B><% $paynum %></B>
+<INPUT TYPE="hidden" NAME="paynum" VALUE="<% $paynum %>">
+
+<BR>Date: <B><% time2str("%D", $cust_pay->_date) %></B>
+
+<BR>Amount: $<B><% $cust_pay->paid %></B>
+
+<BR>Unapplied amount: $<B><% $unapplied %></B>
+
+<SCRIPT TYPE="text/javascript">
+function changed(what) {
+  cust_bill = what.options[what.selectedIndex].value;
+
+% foreach my $cust_bill ( @cust_bill ) {
+
+    if ( cust_bill == <% $cust_bill->invnum %> ) {
+      what.form.amount.value = "<% min($cust_bill->owed, $unapplied) %>";
+    }
+
+% } 
+
+  if ( cust_bill == "Refund" ) {
+    what.form.amount.value = "<% $unapplied %>";
+  }
+}
+</SCRIPT>
+
+<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">
+<OPTION VALUE="">
+
+% foreach my $cust_bill ( @cust_bill ) { 
+  <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D", $cust_bill->_date) %> - $<% $cust_bill->owed %>
+% } 
+
+<OPTION VALUE="Refund">Refund
+</SELECT>
+
+<BR>Amount $<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8>
+
+<BR>
+<CENTER><INPUT TYPE="submit" VALUE="Apply"></CENTER>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+my($paynum, $amount, $invnum);
+if ( $cgi->param('error') ) {
+  $paynum = $cgi->param('paynum');
+  $amount = $cgi->param('amount');
+  $invnum = $cgi->param('invnum');
+} else {
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/;
+  $paynum = $1;
+  $amount = '';
+  $invnum = '';
+}
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } );
+die "payment $paynum not found!" unless $cust_pay;
+
+my $unapplied = $cust_pay->unapplied;
+
+my @cust_bill = sort {    $a->_date  <=> $b->_date
+                       or $a->invnum <=> $b->invnum
+                     }
+                grep { $_->owed != 0 }
+                qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } );
+
+</%init>
diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi
new file mode 100755 (executable)
index 0000000..36109cf
--- /dev/null
@@ -0,0 +1,84 @@
+<% 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 %>">
+<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 %>">
+
+Credit
+<% ntable("#cccccc", 2) %>
+
+  <TR>
+    <TD ALIGN="right">Date</TD>
+    <TD BGCOLOR="#ffffff"><% time2str("%D",$_date) %></TD>
+  </TR>
+
+  <TR>
+    <TD ALIGN="right">Amount</TD>
+    <TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8></TD>
+  </TR>
+
+%
+%#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!;
+%
+
+<% include('/elements/tr-select-reason.html',
+           'field'          => 'reasonnum',
+           'reason_class'   => 'R',
+           'control_button' => 'document.credit_popup.submit',
+          )
+%>
+
+  <TR>
+    <TD ALIGN="right">Auto-apply<BR>to invoices</TD>
+    <TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>
+  </TR>
+
+</TABLE>
+
+<BR>
+
+<CENTER><INPUT TYPE="submit" VALUE="Enter credit"></CENTER>
+
+</FORM>
+</BODY>
+</HTML>
+<%once>
+
+my $conf = new FS::Conf;
+
+</%once>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Post credit');
+
+my($custnum, $amount, $reason);
+if ( $cgi->param('error') ) {
+  #$cust_credit = new FS::cust_credit ( {
+  #  map { $_, scalar($cgi->param($_)) } fields('cust_credit')
+  #} );
+  $custnum = $cgi->param('custnum');
+  $amount = $cgi->param('amount');
+  #$refund = $cgi->param('refund');
+  $reason = $cgi->param('reason');
+} else {
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/;
+  $custnum = $1;
+  $amount = '';
+  #$refund = 'yes';
+  $reason = '';
+}
+my $_date = time;
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi
new file mode 100755 (executable)
index 0000000..59a74b2
--- /dev/null
@@ -0,0 +1,93 @@
+<% include('/elements/header-popup.html', 'Apply Credit') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_credit_bill.cgi" METHOD=POST>
+
+Credit #<B><% $crednum %></B>
+<INPUT TYPE="hidden" NAME="crednum" VALUE="<% $crednum %>">
+
+<BR>Date: <B><% time2str("%D", $cust_credit->_date) %></B>
+
+<BR>Amount: $<B><% $cust_credit->amount %></B>
+
+<BR>Unapplied amount: $<B><% $credited %></B>
+
+<BR>Reason: <B><% $cust_credit->reason %></B>
+
+<SCRIPT>
+function changed(what) {
+  cust_bill = what.options[what.selectedIndex].value;
+
+% foreach my $cust_bill ( @cust_bill ) {
+
+  if ( cust_bill == <% $cust_bill->invnum %> ) {
+    what.form.amount.value = "<% min($cust_bill->owed, $credited) %>";
+  }
+
+% } 
+
+  if ( cust_bill == "Refund" ) {
+    what.form.amount.value = "<% $credited %>";
+  }
+}
+</SCRIPT>
+
+<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">
+<OPTION VALUE="">
+
+% foreach my $cust_bill ( @cust_bill ) { 
+  <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D",$cust_bill->_date) %> - $<% $cust_bill->owed %>
+% } 
+
+<OPTION VALUE="Refund">Refund
+</SELECT>
+
+<BR>Amount $<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8>
+
+<BR>
+<CENTER><INPUT TYPE="submit" VALUE="Apply"></CENTER>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+my($crednum, $amount, $invnum);
+if ( $cgi->param('error') ) {
+  #$cust_credit_bill = new FS::cust_credit_bill ( {
+  #  map { $_, scalar($cgi->param($_)) } fields('cust_credit_bill')
+  #} );
+  $crednum = $cgi->param('crednum');
+  $amount = $cgi->param('amount');
+  #$refund = $cgi->param('refund');
+  $invnum = $cgi->param('invnum');
+} else {
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/;
+  $crednum = $1;
+  $amount = '';
+  #$refund = 'yes';
+  $invnum = '';
+}
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } );
+die "credit $crednum not found!" unless $cust_credit;
+
+my $credited = $cust_credit->credited;
+
+my @cust_bill = sort {    $a->_date  <=> $b->_date
+                       or $a->invnum <=> $b->invnum
+                     }
+                grep { $_->owed != 0 }
+                qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } );
+
+</%init>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
new file mode 100755 (executable)
index 0000000..aae8093
--- /dev/null
@@ -0,0 +1,543 @@
+<% include('/elements/header.html',
+      "Customer $action",
+      '',
+      ' onUnload="myclose()"'
+) %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="topform" STYLE="margin-bottom: 0">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+% if ( $custnum ) { 
+
+  Customer #<B><% $custnum %></B> - 
+  <B><FONT COLOR="#<% $cust_main->statuscolor %>">
+    <% ucfirst($cust_main->status) %>
+  </FONT></B>
+  <BR><BR>
+% } 
+
+
+<% &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 ),
+           )
+%>
+
+<!-- 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 } )
+%) {
+%
+
+
+  <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',
+                 )
+      %>
+    </TD>
+  </TR>
+% } else { 
+
+
+  <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="">
+% } 
+
+
+</TABLE>
+
+<!-- birthdate -->
+
+% if ( $conf->exists('cust_main-enable_birthdate') ) {
+
+  <BR>
+  <% 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>
+
+% }
+
+<!-- contact info -->
+
+%  my $same_checked = '';
+%  my $ship_disabled = '';
+%  unless ( $cust_main->ship_last && $same ne 'Y' ) {
+%    $same_checked = 'CHECKED';
+%    $ship_disabled = 'DISABLED 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><BR>
+Billing address
+<% 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_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 )) { 
+      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 (qw( last first company address1 address2 city county state zip country daytime night fax )) { 
+      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>
+Service address 
+(<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,
+          )
+%>
+
+
+<!-- billing info -->
+
+<% include( 'cust_main/billing.html', $cust_main,
+               'payinfo'        => $payinfo,
+               'invoicing_list' => \@invoicing_list,
+           )
+%>
+
+<SCRIPT>
+function bottomfixup(what) {
+
+  var topvars = new Array(
+    'birthdate',
+
+    'custnum', 'agentnum', 'refnum', 'referral_custnum',
+
+    'last', 'first', 'ss', 'company',
+    'address1', 'address2', 'city',
+    'county', 'state', 'zip', 'country',
+    'daytime', 'night', 'fax',
+    'stateid', 'stateid_state',
+
+    'same',
+
+    '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',
+
+    'select' // XXX key
+  );
+
+  var layervars = new Array(
+    'payauto',
+    'payinfo', 'payinfo1', 'payinfo2', 'paytype',
+    'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
+    'paystart_month', 'paystart_year', 'payissue',
+    'payip',
+    'paid'
+  );
+
+  var billing_bottomvars = new Array(
+    'tax',
+    'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX',
+    'invoice_terms',
+    'spool_cdr'
+  );
+
+  for ( f=0; f < topvars.length; f++ ) {
+    var field = topvars[f];
+    copyelement( document.topform.elements[field],
+                 document.bottomform.elements[field]
+               );
+  }
+
+  var layerform = document.topform.select.options[document.topform.select.selectedIndex].value;
+  for ( f=0; f < layervars.length; f++ ) {
+    var field = layervars[f];
+    copyelement( document.forms[layerform].elements[field],
+                 document.bottomform.elements[field]
+               );
+  }
+
+  for ( f=0; f < billing_bottomvars.length; f++ ) {
+    var field = billing_bottomvars[f];
+    copyelement( document.billing_bottomform.elements[field],
+                 document.bottomform.elements[field]
+               );
+  }
+
+}
+
+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);
+}
+
+</SCRIPT>
+
+<FORM ACTION="<% popurl(1) %>process/cust_main.cgi" METHOD=POST NAME="bottomform" onSubmit="document.bottomform.submit.disabled=true; bottomfixup(this.form);" STYLE="margin-top: 0; margin-bottom: 0">
+% foreach my $hidden (
+%     'birthdate',
+%
+%     'custnum', 'agentnum', 'refnum', 'referral_custnum',
+%     'last', 'first', 'ss', 'company',
+%     'address1', 'address2', 'city',
+%     'county', 'state', 'zip', 'country',
+%     'daytime', 'night', 'fax',
+%     'stateid', 'stateid_state',
+%     
+%     'same',
+%     
+%     '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',
+%     
+%     'select', #XXX key
+%
+%     'payauto',
+%     'payinfo', 'payinfo1', 'payinfo2', 'paytype',
+%     'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
+%     'paystart_month', 'paystart_year', 'payissue',
+%     'payip',
+%     'paid',
+%     
+%     'tax',
+%     'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX',
+%     'invoice_terms',
+%     'spool_cdr'
+%   ) {
+%
+
+  <INPUT TYPE="hidden" NAME="<% $hidden %>" VALUE="">
+% } 
+%
+% my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly';
+% if (!$ro_comments || $cust_main->comments) {
+
+<BR>Comments
+<% &ntable("#cccccc") %>
+  <TR>
+    <TD>
+      <TEXTAREA COLS=80 ROWS=5 WRAP="HARD" NAME="comments" <%$ro_comments%>><% $cust_main->comments %></TEXTAREA>
+    </TD>
+  </TR>
+</TABLE>
+%
+% }
+%
+%unless ( $custnum ) {
+%  # pry the wrong place for this logic.  also pretty expensive
+%  #use FS::part_pkg;
+%
+%  #false laziness, copied from FS::cust_pkg::order
+%  my $pkgpart;
+%  my @agents = $FS::CurrentUser::CurrentUser->agents;
+%  if ( scalar(@agents) == 1 ) {
+%    # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART
+%    $pkgpart = $agents[0]->pkgpart_hashref;
+%  } else {
+%    #can't know (agent not chosen), so, allow all
+%    my %typenum;
+%    foreach my $agent ( @agents ) {
+%      next if $typenum{$agent->typenum}++;
+%      #fixed in 5.004_05 #$pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref }
+%      foreach ( keys %{ $agent->pkgpart_hashref } ) { $pkgpart->{$_}++; } #5.004_04 workaround
+%    }
+%  }
+%  #eslaf
+%
+%  my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } }
+%    qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case?
+%
+%  if ( @part_pkg ) {
+%
+%    #    print "<BR><BR>First package", &itable("#cccccc", "0 ALIGN=LEFT"),
+%    #apiabuse & undesirable wrapping
+%
+%    
+
+    <BR>First package
+    <% ntable("#cccccc") %>
+    
+      <TR>
+        <TD COLSPAN=2>
+          <% include('cust_main/select-domain.html',
+                       'pkgparts'      => \@part_pkg,
+                       'saved_pkgpart' => $saved_pkgpart,
+                       'saved_domsvc' => $saved_domsvc,
+                    )
+          %>
+        </TD>
+      </TR>
+% 
+%        #false laziness: (mostly) copied from edit/svc_acct.cgi
+%        #$ulen = $svc_acct->dbdef_table->column('username')->length;
+%        my $ulen = dbdef->table('svc_acct')->column('username')->length;
+%        my $ulen2 = $ulen+2;
+%        my $passwordmax = $conf->config('passwordmax') || 8;
+%        my $pmax2 = $passwordmax + 2;
+%      
+
+    
+      <TR>
+        <TD ALIGN="right">Username</TD>
+        <TD>
+          <INPUT TYPE="text" NAME="username" VALUE="<% $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="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $passwordmax %>>
+          (blank to generate)
+        </TD>
+      </TR>
+    
+      <TR>
+        <TD ALIGN="right">Access number</TD>
+        <TD><% FS::svc_acct_pop::popselector($popnum) %></TD>
+      </TR>
+    </TABLE>
+% } 
+% } 
+
+
+<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $cust_main->otaker %>">
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="<% $custnum ?  "Apply Changes" : "Add Customer" %>">
+<BR>
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+#for misplaced logic below
+#use FS::part_pkg;
+
+#for false laziness below (now more properly lazy)
+#use FS::svc_acct_pop;
+
+#for (other) false laziness below
+#use FS::agent;
+#use FS::type_pkgs;
+
+my $conf = new FS::Conf;
+
+#get record
+
+my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart, $saved_domsvc);
+my(@invoicing_list);
+my ($ss,$stateid,$payinfo);
+my $same = '';
+if ( $cgi->param('error') ) {
+  $cust_main = new FS::cust_main ( {
+    map { $_, scalar($cgi->param($_)) } fields('cust_main')
+  } );
+  $custnum = $cust_main->custnum;
+  $saved_domsvc = $cgi->param('domsvc') || '';
+  if ( $saved_domsvc =~ /^(\d+)$/ ) {
+    $saved_domsvc = $1;
+  } else {
+    $saved_domsvc = '';
+  }
+  $saved_pkgpart = $cgi->param('pkgpart_svcpart') || '';
+  if ( $saved_pkgpart =~ /^(\d+)_/ ) {
+    $saved_pkgpart = $1;
+  } else {
+    $saved_pkgpart = '';
+  }
+  $username = $cgi->param('username');
+  $password = $cgi->param('_password');
+  $popnum = $cgi->param('popnum');
+  @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') );
+  $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
+} elsif ( $cgi->keywords ) { #editing
+  my( $query ) = $cgi->keywords;
+  $query =~ /^(\d+)$/;
+  $custnum=$1;
+  $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+  if ( $cust_main->dbdef_table->column('paycvv')
+       && length($cust_main->paycvv)             ) {
+    my $paycvv = $cust_main->paycvv;
+    $paycvv =~ s/./*/g;
+    $cust_main->paycvv($paycvv);
+  }
+  $saved_pkgpart = 0;
+  $saved_domsvc = 0;
+  $username = '';
+  $password = '';
+  $popnum = 0;
+  @invoicing_list = $cust_main->invoicing_list;
+  $ss = $cust_main->masked('ss');
+  $stateid = $cust_main->masked('stateid');
+  $payinfo = $cust_main->paymask;
+} else {
+  $custnum='';
+  $cust_main = new FS::cust_main ( {} );
+  $cust_main->otaker( &getotaker );
+  $cust_main->referral_custnum( $cgi->param('referral_custnum') );
+  $saved_pkgpart = 0;
+  $saved_domsvc = 0;
+  $username = '';
+  $password = '';
+  $popnum = 0;
+  @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 (file)
index 0000000..6ed35c1
--- /dev/null
@@ -0,0 +1,484 @@
+%if ( $payby_default eq 'HIDE' ) {
+%
+%  $cust_main->payby('BILL') unless $cust_main->payby;
+
+  <INPUT TYPE="hidden" NAME="select" VALUE="<% $cust_main->payby %>">
+
+  </FORM>
+
+  <FORM NAME="<% $cust_main->payby %>" STYLE="margin-top: 0; margin-bottom: 0"> 
+
+    <INPUT TYPE="hidden" NAME="payinfo" VALUE="<% $cust_main->paymask %>">
+
+% foreach my $field (qw( payname paycvv paystart_month paystart_year payissue payip paytype paystate )) { 
+
+    <INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $cust_main->getfield($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="exp_month" VALUE="<% $mon %>">
+  <INPUT TYPE="hidden" NAME="exp_year"  VALUE="<% $year %>">
+
+  </FORM>
+
+  <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0">
+
+  <INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax %>">
+
+  <INPUT TYPE="hidden" NAME="invoicing_list" VALUE="<% join(', ', @invoicing_list) %>">
+
+  </FORM>
+
+% } else {
+%
+%  my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+  <BR>Billing information
+  <% &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>
+
+  <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT>
+  <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT>
+  <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT>
+  <SCRIPT TYPE="text/javascript">
+  function OLiframeContent(src, width, height, name) {
+    return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'
+     +(name?' name="'+name+'" id="'+name+'"':'')+' scrolling="auto">'
+     +'<div>[iframe not supported]</div></iframe>');
+  }
+  </SCRIPT>
+
+%  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="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' => '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="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' => '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="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="payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+%
+%        qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="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="payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD>'.
+%          qq!<TD ALIGN="right">Type</TD><TD><SELECT NAME="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="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="exp_month" VALUE="12">!.
+%        qq!<INPUT TYPE="hidden" NAME="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="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('select-state.html',
+%                    'empty'   => '(choose)',
+%                    'state'   => $cust_main->paystate,
+%                    'country' => $cust_main->country,
+%                    'prefix' => 'pay',
+%                 ). "</TD></TR>"
+%         : '<INPUT TYPE="hidden" NAME="paystate" VALUE="'.
+%            $cust_main->paystate. '">'
+%       ).
+%
+%
+%        qq!<TR><TD COLSPAN=4 WIDTH="608"><INPUT TYPE="checkbox" NAME="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="payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!.
+%
+%        qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!.
+%        qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!.
+%        qq!<INPUT TYPE="hidden" NAME="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="payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!.
+%
+%        qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!.
+%        qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!.
+%
+%        qq!<TR><TD ALIGN="right" WIDTH="200">Attention </TD>!.
+%          qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="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="payinfo" VALUE=""></TD></TR>!.
+%
+%        qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!.
+%          '<TD WIDTH="408">'.
+%
+%          include('/elements/select-month_year.html',
+%                    'prefix' => '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="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="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="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 = (
+%    'CARD' => 'Credit card',
+%    'CHEK' => 'Electronic check',
+%    'LECB' => 'Phone bill billing',
+%    'BILL' => 'Billing',
+%    'CASH' => 'Cash', # initial payment, then billing',
+%    'WEST' => 'Western Union', # initial payment, then billing',
+%    'MCRD' => 'Manual credit card', # initial payment, then billing',
+%    'COMP' => 'Complimentary',
+%  );
+%  if ( $cust_main->custnum ) { #don't offer CASH/WEST/MCRD initial payment types
+%                               # when editing customer
+%    delete $allopt{$_} for qw(CASH WEST MCRD);
+%  }
+%  
+%  tie my %options, 'Tie::IxHash',
+%    map  { $_ => $allopt{$_} }
+%    grep { exists $allopt{$_} }
+%         @payby;
+%
+%  my %payby2option = (
+%    ( map { $_ => $_ } keys %options ),
+%    'DCRD' => 'CARD',
+%    'DCHK' => 'CHEK',
+%  );
+%
+%  my $widget = new HTML::Widgets::SelectLayers(
+%    'options'        => \%options,
+%    #'form_name'      => 'dummy',
+%    #'form_action'    => 'nothingyet',
+%    #chops bottom of page in IE# 'under_position' => 'absolute',
+%    'html_between'   => '</TD></TR></TABLE>',
+%    'selected_layer' => $payby2option{$payby || $payby_default || $payby[0] },
+%    'layer_callback' => sub { my $layer = shift; $payby{$layer}; },
+%  );
+%
+%  
+
+
+  <TD WIDTH="408"><% $widget->html %>
+
+  <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0">
+
+  <% &ntable("#cccccc") %>
+
+    <TR><TD>&nbsp;</TD></TR>
+
+    <TR>
+      <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt</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">
+        <SELECT NAME="invoice_terms">
+          <OPTION VALUE="">Default (<% $conf->config('invoice_default_terms') || 'Payable upon receipt' %>)
+%         foreach my $term ( 'Payable upon receipt',
+%                            ( map "Net $_", 0, 10, 15, 30, 45, 60 ),
+%                          ) {
+            <OPTION VALUE="<% $term %>" <% $cust_main->invoice_terms eq $term ? ' SELECTED' : '' %>><% $term %>
+%         }
+        </SELECT>
+      </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 %>">
+% } 
+
+  </TABLE>
+
+  </FORM>
+
+  <% $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;
+
+</%init>
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
new file mode 100644 (file)
index 0000000..21c6b29
--- /dev/null
@@ -0,0 +1,183 @@
+<% &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%>> , 
+    <INPUT TYPE="text" NAME="<%$pre%>first" VALUE="<% $cust_main->get($pre.'first') %>" onChange="<% $onchange %>" <%$disabled%>>
+  </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%>>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right"><%$r%>Address</TH>
+  <TD COLSPAN=7>
+    <INPUT TYPE="text" NAME="<%$pre%>address1" VALUE="<% $cust_main->get($pre.'address1') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>>
+  </TD>
+</TR>
+
+% my $address2_label_style =
+%   ( $disabled
+%     || ! $conf->exists('cust_main-require_address2')
+%     || ( !$pre && !$opt{'same_checked'} )
+%   )
+%     ? 'visibility:hidden'
+%     : '';
+
+<TR>
+  <TD ALIGN="right"><FONT ID="<% $pre %>address2_required" color="#ff0000" STYLE="<% $address2_label_style %>">*</FONT>&nbsp;<FONT ID="<% $pre %>address2_label" STYLE="<% $address2_label_style %>"><B>Unit&nbsp;#</B></FONT></TD>
+  <TD COLSPAN=7>
+    <INPUT TYPE="text" NAME="<%$pre%>address2" VALUE="<% $cust_main->get($pre.'address2') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right"><%$r%>City</TH>
+  <TD>
+    <INPUT TYPE="text" NAME="<%$pre%>city" VALUE="<% $cust_main->get($pre.'city') %>" onChange="<% $onchange %>" <%$disabled%>>
+  </TD>
+  <TH ALIGN="right" ID="<%$pre%>countylabel" <%$county_style%>><%$r%>County</TH>
+  <TD>
+    <% include('select-county.html', %select_hash ) %>
+  </TD>
+  <TH ALIGN="right"><%$r%>State</TH>
+  <TD>
+    <% include('select-state.html', %select_hash ) %>
+  </TD>
+  <TH><%$r%>Zip</TH>
+  <TD>
+    <INPUT TYPE="text" NAME="<%$pre%>zip" VALUE="<% $cust_main->get($pre.'zip') %>" SIZE=10 onChange="<% $onchange %>" <%$disabled%>>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right"><%$r%>Country</TH>
+  <TD COLSPAN=5><% include('select-country.html', %select_hash ) %></TD>
+</TR>
+
+<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%>>
+  </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%>>
+  </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%>>
+  </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%>></TD>
+  <TD ALIGN="right"><% $stateid_state_label %></TD>
+  <TD><% include('select-state.html', 'state' => $cust_main->stateid_state,
+                                      'country' => $cust_main->country,
+                                      'prefix'  => 'stateid_',
+                                      'onchange' => $onchange,
+                                      'disabled' => $disabled) %></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 $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');
+
+#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'   => $cust_main->get($pre.'county'),
+  'state'    => $cust_main->get($pre.'state'),
+  'country'  => $cust_main->get($pre.'country'),
+  'prefix'   => $pre,
+  'onchange' => $onchange,
+  'disabled' => $disabled,
+);
+
+my @counties = counties( $cust_main->get($pre.'state'),
+                         $cust_main->get($pre.'country'),
+                       );
+my $county_style = scalar(@counties) > 1 ? '' : 'STYLE="visibility:hidden"';
+
+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/select-country.html b/httemplate/edit/cust_main/select-country.html
new file mode 100644 (file)
index 0000000..137f619
--- /dev/null
@@ -0,0 +1,76 @@
+
+<% include('/elements/xmlhttp.html',
+              'url'  => $p.'misc/states.cgi',
+              'subs' => [ $opt{'prefix'}. '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 <% $opt{'prefix'} %>country_changed(what, callback) {
+
+    country = what.options[what.selectedIndex].value;
+
+    function <% $opt{'prefix'} %>update_states(states) {
+
+      // blank the current state list
+      for ( var i = what.form.<% $opt{'prefix'} %>state.length; i >= 0; i-- )
+          what.form.<% $opt{'prefix'} %>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.<% $opt{'prefix'} %>state, statesArray[s], stateLabel);
+      }
+
+      //run the callback
+      if ( callback != null ) 
+        callback();
+    }
+
+    // go get the new states
+    <% $opt{'prefix'} %>get_states( country, <% $opt{'prefix'} %>update_states );
+
+  }
+
+</SCRIPT>
+
+<SELECT NAME="<% $opt{'prefix'} %>country" onChange="<% $opt{'prefix'} %>country_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+
+% foreach my $country (
+%     sort {    ($b eq $countrydefault) <=> ($a eq $countrydefault)
+%            or code2country($a) cmp code2country($b) }
+%     map { $_->country }
+%     qsearch({
+%               'select'    => 'country',
+%               'table'     => 'cust_main_county',
+%               'hashref'   => {},
+%               'extra_sql' => 'GROUP BY country',
+%            })
+% ) {
+
+  <OPTION VALUE="<% $country %>"<% $country eq $opt{'country'} ? ' SELECTED' : '' %>><% code2country($country). " ($country)" %>
+
+% } 
+
+</SELECT>
+
+<%init>
+my %opt = @_;
+foreach my $opt (qw( county state country prefix onchange disabled )) {
+  $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+
+my $conf = new FS::Conf;
+my $countrydefault = $conf->config('countrydefault') || 'US';
+</%init>
+
diff --git a/httemplate/edit/cust_main/select-county.html b/httemplate/edit/cust_main/select-county.html
new file mode 100644 (file)
index 0000000..0dc8268
--- /dev/null
@@ -0,0 +1,113 @@
+% if ( $countyflag ) { 
+
+  <% include('/elements/xmlhttp.html',
+                'url'  => $p.'misc/counties.cgi',
+                'subs' => [ $opt{'prefix'}. '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 <% $opt{'prefix'} %>state_changed(what, callback) {
+
+      state = what.options[what.selectedIndex].value;
+      country = what.form.<% $opt{'prefix'} %>country.options[what.form.<% $opt{'prefix'} %>country.selectedIndex].value;
+  
+      function <% $opt{'prefix'} %>update_counties(counties) {
+
+        // blank the current county list
+        for ( var i = what.form.<% $opt{'prefix'} %>county.length; i >= 0; i-- )
+            what.form.<% $opt{'prefix'} %>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.<% $opt{'prefix'} %>county, countiesArray[s], countyLabel);
+        }
+
+        var countyFormLabel = document.getElementById('<% $opt{'prefix'} %>countylabel');
+
+        if ( countiesArray.length > 1 ) { 
+          what.form.<% $opt{'prefix'} %>county.style.display = '';
+          countyFormLabel.style.visibility = 'visible';
+        } else {
+          what.form.<% $opt{'prefix'} %>county.style.display = 'none';
+          countyFormLabel.style.visibility = 'hidden';
+        }
+
+        //run the callback
+        if ( callback != null ) 
+          callback();
+      }
+  
+      // go get the new counties
+      <% $opt{'prefix'} %>get_counties( state, country, <% $opt{'prefix'} %>update_counties );
+  
+    }
+  
+  </SCRIPT>
+
+  <SELECT NAME="<% $opt{'prefix'} %>county" onChange="<% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+
+% foreach my $county ( @counties ) {
+
+    <OPTION VALUE="<% $county %>"<% $county eq $opt{'county'} ? ' SELECTED' : '' %>><% $county %>
+
+% } 
+
+  </SELECT>
+
+% } else { 
+
+
+  <SCRIPT TYPE="text/javascript">
+    function <% $opt{'prefix'} %>state_changed(what) {
+    }
+  </SCRIPT>
+
+  <INPUT TYPE="hidden" NAME="<% $opt{'prefix'} %>county" VALUE="<% $opt{'county'} %>">
+
+% } 
+
+<%init>
+
+my %opt = @_;
+foreach my $opt (qw( county state country prefix onchange disabled )) {
+  $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+
+my @counties = ();
+if ( $countyflag ) {
+
+  @counties = counties( $opt{'state'}, $opt{'country'} );
+
+  # this is very hacky
+  unless ( scalar(@counties) > 1 ) {
+    if ( $opt{'disabled'} =~ /STYLE=/i ) {
+      $opt{'disabled'} =~ s/STYLE="([^"]+)"/STYLE="$1; display:none"/i;
+    } else {
+      $opt{'disabled'} .= ' STYLE="display:none"';
+    }
+  }
+
+}
+
+</%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/edit/cust_main/select-domain.html b/httemplate/edit/cust_main/select-domain.html
new file mode 100644 (file)
index 0000000..bec1e83
--- /dev/null
@@ -0,0 +1,67 @@
+
+<% include('/elements/xmlhttp.html',
+              'url'  => $p.'misc/svc_acct-domains.cgi',
+              'subs' => [ $opt{'prefix'}. 'get_domains' ],
+           )
+%>
+
+<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;
+  }
+
+  function <% $opt{'prefix'} %>pkgpart_svcpart_changed(what,selected) {
+
+    pkgpart_svcpart = what.options[what.selectedIndex].value;
+
+    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);
+      }
+
+    }
+
+    // go get the new domains
+    <% $opt{'prefix'} %>get_domains( pkgpart_svcpart, <% $opt{'prefix'} %>update_domains );
+
+  }
+
+</SCRIPT>
+
+<SELECT NAME="<% $opt{'prefix'} %>pkgpart_svcpart" onchange="<% $opt{'prefix'} %>pkgpart_svcpart_changed(this,0);" >
+  <OPTION VALUE="">(none)
+
+% foreach my $part_pkg ( @part_pkg ) {
+
+  <OPTION VALUE="<% $part_pkg->pkgpart. "_". $part_pkg->svcpart('svc_acct') %>"<% ( $opt{saved_pkgpart} && $part_pkg->pkgpart == $opt{saved_pkgpart} ) ? ' SELECTED' : '' %>><% $part_pkg->pkg. " - ". $part_pkg->comment %>
+
+% } 
+
+</SELECT>
+<SCRIPT>
+  pkgpart_svcpart_changed(document.bottomform.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 @part_pkg = @{$opt{'pkgparts'}};
+
+</%init>
+
diff --git a/httemplate/edit/cust_main/select-state.html b/httemplate/edit/cust_main/select-state.html
new file mode 100644 (file)
index 0000000..4f1c056
--- /dev/null
@@ -0,0 +1,24 @@
+<SELECT NAME="<% $opt{'prefix'} %>state" onChange="<% $opt{'prefix'} %>state_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+
+% if ($opt{empty}) {
+  <OPTION VALUE=""<% $opt{state} eq '' ? ' SELECTED' : '' %>><% $opt{empty} %>
+% }
+
+% foreach my $state ( keys %states ) { 
+
+  <OPTION VALUE="<% $state %>"<% $state eq $opt{'state'} ? ' SELECTED' : '' %>><% $states{$state} || '(n/a)' %>
+
+% } 
+
+
+</SELECT>
+
+<%init>
+my %opt = @_;
+foreach my $opt (qw( county state country prefix onchange disabled empty )) {
+  $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+
+tie my %states, 'Tie::IxHash', states_hash( $opt{'country'} ); 
+</%init>
+
diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi
new file mode 100755 (executable)
index 0000000..d5297ab
--- /dev/null
@@ -0,0 +1,50 @@
+<% 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->county;
+
+if ( $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 (file)
index 0000000..510839d
--- /dev/null
@@ -0,0 +1,62 @@
+<% 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',
+                   '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 'error, but no taxnum';
+  $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', },
+);
+
+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 (executable)
index 0000000..6c6a1a9
--- /dev/null
@@ -0,0 +1,45 @@
+<% 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 %>">
+
+
+<BR><BR>
+<TEXTAREA NAME="comment" ROWS="12" COLS="60">
+<% $comment %>
+</TEXTAREA>
+
+<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;
+}
+
+$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 (executable)
index 0000000..92abb7b
--- /dev/null
@@ -0,0 +1,147 @@
+% if ( $link eq 'popup' ) { 
+  <% include('/elements/header-popup.html', $title ) %>
+% } else { 
+  <%  include("/elements/header.html", $title, '') %>
+% } 
+
+<% include('/elements/error.html') %>
+
+<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 ACTION="<% popurl(1) %>process/cust_pay.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="link" VALUE="<% $link %>">
+<INPUT TYPE="hidden" NAME="linknum" VALUE="<% $linknum %>">
+
+% unless ( $link eq 'popup' ) { 
+    <% small_custview($custnum, $conf->config('countrydefault')) %>
+% } 
+
+<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>">
+
+<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("%m/%d/%Y %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:   "%m/%d/%Y",
+    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><% $payby{$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>
+
+</TABLE>
+
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch %>">
+
+<BR>
+<INPUT TYPE="submit" VALUE="Post payment">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%once>
+
+my $conf = new FS::Conf;
+
+my %payby = (
+  'BILL' => 'Check',
+  'CASH' => 'Cash',
+  'WEST' => 'Western Union',
+  'MCRD' => 'Manual credit card',
+);
+
+my $money_char = $conf->config('money_char') || '$';
+
+</%once>
+<%init>
+
+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') ? str2time($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 '. $payby{$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' ) {
+  $custnum = $linknum;
+}
+</%init>
+
diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi
new file mode 100755 (executable)
index 0000000..ecc2119
--- /dev/null
@@ -0,0 +1,154 @@
+<% include('/elements/header.html', "Add/Edit Packages", '') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_pkg.cgi" METHOD=POST>
+
+<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') } = $_->getfield('comment');
+  next if $_->disabled;
+  $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
+  $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+}
+
+my($custnum, %remove_pkg);
+if ( $cgi->param('error') ) {
+  $custnum = $cgi->param('custnum');
+  %remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg');
+} else {
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/;
+  $custnum = $1;
+  %remove_pkg = ();
+}
+
+my $p1 = popurl(1);
+
+</%init>
+
diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi
new file mode 100755 (executable)
index 0000000..3333f5d
--- /dev/null
@@ -0,0 +1,141 @@
+<% include('/elements/header.html', 'Refund '. ucfirst(lc($payby)). ' payment', '') %>
+
+<% include('/elements/error.html') %>
+
+<% 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="refundnum" VALUE="">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+<INPUT TYPE="hidden" NAME="paynum" VALUE="<% $paynum %>">
+<INPUT TYPE="hidden" NAME="_date" VALUE="<% $_date %>">
+<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>">
+<INPUT TYPE="hidden" NAME="payinfo" VALUE="">
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="">
+<INPUT TYPE="hidden" NAME="credited" VALUE="">
+<BR>
+% if ( $cust_pay ) {
+%
+%  #false laziness w/FS/FS/cust_pay.pm
+%  my $payby = $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$'/);
+%  }
+%  $payby =~ s/^BILL$/Check/ if $paymask;
+%  $payby =~ s/^CHEK$/Electronic check/;
+%
+%
+
+
+  <BR>Payment
+  <% ntable("#cccccc", 2) %>
+
+    <TR>
+      <TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$<% $cust_pay->paid %></TD>
+    </TR>
+
+  <TR>
+    <TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff"><% time2str("%D",$cust_pay->_date) %></TD>
+  </TR>
+
+  <TR>
+    <TD ALIGN="right">Method</TD><TD BGCOLOR="#ffffff"><% ucfirst(lc($payby)) %> # <% $paymask %></TD>
+  </TR>
+
+% unless ( $paydate ) {  # 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("%D",$_date) %></TD>
+  </TR>
+
+  <TR>
+    <TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="refund" VALUE="<% $refund %>" SIZE=8 MAXLENGTH=8></TD>
+  </TR>
+
+  <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>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Refund payment');
+
+my $conf = new FS::Conf;
+my $custnum = $cgi->param('custnum');
+my $refund  = $cgi->param('refund');
+my $payby   = $cgi->param('payby');
+my $reason  = $cgi->param('reason');
+
+my( $paynum, $cust_pay ) = ( '', '' );
+if ( $cgi->param('paynum') =~ /^(\d+)$/ ) {
+  $paynum = $1;
+  $cust_pay = qsearchs('cust_pay', { paynum=>$paynum } )
+    or die "unknown payment # $paynum";
+  $refund ||= $cust_pay->unrefunded;
+  if ( $custnum ) {
+    die "payment # $paynum is not for specified customer # $custnum"
+      unless $custnum == $cust_pay->custnum;
+  } else {
+    $custnum = $cust_pay->custnum;
+  }
+}
+die "no custnum or paynum specified!" unless $custnum;
+
+my $_date = time;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
new file mode 100644 (file)
index 0000000..ad52f7a
--- /dev/null
@@ -0,0 +1,536 @@
+<%doc>
+
+Example:
+
+  include( 'elements/edit.html',  
+    'name'  =>
+    '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't use after a tablebreak-tr-title yet... grep "OneTrueTable")
+                                       #title
+                                       #tablebreak-tr-title
+                                       #hidden - hidden value from object
+                                       #fixed - display fixed value from object or here
+                                       #fixed-country
+                                       #fixed-state
+                    'value' => 'Y', #for checkbox, title, fixed, fixedhidden
+                    'disabled' => 0,
+                    'onchange' => 'javascript_function',
+                    'm2name_table'   => 'table_name',   #only tested w/
+                                                        # selectlayers so far
+                                                        # might work w/select
+                                                        # dunno others
+                    'm2name_namecol' => 'name_column',  # 
+                    'm2name_label'   => 'Label',        #
+                    'm2name_new_default' => \@table_name_objects, #default
+                                                                  #m2name
+                                                                  #objects for
+                                                                  #new records
+                    'm2name_error_callback' => sub { my($cgi, $object) = @_; },
+                    'm2name_remove_warnings' => \%warnings, #hashref of warning
+                                                            #messages for
+                                                            #m2name removal
+                    'm2name_new_js' => 'function_name', #javascript function
+                                                        #called on spawned rows
+                                                        #(one arg: new_element)
+                    'm2name_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_right' => 'Access Right Name',
+   
+    #run when re-displaying with an error
+    'error_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
+   
+    #run when editing
+    'edit_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
+   
+    # returns a hashref for the new object
+    'new_hashref_callback'
+   
+    #run when adding
+    'new_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
+   
+    #XXX describe
+    'field_callback' => sub { },
+
+    #string or coderef of additional HTML to add before </TABLE>
+    'html_table_bottom' => '',
+   
+    'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
+   
+    'html_bottom' => '', #string
+    'html_bottom' => sub {
+                           my $object = shift;
+                           # ...
+                           "html_string";
+                         },
+   
+    # overrides default popurl(1)."process/$table.html"
+    'post_url' => popurl(1).'process/something', 
+
+    #we're in a popup (no title/menu/searchboxes)
+    'popup' => 1,
+   
+  );
+
+</%doc>
+
+<% include('/elements/header'. ( $opt{popup} ? '-popup' : '' ). '.html',
+              $title,
+              include( '/elements/menubar.html', @menubar )
+           )
+%>
+
+<% include('/elements/error.html') %>
+
+% my $url = $opt{'post_url'} || popurl(1)."process/$table.html";
+
+<FORM ACTION="<% $url %>" METHOD=POST NAME="edit_topform">
+
+<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $table %>">
+<INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $object->$pkey() %>">
+
+<FONT SIZE="+1"><B>
+<% ( $opt{labels} && exists $opt{labels}->{$pkey} )
+      ? $opt{labels}->{$pkey}
+      : $pkey
+%>
+</B></FONT>
+#<% $object->$pkey() || "(NEW)" %>
+
+%# <% ntable("#cccccc",0) %>
+<TABLE ID="OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+% my $g_row = 0;
+% foreach my $f ( map { ref($_) ? $_ : {'field'=>$_} }
+%                       @$fields
+%                 ) {
+%
+%   &{ $opt{'field_callback'} }( $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 = {};
+%   if ( $f->{'layer_values_callback'} && ! $f->{'m2name_table'} ) {
+%     $layer_values = &{ $f->{'layer_values_callback'} }( $cgi, $object );
+%   }
+%   warn "layer values: ". Dumper($layer_values)
+%     if $opt{'debug'};
+%
+%   my %include_common = (
+%
+%     #text and derivitives
+%     'size'          => $f->{'size'},
+%
+%     #checkbox, title, fixed, fixedhidden
+%     #& 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'},
+%   );
+%
+%   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)/;
+%
+%     $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;
+%   };
+%
+%   $g_row++;
+%   $g_row++ if $type eq 'title';
+%
+%   my $fieldnum = '';
+%   my $curr_value = '';
+%   if ( $f->{'m2name_table'} ) { #XXX test this for all types of fields
+%     my $table = $f->{'m2name_table'};
+%     my $col   = $f->{'m2name_namecol'};
+%     $fieldnum = 0;
+%     $layer_prefix_on = 1;
+%     #print out the fields for the existing m2names
+%     my @existing = ();
+%     if ( $mode eq 'error' ) {
+%       @existing = &{ $f->{'m2name_error_callback'} }( $cgi, $object );
+%     } elsif ( $object->$pkey() ) { # $mode eq 'edit'
+%       @existing = $object->$table();
+%     } elsif ( $f->{'m2name_new_default'} ) { # && $mode eq 'new'
+%       @existing = @{ $f->{'m2name_new_default'} };
+%     }
+%     foreach my $name_obj ( @existing ) {
+%
+%       my $ex_label = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this '.
+%                      lc($f->{'m2name_label'}).
+%                      qq(" onClick="remove_$field($fieldnum);").
+%                      ' STYLE="color:#ff0000;font-weight:bold;'.
+%                              'padding-left:2px;padding-right:2px"'.
+%                      '>&nbsp;'. ($f->{'m2name_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' : '' ),
+%       );
+
+        <% include( @existing ) %>
+
+%       $fieldnum++;
+%       $g_row++;
+%     }
+%     #$field .= $fieldnum;
+%     $onchange .= "\nspawn_$field(what);";
+%   } else {
+%     $curr_value = $object->$field();
+%   }
+%
+%   my @include = &{ $include_sub }(
+%     'label'      => $label,
+%     'fieldnum'   => $fieldnum,
+%     'curr_value' => $curr_value,
+%     'object'     => $object,
+%     'onchange'   => $onchange,
+%     'cell_style'   => ( $fieldnum ? 'border-top:1px solid black' : '' ),
+%   );
+
+    <% include( @include ) %>
+
+%   if ( $f->{'m2name_table'} ) {
+
+      <SCRIPT TYPE="text/javascript">
+
+        var rownum = <% $g_row %>;
+        var fieldnum = <% $fieldnum %>;
+
+        function spawn_<%$field%>(what) {
+
+          // only spawn if we're the last element... return if not
+
+          var field_regex = /(\d+)$/;
+          var match = field_regex.exec(what.name);
+          if ( !match ) {
+            alert(what.name + " didn't match?!");
+            return;
+          }
+          if ( match[1] != fieldnum ) {
+            return;
+          }
+
+          // change the label on the last entry & add a remove button
+          var prev_label = document.getElementById('<% $field %>_label' + fieldnum );
+          prev_label.innerHTML = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this <% lc($f->{'m2name_label'}) %>" onClick="remove_<% $field %>(' + fieldnum + ');" STYLE="color:#ff0000;font-weight:bold;padding-left:2px;padding-right:2px" >&nbsp;<% $f->{'m2name_label'} || $field %>';
+
+          fieldnum++;
+
+          //get the new widget
+
+%         $include[0] =~ s(^/elements/tr-)(/elements/);
+%         my @layer_opt = ( @include,
+%                           'field'        => $field."MAGIC_NUMBER",
+%                           'layer_prefix' => $field."MAGIC_NUMBER.",
+%                         );
+
+          var newrow =  <% include(@layer_opt, html_only=>1) |js_string %>;
+
+%         if ( $type eq 'selectlayers' ) { #until the rest have html/js_only
+            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, fieldnum );
+          newfunc = newfunc.replace( magic_regex, 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('OneTrueTable');
+
+          var row = table.insertRow(rownum++);
+
+          var label_cell = document.createElement('TD');
+
+          label_cell.id = '<% $field %>_label' + 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.innerHTML = newrow;
+
+          row.appendChild(widget_cell);
+
+%         if ( $f->{'m2name_new_js'} ) {
+            // take out items selected in previous dropdowns
+            var new_element = document.getElementById("<%$field%>" + fieldnum );
+            <% $f->{'m2name_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);
+
+%         my $warnings = $f->{'m2name_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->{m2name_remove_js} ) {
+            var opt = select.options[select.selectedIndex];
+            <% $f->{m2name_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="<% $object->$pkey() ? "Apply changes" : "Add $opt{'name'}" %>">
+
+</FORM>
+
+<% 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);
+}
+
+my $object;
+my $mode;
+if ( $cgi->param('error') ) {
+
+  $mode = 'error';
+
+
+  $object = $class->new( {
+    map { $_ => scalar($cgi->param($_)) } fields($table)
+  });
+
+  &{$opt{'error_callback'}}($cgi, $object, $fields)
+    if $opt{'error_callback'};
+
+} 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({
+    'table'     => $table,
+    'hashref'   => { $pkey => $1 },
+    'extra_sql' => ( $opt{'agent_virt'}
+                       ? ' AND '. $curuser->agentnums_sql(
+                                    'null_right' => $opt{'agent_null_right'}
+                                  )
+                       : ''
+                   ),
+  });
+  warn "$table $pkey => $1"
+    if $opt{'debug'};
+
+  &{$opt{'edit_callback'}}($cgi, $object, $fields)
+    if $opt{'edit_callback'};
+
+} else { #adding
+
+  $mode = 'new';
+
+  my $hashref = $opt{'new_hashref_callback'}
+                  ? &{$opt{'new_hashref_callback'}}
+                  : {};
+
+  $object = $class->new( $hashref );
+
+  &{$opt{'new_callback'}}($cgi, $object, $fields)
+    if $opt{'new_callback'};
+
+}
+
+my $action = $object->$pkey() ? 'Edit' : 'Add';
+
+my $title = "$action $opt{'name'}";
+
+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 {
+  @menubar = (
+    #eventually use Lingua::bs to pluralize
+    "View all $opt{'name'}s" => $viewall_url,
+  );
+}
+
+</%init>
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
new file mode 100644 (file)
index 0000000..72abcba
--- /dev/null
@@ -0,0 +1,99 @@
+%
+%  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??
+%  }
+%
+<% include( 'edit.html',
+
+                 'menubar' => [],
+
+                 'error_callback' => sub {
+                   my( $cgi, $svc_x ) = @_;
+                   #$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;
+                 },
+
+                 'edit_callback' => sub {
+                   my( $cgi, $svc_x ) = @_;
+                   #$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;
+                 },
+
+                 'new_hash_callback' => sub {
+                   #my( $cgi, $svc_x ) = @_;
+
+                   { svcpart => $svcpart };
+
+                 },
+
+                 'new_callback' => sub {
+                    my( $cgi, $svc_x ) = @_;;
+
+                    $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart });
+                    die "No part_svc entry!" unless $part_svc;
+
+                    #$svcnum='';
+
+                    $svc_x->set_default_and_fixed;
+
+                 },
+
+                 'field_callback' => sub {
+                   my $f = shift;
+                   my $columndef = $part_svc->part_svc_column($f->{'field'});
+                   my $flag = $columndef->columnflag;
+                   if ( $flag eq 'F' ) {
+                     $f->{'type'} = 'fixed';
+                     $f->{'value'} = $columndef->columnvalue;
+                   }
+                 },
+
+                 '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
+             )
+%>
diff --git a/httemplate/edit/inventory_class.html b/httemplate/edit/inventory_class.html
new file mode 100644 (file)
index 0000000..3ab47fe
--- /dev/null
@@ -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 (file)
index 0000000..0c45e4b
--- /dev/null
@@ -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 (PNG 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 (file)
index 0000000..851ab5e
--- /dev/null
@@ -0,0 +1,80 @@
+<% 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' ) {
+
+% #init
+  <SCRIPT TYPE="text/javascript" src="<% $p %>elements/fckeditor/fckeditor.js">
+  </SCRIPT>
+
+% #editor
+  <SCRIPT TYPE="text/javascript">
+    var oFCKeditor = new FCKeditor('value');
+    oFCKeditor.Value = <% $value |js_string %>;
+
+    oFCKeditor.BasePath = '<% $p %>elements/fckeditor/';
+    oFCKeditor.Config['SkinPath'] = '<% $p %>elements/fckeditor/editor/skins/silver/';
+    oFCKeditor.Height = '800';
+    oFCKeditor.Config['StartupFocus'] = true;
+
+    oFCKeditor.Create();
+
+  </SCRIPT>
+
+% } 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/msgcat.cgi b/httemplate/edit/msgcat.cgi
new file mode 100755 (executable)
index 0000000..85b3008
--- /dev/null
@@ -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 (executable)
index 0000000..2547067
--- /dev/null
@@ -0,0 +1,554 @@
+<% 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">
+% 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>';
+%}
+%
+%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', '%%%late_taxclass%%%',
+%              'name' => 'late_taxclass' );
+%  $late_percent_taxclass =
+%    '<BR>Taxclass '.
+%    include('/elements/select-taxclass.html', '%%%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%%%);',
+%    '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%%%, );',
+%    'html'   => " $money_char ". '<INPUT TYPE="text" SIZE="7" NAME="balanceover" VALUE="%%%balanceover%%%">',
+%    'weight' => 10,
+%    'reason' => 'S',
+%  },
+%  'suspend-if-pkgpart' => {
+%    'name'   => 'Suspend packages',
+%    'code'   => '$cust_main->suspend_if_pkgpart({pkgparts => [%%%if_pkgpart%%%,], reason => %%%sreason%%%,});',
+%    'html'   => sub { &select_pkgpart('if_pkgpart', @_) },
+%    'weight' => 10,
+%    'reason' => 'S',
+%  },
+%  'suspend-unless-pkgpart' => {
+%    'name'   => 'Suspend packages except',
+%    'code'   => '$cust_main->suspend_unless_pkgpart({unless_pkgpart => [%%%unless_pkgpart%%%], reason => %%%sreason%%%,});',
+%    'html'   => sub { &select_pkgpart('unless_pkgpart', @_) },
+%    'weight' => 10,
+%    'reason' => 'S',
+%  },
+%  'cancel' => {
+%    'name'   => 'Cancel',
+%    'code'   => '$cust_main->cancel(reason => %%%creason%%%);',
+%    'weight' => 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' => 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%%%\');',
+%    '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>
+%        </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,
+%  },
+%
+%  'collect' => {
+%    'name' => 'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)',
+%    'code' => '$cust_main->collect();',
+%    'weight'  => 80,
+%  },
+%
+%;
+%
+<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>';
+%}
+%
+%  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_event.html b/httemplate/edit/part_event.html
new file mode 100644 (file)
index 0000000..5b14edf
--- /dev/null
@@ -0,0 +1,681 @@
+<% 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',
+                              m2name_label       => 'Condition',
+                              m2name_new_default => \@implicit_condition_objs,
+                              m2name_error_callback  =>
+                                $condition_error_callback,
+                              m2name_remove_warnings =>
+                                \%condition_remove_warnings,
+                              m2name_new_js      => 'condition_repop',
+                              m2name_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 {
+  #m2name_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 (file)
index 0000000..d579797
--- /dev/null
@@ -0,0 +1,123 @@
+<% 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' ) {
+        $html .= qq!<SELECT NAME="$option">!;
+        foreach my $select_option ( @{$optinfo->{options}} ) {
+          #if ( ref($select_option) ) {
+          #} else {
+            my $selected = $select_option eq $value ? ' SELECTED' : '';
+            $html .= qq!<OPTION VALUE="$select_option"$selected>!.
+                     qq!$select_option</OPTION>!;
+          #}
+        }
+        $html .= '</SELECT>';
+      } elsif ( $type eq 'textarea' ) {
+        $html .= qq!<TEXTAREA NAME="$option" COLS=80 ROWS=8 WRAP="virtual">!.
+                 encode_entities($value). '</TEXTAREA>';
+      } elsif ( $type eq 'text' ) {
+        $html .= qq!<INPUT TYPE="text" NAME="$option" VALUE="!.
+                 encode_entities($value). '" SIZE=64>';
+      } elsif ( $type eq 'checkbox' ) {
+        $html .= qq!<INPUT TYPE="checkbox" NAME="$option" VALUE="1"!;
+        $html .= ' CHECKED' if $value;
+        $html .= '>';
+      } else {
+        $html .= "unknown type $type";
+      }
+      $html .= '</TD></TR>';
+    }
+    $html .= '</TABLE>';
+
+    $html .= '<INPUT TYPE="hidden" NAME="options" VALUE="'.
+             join(',', keys %{$exports->{$layer}{options}} ). '">';
+
+    $html .= '<INPUT TYPE="hidden" NAME="nodomain" VALUE="'.
+             $exports->{$layer}{nodomain}. '">';
+
+    $html .= '<INPUT TYPE="submit" VALUE="'.
+             ( $part_export->exportnum ? "Apply changes" : "Add export" ).
+             '">';
+
+    $html;
+  },
+);
+
+</%init>
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
new file mode 100755 (executable)
index 0000000..ec001cb
--- /dev/null
@@ -0,0 +1,429 @@
+<% include('/elements/header.html', "$action Package Definition", menubar(
+  'View all packages' => popurl(2). 'browse/part_pkg.cgi',
+)) %>
+% #), ' onLoad="visualize()"'); 
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="dummy">
+
+<% itable('',8,1) %><TR><TD VALIGN="top">
+
+Package information
+
+<% ntable("#cccccc",2) %>
+  <TR>
+    <TD ALIGN="right">Package Definition #</TD>
+    <TD BGCOLOR="#ffffff">
+      <% $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)" %>
+    </TD>
+  </TR>
+  <TR>
+    <TD ALIGN="right">Package (customer-visible)</TD>
+    <TD>
+      <INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="<% $part_pkg->pkg %>">
+    </TD>
+  </TR>
+  <TR>
+    <TD ALIGN="right">Comment (customer-hidden)</TD>
+    <TD>
+      <INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="<%$part_pkg->comment%>">
+    </TD>
+  </TR>
+  <% include( '/elements/tr-select-pkg_class.html',
+                'curr_value' => $part_pkg->classnum,
+            )
+  %>
+  <TR>
+    <TD ALIGN="right">Promotional code</TD>
+    <TD>
+      <INPUT TYPE="text" NAME="promo_code" SIZE=32 VALUE="<%$part_pkg->promo_code%>">
+    </TD>
+  </TR>
+  <TR>
+    <TD ALIGN="right">Disable new orders</TD>
+    <TD>
+      <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>
+    </TD>
+  </TR>
+
+</TABLE>
+
+</TD><TD VALIGN="top">
+
+Tax information
+<% ntable("#cccccc", 2) %>
+  <TR>
+    <TD ALIGN="right">Setup fee tax exempt</TD>
+    <TD>
+      <INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $hashref->{setuptax} eq 'Y' ? ' CHECKED' : '' %>>
+    </TD>
+  </TR>
+  <TR>
+    <TD ALIGN="right">Recurring fee tax exempt</TD>
+    <TD>
+      <INPUT TYPE="checkbox" NAME="recurtax" VALUE="Y" <% $hashref->{recurtax} eq 'Y' ? ' CHECKED' : '' %>>
+    </TD>
+  </TR>
+
+% if ( $conf->exists('enable_taxclasses') ) { 
+
+  <TR>
+    <TD align="right">Tax class</TD>
+    <TD>
+      <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %>
+    </TD>
+  </TR>
+
+% } else { 
+
+  <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %>
+
+% } 
+
+</TABLE>
+<BR>
+
+Line-item revenue recognition
+<% ntable("#cccccc", 2) %>
+% tie my %weight, 'Tie::IxHash',
+%   'pay_weight'    => 'Payment',
+%   'credit_weight' => 'Credit'
+% ;
+% foreach my $weight (keys %weight) {
+    <TR>
+      <TD ALIGN="right"><% $weight{$weight} %> weight</TD>
+      <TD>
+        <INPUT TYPE="text" NAME="<% $weight %>" SIZE=6 VALUE=<% $hashref->{$weight} || 0 %>>
+      </TD>
+    </TR>
+% }
+</TABLE>
+
+</TD><TD VALIGN="top">
+
+% if ( $cgi->param('clone') ) {
+
+    <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 {
+
+    Reseller information
+    <% ntable("#cccccc", 2) %>
+      <TR>
+        <TD ALIGN="right"><% 'Agent Types' %></TD>
+        <TD>
+          <% include( '/elements/select-table.html',
+                      'element_name' => 'agent_type',
+                      'table'        => 'agent_type',
+                      'name_col'     => 'atype',
+                      'value'        => \@agent_type,
+                      'multiple'     =>  '1',
+                      'element_etc'  => 'size="10"',
+                    )
+          %>
+        </TD>
+      </TR>
+    </TABLE>
+
+% }
+
+</TD></TR></TABLE>
+
+%my $thead =  "\n\n". ntable('#cccccc', 2).
+%             '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>'.
+%             '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Primary</FONT></TH>'.
+%             '<TH BGCOLOR="#dcdcdc">Service</TH></TR>';
+
+<BR><BR>Services included
+<% itable('', 4, 1) %><TR><TD VALIGN="top">
+<% $thead %>
+%
+%
+%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 @fixups = ();
+%my $count = 0;
+%my $columns = 3;
+%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=4 MAXLENGTH=3 VALUE="<% $quan %>">
+    </TD>
+   
+    <TD>
+      <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>
+% foreach my $f ( qw( clone pkgnum ) ) { #safe, these were untained in %init 
+    <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $cgi->param($f) %>">
+% }
+
+<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $part_pkg->pkgpart %>">
+%
+%
+%# prolly should be in database
+%tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+%
+%my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
+%                    split("\n", ($clone_part_pkg||$part_pkg)->plandata );
+%#warn join("\n", map { "$_: $plandata{$_}" } keys %plandata ). "\n";
+%
+%tie my %options, 'Tie::IxHash', map { $_=>$plans{$_}->{'name'} } keys %plans;
+%
+%#my @form_select = ('classnum');
+%#if ( $conf->exists('enable_taxclasses') ) {
+%#  push @form_select, 'taxclass';
+%#} else {
+%#  push @fixups, 'taxclass'; #hidden
+%#}
+%my @form_elements = ( 'classnum', 'taxclass', 'agent_type' );
+%
+%my @form_radio = ( 'pkg_svc_primary' );
+%
+%tie my %freq, 'Tie::IxHash', %{FS::part_pkg->freqs_href()};
+%if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) {
+%  delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq;
+%}
+%
+%#this should be replaced by /elements/selectlayers.html
+%my $widget = new HTML::Widgets::SelectLayers(
+%  'selected_layer' => $part_pkg->plan,
+%  'options'        => \%options,
+%  'form_name'      => 'dummy',
+%  'form_action'    => 'process/part_pkg.cgi',
+%  'form_elements'  => \@form_elements,
+%  'form_text'      => [ qw(pkg comment promo_code clone pkgnum pkgpart),
+%                        qw(pay_weight credit_weight), #keys(%weight),
+%                        @fixups,
+%                      ],
+%  'form_checkbox'  => [ qw(setuptax recurtax disabled) ],
+%  'form_radio'     => \@form_radio,
+%  'layer_callback' => sub {
+%    my $layer = shift;
+%    my $html = qq!<INPUT TYPE="hidden" NAME="plan" VALUE="$layer">!.
+%               ntable("#cccccc",2);
+%    $html .= '
+%      <TR>
+%        <TD ALIGN="right">Recurring fee frequency </TD>
+%        <TD><SELECT NAME="freq">
+%    ';
+%
+%    my @freq = keys %freq;
+%    @freq = grep { /^\d+$/ } @freq
+%      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'};
+%    foreach my $field ( exists($plans{$layer}->{'fieldorder'})
+%                          ? @{$plans{$layer}->{'fieldorder'}}
+%                          : keys %{ $href }
+%                      ) {
+%
+%      $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>';
+%
+%      my $format = sub { shift };
+%      $format = $href->{$field}{'format'} if exists($href->{$field}{'format'});
+%      if ( ! exists($href->{$field}{'type'}) ) {
+%        $html .= qq!<INPUT TYPE="text" NAME="$field" VALUE="!.
+%                 ( exists($plandata{$field})
+%                     ? &$format($plandata{$field})
+%                     : $href->{$field}{'default'} ).
+%                 qq!" onChange="fchanged(this)">!;
+%      } elsif ( $href->{$field}{'type'} eq 'checkbox' ) {
+%        $html .= qq!<INPUT TYPE="checkbox" NAME="$field" VALUE=1 !.
+%                 ( exists($plandata{$field}) && $plandata{$field}
+%                   ? ' CHECKED'
+%                   : ''
+%                 ). '>';
+%      } elsif ( $href->{$field}{'type'} =~ /^select/ ) {
+%        $html .= '<SELECT';
+%        $html .= ' MULTIPLE'
+%          if $href->{$field}{'type'} eq 'select_multiple';
+%        $html .= qq! NAME="$field" onChange="fchanged(this)">!;
+%
+%        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"!.
+%                     (  $plandata{$field} =~ /(^|, *)$value *(,|$)/
+%                          ? ' SELECTED'
+%                          : ''
+%                     ).
+%                     '>'. $record->getfield($href->{$field}{'select_label'});
+%          }
+%        } elsif ( $href->{$field}{'select_options'} ) {
+%          foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) {
+%            my $value = $href->{$field}{'select_options'}{$key};
+%            $html .= qq!<OPTION VALUE="$key"!.
+%                     ( $plandata{$field} =~ /(^|, *)$value *(,|$)/
+%                         ? ' SELECTED'
+%                         : ''
+%                     ).
+%                     '>'. $value;
+%          }
+%
+%        } else {
+%          $html .= '<font color="#ff0000">warning: '.
+%                   "don't know how to retreive options for $field select field".
+%                   '</font>';
+%        }
+%        $html .= '</SELECT>';
+%      }
+%
+%      $html .= '</TD></TR>';
+%    }
+%    $html .= '</TABLE>';
+%
+%    $html .= '<INPUT TYPE="hidden" NAME="plandata" VALUE="'.
+%             join(',', keys %{ $href } ). '">'.
+%             '<BR><BR>';
+%             
+%    $html .= '<INPUT TYPE="submit" VALUE="'.
+%               ( $action eq 'Custom'
+%                   ? 'Customize package'
+%                   : ( $hashref->{pkgpart} ? "Apply changes" : "Add package" )
+%               ).
+%             '" onClick="fchanged(this)">';
+%
+%    $html;
+%
+%  },
+%);
+%
+%
+
+
+<BR><BR>Price plan <% $widget->html %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {
+  $cgi->param('clone', $1);
+} else {
+  $cgi->param('clone', '');
+}
+if ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+  $cgi->param('pkgnum', $1);
+} else {
+  $cgi->param('pkgnum', '');
+}
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+  unless $curuser->access_right('Edit package definitions')
+      || $curuser->access_right('Edit global package definitions')
+      || ( $cgi->param('pkgnum') && $curuser->access_right('Customize customer package') );
+
+my ($query) = $cgi->keywords;
+
+my $conf = new FS::Conf; 
+my $part_pkg = '';
+my @agent_type = ();
+my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+if ( $cgi->param('error') ) {
+  $part_pkg = new FS::part_pkg ( {
+    map { $_, scalar($cgi->param($_)) } fields('part_pkg')
+  } );
+  (@agent_type) = $cgi->param('agent_type');
+}
+
+my $action = '';
+my $clone_part_pkg = '';
+my $pkgpart = '';
+if ( $cgi->param('clone') ) {
+  $pkgpart = $cgi->param('clone');
+  #$action = 'Custom Pricing';
+  $action = 'Custom';
+  $clone_part_pkg= qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } );
+  $part_pkg ||= $clone_part_pkg->clone;
+  $part_pkg->disabled('Y') unless $cgi->param('error');
+} elsif ( $query && $query =~ /^(\d+)$/ ) {
+  (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1})
+    unless $part_pkg;
+  $part_pkg ||= qsearchs('part_pkg',{'pkgpart'=>$1});
+  $pkgpart = $part_pkg->pkgpart;
+} else {
+  unless ( $part_pkg ) {
+    $part_pkg = new FS::part_pkg {};
+    $part_pkg->plan('flat');
+    @agent_type = @all_agent_types if $conf->exists('agent_defaultpkg');
+      
+  }
+}
+unless ( $part_pkg->plan ) { #backwards-compat
+  $part_pkg->plan('flat');
+  $part_pkg->plandata("setup_fee=". $part_pkg->setup. "\n".
+                      "recur_fee=". $part_pkg->recur. "\n");
+}
+$action ||= $part_pkg->pkgpart ? 'Edit' : 'Add';
+my $hashref = $part_pkg->hashref;
+
+</%init>
diff --git a/httemplate/edit/part_pkg_taxclass.html b/httemplate/edit/part_pkg_taxclass.html
new file mode 100644 (file)
index 0000000..e767057
--- /dev/null
@@ -0,0 +1,32 @@
+<% include('/elements/header.html', "$action taxclass") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/part_pkg_taxclass.html" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="taxclassnum" VALUE="">
+
+Tax class <INPUT TYPE="text" NAME="taxclass" VALUE="<% $taxclass |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 = '';
+if ( $cgi->param('error') ) {
+  $taxclass = $cgi->param('taxclass');
+}
+
+my $action = 'Add';
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html
new file mode 100755 (executable)
index 0000000..daf8773
--- /dev/null
@@ -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 (executable)
index 0000000..4b8a240
--- /dev/null
@@ -0,0 +1,360 @@
+<% 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_www - Virtual domain website
+    <LI>svc_broadband - Broadband/High-speed Internet service (always-on)
+    <LI>svc_phone - Customer phone numbers
+    <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} }, 
+%           },
+%# need to template-ize httemplate/edit/svc_* first
+%#    '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" COLSPAN=2>Modifier</TH>'.
+%               '</TR>';
+%
+%      my $bgcolor1 = '#eeeeee';
+%      my $bgcolor2 = '#ffffff';
+%      my $bgcolor;
+%
+%      #yucky kludge
+%      my @fields = defined( dbdef->table($layer) )
+%                      ? grep { $_ ne 'svcnum' } fields($layer)
+%                      : ();
+%      push @fields, 'usergroup' if $layer eq 'svc_acct'; #kludge
+%      $part_svc->svcpart($clone) if $clone; #haha, undone below
+%
+%
+%      foreach my $field (@fields) {
+%
+%        #my $def = $defs{$layer}{$field};
+%        my $def = FS::part_svc->svc_table_fields($layer)->{$field};
+%        my $label = $def->{'def_label'} || $def->{'label'};
+%        my $formatter = $def->{'format'} || sub { shift };
+%        my $part_svc_column = $part_svc->part_svc_column($field);
+%        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 CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!.
+%                 ( $label || $field ).
+%                 "</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 ) {
+%
+%            #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 '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});
+%              $html .= qq!<OPTION VALUE="$rvalue"!.
+%                  (grep(/^$rvalue$/, split(',',$value)) ? ' SELECTED>' : '>' ).
+%                  $record->getfield($def->{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 '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 'disabled' ) {
+%
+%          $html .=
+%            qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!;
+%
+%        } else {
+%
+%          $html .= '<font color="#ff0000">unknown type'. $def->{type};
+%
+%        }
+%
+%        $html .= "</TD></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 (file)
index 0000000..04ba9b0
--- /dev/null
@@ -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 (file)
index 0000000..e3893cf
--- /dev/null
@@ -0,0 +1,132 @@
+<% include("/elements/header.html","$action Payment gateway", menubar(
+  'View all payment gateways' => $p. 'browse/payment_gateway.html',
+)) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/payment_gateway.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="gatewaynum" VALUE="<% $payment_gateway->gatewaynum %>">
+Gateway #<% $payment_gateway->gatewaynum || "(NEW)" %>
+
+<% ntable('#cccccc', 2, '') %>
+
+<TR>
+  <TH ALIGN="right">Gateway: </TH>
+  <TD>
+% if ( $payment_gateway->gatewaynum ) { 
+
+
+      <% $payment_gateway->gateway_module %>
+      <INPUT TYPE="hidden" NAME="gateway_module" VALUE="<% $payment_gateway->gateway_module %>">
+% } else { 
+
+
+      <SELECT NAME="gateway_module" SIZE=1>
+% foreach my $module ( qw(
+%             2CheckOut
+%             AuthorizeNet
+%             BankOfAmerica
+%             Beanstream
+%             Capstone
+%             Cardstream
+%             CashCow
+%             CyberSource
+%             eSec
+%             eSelectPlus
+%             Exact
+%             iAuthorizer
+%             IPaymentTPG
+%             Jettis
+%             LinkPoint
+%             MerchantCommerce
+%             Network1Financial
+%             OCV
+%             OpenECHO
+%             PayConnect
+%             PayflowPro
+%             PaymentsGateway
+%             PXPost
+%             SecureHostingUPG
+%             Skipjack
+%             StGeorge
+%             SurePay
+%             TCLink
+%             TransactionCentral
+%             TransFirsteLink
+%             VirtualNet
+%           ) ) {
+%        
+
+          <OPTION VALUE="<% $module %>"><% $module %>
+% } 
+
+      </SELECT>
+% } 
+
+
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">Username: </TH>
+  <TD><INPUT TYPE="text" NAME="gateway_username" VALUE="<% $payment_gateway->gateway_username %>"></TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">Password: </TH>
+  <TD><INPUT TYPE="text" NAME="gateway_password" VALUE="<% $payment_gateway->gateway_password %>"></TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">Action: </TH>
+  <TD>
+    <SELECT NAME="gateway_action" SIZE=1>
+% foreach my $action ( 
+%                              'Normal Authorization',
+%                              'Authorization Only',
+%                              'Authorization Only, Post Authorization',
+%                            ) {
+%      
+
+        <OPTION VALUE="<% $action %>"<% $action eq $payment_gateway->gateway_action ? ' SELECTED' : '' %>><% $action %>
+% } 
+
+    </SELECT>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">Options: (Name/Value pairs, one element per line)</TH>
+  <TD>
+    <TEXTAREA ROWS="5" NAME="gateway_options"><% join("\r", $payment_gateway->options ) %></TEXTAREA>
+  </TD>
+</TR>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="<% $payment_gateway->gatewaynum ? "Apply changes" : "Add gateway" %>">
+    </FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $payment_gateway;
+if ( $cgi->param('error') ) {
+  $payment_gateway = new FS::payment_gateway ( {
+    map { $_, scalar($cgi->param($_)) } fields('payment_gateway')
+  } );
+} elsif ( $cgi->keywords ) {
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/;
+  $payment_gateway = qsearchs( 'payment_gateway', { 'gatewaynum' => $1 } );
+} else { #adding
+  $payment_gateway = new FS::payment_gateway {};
+}
+my $action = $payment_gateway->gatewaynum ? 'Edit' : 'Add';
+#my $hashref = $payment_gateway->hashref;
+
+</%init>
diff --git a/httemplate/edit/pkg_class.html b/httemplate/edit/pkg_class.html
new file mode 100644 (file)
index 0000000..eddbfc1
--- /dev/null
@@ -0,0 +1,22 @@
+<% include( 'elements/edit.html',
+              'name'   => 'Package Class',
+              'table'  => 'pkg_class',
+              'fields' => [
+                            'classname',
+                            { field=>'disabled', type=>'checkbox', value=>'Y', },
+                          ],
+              'labels' => { 
+                            'classnum'  => 'Class number',
+                            'classname' => '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/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi
new file mode 100644 (file)
index 0000000..9e1c30b
--- /dev/null
@@ -0,0 +1,110 @@
+<% 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 = ''; }">
+
+<SELECT NAME="type">
+% foreach (qw(alpha alphanumeric numeric)) { 
+  <OPTION<% $cgi->param('type') eq $_ ? ' SELECTED' : '' %>><% $_ %>
+% } 
+</SELECT>
+
+prepaid cards
+
+<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',
+  1000       => 'Kbytes',
+  1000000    => 'Mbytes',
+  1000000000 => 'Gbytes',
+;
+
+$cgi->param('multiplier',     '60')      unless $cgi->param('multiplier');
+$cgi->param('upmultiplier',   '1000000') unless $cgi->param('upmultiplier');
+$cgi->param('downmultiplier', '1000000') unless $cgi->param('downmultiplier');
+$cgi->param('totalmultiplier','1000000') 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 (executable)
index 0000000..ebcb7e4
--- /dev/null
@@ -0,0 +1,36 @@
+%if ( $error ) {
+%  $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string ) %>
+%} else { 
+%  my $custnum = $new->custnum;
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum#cust_pkg$pkgnum" ) %>
+%}
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->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{'setup'} = $cgi->param('setup') ? str2time($cgi->param('setup')) : '';
+$hash{'bill'} = $cgi->param('bill') ? str2time($cgi->param('bill')) : '';
+$hash{'last_bill'} =
+  $cgi->param('last_bill') ? str2time($cgi->param('last_bill')) : '';
+$hash{'adjourn'} = $cgi->param('adjourn') ? str2time($cgi->param('adjourn')) : '';
+$hash{'expire'} = $cgi->param('expire') ? str2time($cgi->param('expire')) : '';
+
+my $new;
+my $error;
+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
+   )
+{
+  $error = '_bill_areyousure';
+} 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 (file)
index 0000000..581b50f
--- /dev/null
@@ -0,0 +1,16 @@
+<% 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',
+               },
+           )
+%>
diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html
new file mode 100644 (file)
index 0000000..ca6bb60
--- /dev/null
@@ -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 (executable)
index 0000000..85780c6
--- /dev/null
@@ -0,0 +1,21 @@
+%
+%
+%my $error = '';
+%my $ip_gateway = $cgi->param('ip_gateway');
+%my $ip_netmask = $cgi->param('ip_netmask');
+%
+%my $new = new FS::addr_block {
+%    ip_gateway => $ip_gateway,
+%    ip_netmask => $ip_netmask,
+%    routernum  => 0 };
+%
+%$error = $new->insert;
+%
+%if ( $error ) {
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string );
+%} else { 
+%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+%} 
+%
+
diff --git a/httemplate/edit/process/addr_block/allocate.cgi b/httemplate/edit/process/addr_block/allocate.cgi
new file mode 100755 (executable)
index 0000000..a94c032
--- /dev/null
@@ -0,0 +1,26 @@
+%
+%my $error = '';
+%my $blocknum = $cgi->param('blocknum');
+%my $routernum = $cgi->param('routernum');
+%
+%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+%my $router = qsearchs('router', { routernum => $routernum });
+%
+%if($addr_block) {
+%  if ($router) {
+%    $error = $addr_block->allocate($router);
+%  } else {
+%    $error = "Cannot find router with routernum $routernum";
+%  }
+%} else {
+%  $error = "Cannot find block with blocknum $blocknum";
+%}
+%
+%if ( $error ) {
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string);
+%} else { 
+%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+%}
+%
+
diff --git a/httemplate/edit/process/addr_block/deallocate.cgi b/httemplate/edit/process/addr_block/deallocate.cgi
new file mode 100755 (executable)
index 0000000..494c19f
--- /dev/null
@@ -0,0 +1,25 @@
+%
+%my $error = '';
+%my $blocknum = $cgi->param('blocknum');
+%
+%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+%
+%if($addr_block) {
+%  my $router = $addr_block->router;
+%  if ($router) {
+%    $error = $addr_block->deallocate($router);
+%  } else {
+%    $error = "Block is not allocated to a router";
+%  }
+%} else {
+%  $error = "Cannot find block with blocknum $blocknum";
+%}
+%
+%if ( $error ) {
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string);
+%} else { 
+%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+%}
+%
+
diff --git a/httemplate/edit/process/addr_block/split.cgi b/httemplate/edit/process/addr_block/split.cgi
new file mode 100755 (executable)
index 0000000..617c3f8
--- /dev/null
@@ -0,0 +1,20 @@
+%
+%my $error = '';
+%my $blocknum = $cgi->param('blocknum');
+%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+%
+%if ( $addr_block) {
+%  $error = $addr_block->split_block;
+%} else {
+%  $error = "Unknown blocknum: $blocknum";
+%}
+%
+%
+%if ( $error ) {
+%  $cgi->param('error', $error);
+%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string );
+%} else { 
+%  print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+%} 
+%
+
diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi
new file mode 100755 (executable)
index 0000000..ad550cc
--- /dev/null
@@ -0,0 +1,30 @@
+%if ( $error ) {
+%  $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ) %>
+%} else { 
+<% $cgi->redirect(popurl(3). "browse/agent.cgi") %>
+%}
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agentnum = $cgi->param('agentnum');
+
+my $old = qsearchs('agent',{'agentnum'=>$agentnum}) if $agentnum;
+
+my $new = new FS::agent ( {
+  map {
+    $_, scalar($cgi->param($_));
+  } fields('agent')
+} );
+
+my $error;
+if ( $agentnum ) {
+  $error=$new->replace($old);
+} else {
+  $error=$new->insert;
+  $agentnum=$new->getfield('agentnum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/agent_payment_gateway.html b/httemplate/edit/process/agent_payment_gateway.html
new file mode 100644 (file)
index 0000000..5b5fd94
--- /dev/null
@@ -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 (executable)
index 0000000..ad5963b
--- /dev/null
@@ -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_svc.cgi b/httemplate/edit/process/bulk-cust_svc.cgi
new file mode 100644 (file)
index 0000000..313b061
--- /dev/null
@@ -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/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi
new file mode 100755 (executable)
index 0000000..e2f89f1
--- /dev/null
@@ -0,0 +1,49 @@
+%if ( $error ) {
+%  $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ) %>
+%} else {
+<% header('Payment application sucessful') %>
+  <SCRIPT TYPE="text/javascript">
+    window.top.location.reload();
+  </SCRIPT>
+  </BODY>
+  </HTML>
+% } 
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+$cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!";
+my $paynum = $1;
+
+my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } )
+  or die "No such paynum";
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pay->custnum } )
+  or die "Bogus credit:  not attached to customer";
+
+my $custnum = $cust_main->custnum;
+
+my $new;
+if ($cgi->param('invnum') =~ /^Refund$/) {
+  $new = new FS::cust_refund ( {
+    'reason'  => 'Refunding payment', #enter reason in UI
+    'refund'  => $cgi->param('amount'),
+    'payby'   => 'BILL',
+    #'_date'   => $cgi->param('_date'),
+    'payinfo' => 'Cash', #enter payinfo in UI
+    'paynum' => $paynum,
+  } );
+} else {
+  $new = new FS::cust_bill_pay ( {
+    map {
+      $_, scalar($cgi->param($_));
+    #} qw(custnum _date amount invnum)
+    } fields('cust_bill_pay')
+  } );
+}
+
+my $error = $new->insert;
+
+</%init>
diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi
new file mode 100755 (executable)
index 0000000..8715ad6
--- /dev/null
@@ -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 (executable)
index 0000000..17f9fcb
--- /dev/null
@@ -0,0 +1,50 @@
+%if ( $error ) {
+%  $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ) %>
+%} else {
+<% header('Credit application sucessful') %>
+  <SCRIPT TYPE="text/javascript">
+    window.top.location.reload();
+  </SCRIPT>
+  </BODY>
+  </HTML>
+% } 
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+$cgi->param('crednum') =~ /^(\d*)$/ or die "Illegal crednum!";
+my $crednum = $1;
+
+my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } )
+  or die "No such crednum";
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_credit->custnum } )
+  or die "Bogus credit:  not attached to customer";
+
+my $custnum = $cust_main->custnum;
+
+my $new;
+if ($cgi->param('invnum') =~ /^Refund$/) {
+  $new = new FS::cust_refund ( {
+    'reason'  => ( $cust_credit->reason || 'refund from credit' ),
+    'refund'  => $cgi->param('amount'),
+    'payby'   => 'BILL',
+    #'_date'   => $cgi->param('_date'),
+    #'payinfo' => 'Cash',
+    'payinfo' => 'Refund',
+    'crednum' => $crednum,
+  } );
+} else {
+  $new = new FS::cust_credit_bill ( {
+    map {
+      $_, scalar($cgi->param($_));
+    #} qw(custnum _date amount invnum)
+    } fields('cust_credit_bill')
+  } );
+}
+
+my $error = $new->insert;
+
+</%init>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
new file mode 100755 (executable)
index 0000000..b0c9e3e
--- /dev/null
@@ -0,0 +1,203 @@
+% 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 $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 $payby = $cgi->param('select'); # XXX key
+
+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')
+} );
+
+ delete( $new->hashref->{'agent_custid'} )
+   unless $new->hashref->{'agent_custid'};
+
+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
+  );
+}
+
+if ( $cgi->param('birthdate') && $cgi->param('birthdate') =~ /^([ 0-9\-\/]{0,10})$/) {
+  my $conf = new FS::Conf;
+  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($1);
+  if ($dt) {
+    $new->setfield('birthdate', $dt->epoch);
+    $cgi->param('birthdate', $dt->epoch);
+  } else {
+#    $error ||= $cgi->param('birthdate') . " is an invalid birthdate:" . $parser->errmsg;
+    $error ||= "Invalid birthdate: " . $cgi->param('birthdate') . ".";
+    $cgi->param('birthdate', '');
+  }
+}
+
+$new->setfield('paid', $cgi->param('paid') )
+  if $cgi->param('paid');
+
+#perhaps this stuff should go to cust_main.pm
+my $cust_pkg = '';
+my $svc_acct = '';
+if ( $new->custnum eq '' ) {
+
+  if ( $cgi->param('pkgpart_svcpart') ) {
+    my $x = $cgi->param('pkgpart_svcpart');
+    $x =~ /^(\d+)_(\d+)$/ or die "illegal pkgpart_svcpart $x\n";
+    my($pkgpart, $svcpart) = ($1, $2);
+    #false laziness: copied from FS::cust_pkg::order (which should become a
+    #FS::cust_main method)
+    my(%part_pkg);
+    # generate %part_pkg
+    # $part_pkg{$pkgpart} is true iff $custnum may purchase $pkgpart
+    my $agent = qsearchs('agent',{'agentnum'=> $new->agentnum });
+       #my($type_pkgs);
+       #foreach $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) {
+       #  my($pkgpart)=$type_pkgs->pkgpart;
+       #  $part_pkg{$pkgpart}++;
+       #}
+    # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart
+    my $pkgpart_href = $agent->pkgpart_hashref;
+    #eslaf
+
+    # this should wind up in FS::cust_pkg!
+    $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't ".
+               "purchase pkgpart ". $pkgpart
+      #unless $part_pkg{ $pkgpart };
+      unless $pkgpart_href->{ $pkgpart };
+
+    $cust_pkg = new FS::cust_pkg ( {
+      #later         'custnum' => $custnum,
+      'pkgpart' => $pkgpart,
+    } );
+    #$error ||= $cust_pkg->check;
+
+    #$cust_svc = new FS::cust_svc ( { 'svcpart' => $svcpart } );
+
+    #$error ||= $cust_svc->check;
+
+    my %svc_acct = (
+                     'svcpart'   => $svcpart,
+                     'username'  => $cgi->param('username'),
+                     '_password' => $cgi->param('_password'),
+                     'popnum'    => $cgi->param('popnum'),
+                   );
+    $svc_acct{'domsvc'} = $cgi->param('domsvc')
+      if $cgi->param('domsvc');
+
+    $svc_acct = new FS::svc_acct \%svc_acct;
+
+    #and just in case you were silly
+    $svc_acct->svcpart($svcpart);
+    $svc_acct->username($cgi->param('username'));
+    $svc_acct->_password($cgi->param('_password'));
+    $svc_acct->popnum($cgi->param('popnum'));
+
+    #$error ||= $svc_acct->check;
+
+  } elsif ( $cgi->param('username') ) { #good thing to catch
+    $error = "Can't assign username without a package!";
+  }
+
+  use Tie::RefHash;
+  tie my %hash, 'Tie::RefHash';
+  %hash = ( $cust_pkg => [ $svc_acct ] ) if $cust_pkg;
+  $error ||= $new->insert( \%hash, \@invoicing_list );
+
+  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|CHEK|DCHK)$/ && $new->payinfo =~ /xx/) {
+    $new->payinfo($old->payinfo);
+  }
+
+  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);
+
+  warn "$me returned from replace" if $DEBUG;
+  
+}
+
+</%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 (executable)
index 0000000..a917825
--- /dev/null
@@ -0,0 +1,44 @@
+%
+%
+%my($query) = $cgi->keywords;
+%$query =~ /^(\d+)$/ or die "Illegal taxnum!";
+%my $taxnum = $1;
+%my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } )
+%  or die "Unknown taxnum $taxnum";
+%
+%#really should do this in a .pm & start transaction
+%
+%foreach my $delete ( qsearch('cust_main_county', {
+%                    'country' => $cust_main_county->country,
+%                    'state' => $cust_main_county->state  
+%                 } ) ) {
+%#  unless ( qsearch('cust_main',{
+%#    'state'  => $cust_main_county->getfield('state'),
+%#    'county' => $cust_main_county->getfield('county'),
+%#    'country' =>  $cust_main_county->getfield('country'),
+%#  } ) ) {
+%    my $error = $delete->delete;
+%    die $error if $error;
+%#  } else {
+%    #should really fix the $cust_main record
+%#  }
+%
+%}
+%
+%$cust_main_county->taxnum('');
+%$cust_main_county->county('');
+%my $error = $cust_main_county->insert;
+%die $error if $error;
+%
+%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi");
+%
+%
+<%init>
+
+#this isn't actually linked from anywhere just now, but it will be again soon
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+
+</%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 (executable)
index 0000000..758345e
--- /dev/null
@@ -0,0 +1,78 @@
+<% 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',$_);
+  } else {
+    $new->setfield('county',$_);
+  }
+  my $error = $new->insert;
+  die $error if $error;
+}
+
+unless ( qsearch( 'cust_main', {
+                                 'state'  => $cust_main_county->state,
+                                 'county' => $cust_main_county->county,
+                                 'country' =>  $cust_main_county->country,
+                               } )
+         || ! @expansion
+) {
+  my $error = $cust_main_county->delete;
+  die $error if $error;
+}
+
+if ( $cgi->param('taxclass') ) {
+  print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi?".
+                         'state='.   uri_escape($cust_main_county->state  ).';'.
+                         'county='.  uri_escape($cust_main_county->county ).';'.
+                         '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 (file)
index 0000000..cb56166
--- /dev/null
@@ -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 (executable)
index 0000000..9689ca6
--- /dev/null
@@ -0,0 +1,60 @@
+%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">
+      parent.cust_main_notes.location.reload();
+      try{parent.cust_main_notes.cClick()}
+      catch(err){}
+      try{parent.cClick()}
+      catch(err){}
+    </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 $otaker = $FS::CurrentUser::CurrentUser->name;
+$otaker = $FS::CurrentUser::CurrentUser->username
+  if ($otaker eq "User, Legacy");
+
+my $new = new FS::cust_main_note ( {
+  notenum  => $notenum,
+  custnum  => $custnum,
+  _date    => time,
+  otaker   => $otaker,
+  comments =>  $cgi->param('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 (executable)
index 0000000..647f6fc
--- /dev/null
@@ -0,0 +1,55 @@
+%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;
+%  }
+%  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 = str2time($cgi->param('_date'));
+
+my $new = new FS::cust_pay ( {
+  $field => $linknum,
+  _date  => $_date,
+  map {
+    $_, scalar($cgi->param($_));
+  } qw(paid payby payinfo paybatch)
+  #} fields('cust_pay')
+} );
+
+my $error = $new->insert( 'manual' => 1 );
+
+</%init>
diff --git a/httemplate/edit/process/cust_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi
new file mode 100755 (executable)
index 0000000..bdade32
--- /dev/null
@@ -0,0 +1,68 @@
+% if ($error) {
+%   $cgi->param('error', $error);
+%   $cgi->redirect(popurl(3). $error_redirect. '?'. $cgi->query_string );
+% } elsif ( $action eq 'change' ) {
+
+    <% header("Package changed") %>
+      <SCRIPT TYPE="text/javascript">
+        window.top.location.reload();
+      </SCRIPT>
+    </BODY>
+    </HTML>
+
+% } elsif ( $action eq 'bulk' ) {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+% } else {
+%   die "guru exception #5: action is neither change nor bulk!";
+% }
+<%init>
+
+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 $curuser = $FS::CurrentUser::CurrentUser;
+
+my( $action, $error_redirect );
+my @pkgparts = ();
+if ( $cgi->param('new_pkgpart') =~ /^(\d+)$/ ) { #came from misc/change_pkg.cgi
+
+  $action = 'change';
+  $error_redirect = "misc/change_pkg.cgi";
+  @pkgparts = ($1);
+
+  die "access denied"
+    unless $curuser->access_right('Change customer package');
+
+} else { #came from edit/cust_pkg.cgi
+
+  $action = 'bulk';
+  $error_redirect = "edit/cust_pkg.cgi";
+
+  die "access denied"
+    unless $curuser->access_right('Bulk change customer packages');
+
+  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_refund.cgi b/httemplate/edit/process/cust_refund.cgi
new file mode 100755 (executable)
index 0000000..1a7a394
--- /dev/null
@@ -0,0 +1,43 @@
+%if ( $error ) {
+%  $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cust_refund.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+%}
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Refund payment');
+
+$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
+my $custnum = $1;
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+  or die "unknown custnum $custnum";
+
+my $error = '';
+if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) { 
+  my %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 {
+  die 'unimplemented';
+  #my $new = new FS::cust_refund ( {
+  #  map {
+  #    $_, scalar($cgi->param($_));
+  #  } ( fields('cust_refund'), '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 (file)
index 0000000..e22cbb2
--- /dev/null
@@ -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/domain_record.cgi b/httemplate/edit/process/domain_record.cgi
new file mode 100755 (executable)
index 0000000..2e427e4
--- /dev/null
@@ -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/elements/process.html b/httemplate/edit/process/elements/process.html
new file mode 100644 (file)
index 0000000..a671ca1
--- /dev/null
@@ -0,0 +1,225 @@
+<%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'
+   OR
+   'redirect'     => 'view/table.cgi?', # value of primary key is appended
+   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', ... ],
+
+   'process_m2m' => { 'link_table'   => 'link_table_name',
+                      'target_table' => 'target_table_name',
+                    },
+   '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',
+
+
+                       },
+
+   #supplies arguments to insert() and replace()
+   # for use with tables that are FS::option_Common
+   '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>
+
+%} elsif ( $opt{'redirect'} ) {
+%
+<% $cgi->redirect( $opt{'redirect'}. $pkeyvalue ) %>
+%
+%} else { 
+%
+<% $cgi->redirect( popurl(3). ($opt{viewall_dir}||'search'). "/$table.html" ) %>
+%}
+<%once>
+
+  my $me = 'process.html:';
+
+</%once>
+<%init>
+
+my(%opt) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#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 { $_ => scalar($cgi->param($_)) } @$fields;
+
+my $new = $class->new( \%hash );
+
+if ( $opt{'agent_virt'} ) {
+  die "illegal agentnum"
+    unless $curuser->agentnums_href->{$new->agentnum}
+        or $opt{'agent_null_right'}
+           && ! $new->agentnum
+           && $curuser->access_right($opt{'agent_null_right'});
+}
+
+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));
+  }
+}
+
+my $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'} ) {
+
+  if ( $opt{'debug'} ) {
+    warn "$me processing m2m:\n". Dumper( %{ $opt{'process_m2m'} },
+                                          'params' => scalar($cgi->Vars),
+                                        );
+  }
+
+  $error = $new->process_m2m( %{ $opt{'process_m2m'} },
+                              'params' => scalar($cgi->Vars),
+                            );
+}
+
+if ( !$error && $opt{'process_m2name'} ) {
+
+  if ( $opt{'debug'} ) {
+    warn "$me processing m2name:\n". Dumper( %{ $opt{'process_m2name'} },
+                                             'params' => scalar($cgi->Vars),
+                                           );
+  }
+
+  $error = $new->process_m2name( %{ $opt{'process_m2name'} },
+                                 '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 (file)
index 0000000..8e8c99a
--- /dev/null
@@ -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 (file)
index 0000000..6428763
--- /dev/null
@@ -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 (file)
index 0000000..dbf978e
--- /dev/null
@@ -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 (file)
index 0000000..757fa94
--- /dev/null
@@ -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("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 (file)
index 0000000..6c9371a
--- /dev/null
@@ -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/msgcat.cgi b/httemplate/edit/process/msgcat.cgi
new file mode 100644 (file)
index 0000000..7175fa2
--- /dev/null
@@ -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 (executable)
index 0000000..3534519
--- /dev/null
@@ -0,0 +1,92 @@
+%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){
+    my $new = new FS::part_bill_event ( {
+      map {
+        $_, scalar($cgi->param($_));
+      } fields('part_bill_event'),
+    } );
+    $new->setfield('reason', $rnum);
+
+    if ( $eventpart ) {
+      $error = $new->replace($old);
+    } else {
+      $error = $new->insert;
+      $eventpart = $new->getfield('eventpart');
+    }
+  }
+} 
+
+</%init>
diff --git a/httemplate/edit/process/part_event.html b/httemplate/edit/process/part_event.html
new file mode 100644 (file)
index 0000000..428025f
--- /dev/null
@@ -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 (file)
index 0000000..b5f82e8
--- /dev/null
@@ -0,0 +1,41 @@
+%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 $value = $cgi->param($_);
+  $value =~ s/\r\n/\n/g; #browsers? (textarea)
+  $_ => $value;
+} split(',', $cgi->param('options'));
+
+my $new = new FS::part_export ( {
+  map {
+    $_, scalar($cgi->param($_));
+  } fields('part_export')
+} );
+
+my $error;
+if ( $exportnum ) {
+  #warn $old;
+  #warn $exportnum;
+  #warn $new->machine;
+  $error = $new->replace($old,\%options);
+} else {
+  $error = $new->insert(\%options);
+#  $exportnum = $new->exportnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
new file mode 100755 (executable)
index 0000000..36debfc
--- /dev/null
@@ -0,0 +1,113 @@
+%if ( $error ) {
+%  $dbh->rollback if $oldAutoCommit;
+%  $cgi->param('error', $error );
+<% $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string ) %>
+%} elsif ( $custnum )  {
+%  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+%} else {
+%  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+<% $cgi->redirect(popurl(3). "browse/part_pkg.cgi") %>
+%}
+<%init>
+
+my $dbh = dbh;
+my $conf = new FS::Conf;
+
+my $pkgpart = $cgi->param('pkgpart');
+
+my $old = qsearchs('part_pkg',{'pkgpart'=>$pkgpart}) if $pkgpart;
+
+tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+my $href = $plans{$cgi->param('plan')}->{'fields'};
+
+#fixup plandata
+my $error;
+my $plandata = $cgi->param('plandata');
+my @plandata = split(',', $plandata);
+$cgi->param('plandata', 
+  join('', map { my $parser = sub { shift };
+                 $parser = $href->{$_}{parse} if exists($href->{$_}{parse});
+                 my $value = join(', ', &$parser($cgi->param($_)));
+                 my $check = $href->{$_}{check};
+                 if ( $check && ! &$check($value) ) {
+                   $value = join(', ', $cgi->param($_));
+                   $error ||= "Illegal ". ($href->{$_}{name}||$_). ": $value";
+                 }
+                 "$_=$value\n";
+               } @plandata )
+);
+
+foreach (qw( setuptax recurtax disabled )) {
+  $cgi->param($_, '') unless defined $cgi->param($_);
+}
+
+my @agents;
+foreach ($cgi->param('agent_type')) {
+  /^(\d+)$/;
+  push @agents, $1 if $1;
+}
+$error = "At least one agent type must be specified."
+  unless( scalar(@agents) ||
+          $cgi->param('clone') && $cgi->param('clone') =~ /^\d+$/ ||
+          !$pkgpart && $conf->exists('agent-defaultpkg')
+        );
+
+my $new = new FS::part_pkg ( {
+  map {
+    $_ => scalar($cgi->param($_));
+  } fields('part_pkg')
+} );
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+
+my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) }
+              map { $_->svcpart }
+              qsearch('part_svc', {} );
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $custnum = '';
+if ( $error ) {
+
+ # fall through
+
+} elsif ( $cgi->param('taxclass') eq '(select)' ) {
+
+  $error = 'Must select a tax class';
+
+} elsif ( $pkgpart ) {
+
+  die "access denied"
+    unless $curuser->access_right('Edit package definitions')
+        || $curuser->access_right('Edit global package definitions');
+
+  $error = $new->replace( $old,
+                          pkg_svc     => \%pkg_svc,
+                          primary_svc => scalar($cgi->param('pkg_svc_primary')),
+                        );
+} else {
+
+  die "access denied"
+    unless $curuser->access_right('Edit package definitions')
+        || $curuser->access_right('Edit global package definitions')
+        || ( $cgi->param('pkgnum') && $curuser->access_right('Customize customer package') );
+
+  $error = $new->insert(  pkg_svc     => \%pkg_svc,
+                          primary_svc => scalar($cgi->param('pkg_svc_primary')),
+                          cust_pkg    => $cgi->param('pkgnum'),
+                          custnum_ref => \$custnum,
+                       );
+  $pkgpart = $new->pkgpart;
+}
+
+unless ( $error || $conf->exists('agent_defaultpkg') ) {
+  my $error = $new->process_m2m(
+    'link_table'   => 'type_pkgs',
+    'target_table' => 'agent_type',
+    'params'       => \@agents,
+  );
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_pkg_taxclass.html b/httemplate/edit/process/part_pkg_taxclass.html
new file mode 100644 (file)
index 0000000..8f149bb
--- /dev/null
@@ -0,0 +1,53 @@
+% if ( $error ) {
+%  $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "part_pkg_taxclass.html?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/cust_main_county.cgi?taxclass=". uri_escape($part_pkg_taxclass->taxclass) ) %>
+%}
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $part_pkg_taxclass = new FS::part_pkg_taxclass {
+  'taxclass' => $cgi->param('taxclass'),
+};
+
+#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 part_pkg_taxclass...
+
+my $error = $part_pkg_taxclass->insert;
+
+unless ( $error ) {
+  #auto-add the new taxclass to any regions that have taxclasses already
+
+  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' => $part_pkg_taxclass->taxclass,
+      #exempt_amount
+      #taxname
+      #setuptax
+      #recurtax
+    };
+    $error = $cust_main_county->insert;
+    #last if $error;
+    die $error if $error;
+  }
+
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_referral.html b/httemplate/edit/process/part_referral.html
new file mode 100755 (executable)
index 0000000..40cbc97
--- /dev/null
@@ -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 (executable)
index 0000000..65de3fc
--- /dev/null
@@ -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 (file)
index 0000000..b16bc3d
--- /dev/null
@@ -0,0 +1,35 @@
+%if ( $error ) {
+%  $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "payment_gateway.html?". $cgi->query_string ) %>
+%} else { 
+<% $cgi->redirect(popurl(3). "browse/payment_gateway.html") %>
+%}
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $gatewaynum = $cgi->param('gatewaynum');
+
+my $old = qsearchs('payment_gateway',{'gatewaynum'=>$gatewaynum}) if $gatewaynum;
+
+my $new = new FS::payment_gateway ( {
+  map {
+    $_, scalar($cgi->param($_));
+  } fields('payment_gateway')
+} );
+
+my @options = split(/\r?\n/, $cgi->param('gateway_options') );
+pop @options
+  if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
+my %options = @options;
+
+my $error;
+if ( $gatewaynum ) {
+  $error=$new->replace($old, \%options);
+} else {
+  $error=$new->insert(\%options);
+  $gatewaynum=$new->getfield('gatewaynum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/pkg_class.html b/httemplate/edit/process/pkg_class.html
new file mode 100644 (file)
index 0000000..b196df3
--- /dev/null
@@ -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 (file)
index 0000000..8f2eb2b
--- /dev/null
@@ -0,0 +1,62 @@
+%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')), 
+                                        $hashref
+                                      );
+
+</%init>
diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi
new file mode 100644 (file)
index 0000000..22f9685
--- /dev/null
@@ -0,0 +1,50 @@
+% 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 $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;
+
+if ( $param->{'taxclass'} eq '(select)' ) {
+  $error .= "Must select a tax class.  ";
+}
+
+unless ( $error ) {
+  my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+    or $error .= "Unknown customer number $custnum.  ";
+
+  $error ||= $cust_main->charge( {
+    'amount'     => $amount,
+    'pkg'        => scalar($cgi->param('pkg')),
+    'taxclass'   => scalar($cgi->param('taxclass')),
+    '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 (file)
index 0000000..6b65653
--- /dev/null
@@ -0,0 +1,33 @@
+%if ($error) {
+%  $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'misc/order_pkg.html?'. $cgi->query_string ) %>
+%} else {
+%  my $frag = "cust_pkg". $cust_pkg[0]->pkgnum;
+<% 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?keywords=$custnum;fragment=$frag#$frag" %>';
+
+  </SCRIPT>
+
+  </BODY></HTML>
+%}
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Order customer package');
+
+#untaint custnum
+$cgi->param('custnum') =~ /^(\d+)$/
+  or die 'illegal custnum '. $cgi->param('custnum');
+my $custnum = $1;
+$cgi->param('pkgpart') =~ /^(\d+)$/
+  or die 'illegal pkgpart '. $cgi->param('pkgpart');
+my $pkgpart = $1;
+
+my @cust_pkg = ();
+my $error = FS::cust_pkg::order($custnum, [ $pkgpart ], [], \@cust_pkg, [ $cgi->param('refnum') ] );
+
+</%init>
diff --git a/httemplate/edit/process/rate.cgi b/httemplate/edit/process/rate.cgi
new file mode 100755 (executable)
index 0000000..48d9322
--- /dev/null
@@ -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 (file)
index 0000000..6200d61
--- /dev/null
@@ -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 (executable)
index 0000000..3933ff3
--- /dev/null
@@ -0,0 +1,53 @@
+%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 {
+                        new FS::rate_prefix {
+                          'countrycode' => $countrycode,
+                          'npa'         => $_,
+                        }
+                      } @npa;
+
+my @dest_detail = map {
+  my $ratenum = $_->ratenum;
+  new FS::rate_detail {
+    'ratenum'  => $ratenum,
+    map { $_ => $cgi->param("$_$ratenum") }
+        qw( min_included min_charge sec_granularity )
+  };
+} 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 (file)
index 0000000..cb79ed2
--- /dev/null
@@ -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 (file)
index 0000000..3172b27
--- /dev/null
@@ -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 (file)
index 0000000..035e10b
--- /dev/null
@@ -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 (file)
index 0000000..7e0baf7
--- /dev/null
@@ -0,0 +1,70 @@
+%local $FS::UID::AutoCommit=0;
+%
+%sub check {
+%  my $error = shift;
+%  if($error) {
+%    $cgi->param('error', $error);
+%    print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string);
+%    dbh->rollback;
+%    exit;
+%  }
+%}
+%
+%my $error = '';
+%my $routernum  = $cgi->param('routernum');
+%my $routername = $cgi->param('routername');
+%my $old = qsearchs('router', { routernum => $routernum });
+%my @old_psr;
+%
+%my $new = new FS::router {
+%  map {
+%    ($_, scalar($cgi->param($_)));
+%  } fields('router')
+%};
+%
+%if($old) {
+%  $error = $new->replace($old);
+%} else {
+%  $error = $new->insert;
+%  $routernum = $new->routernum;
+%}
+%
+%check($error);
+%
+%if ($old) {
+%  @old_psr = $old->part_svc_router;
+%  foreach my $psr (@old_psr) {
+%    if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') {
+%      # do nothing
+%    } else {
+%      $error = $psr->delete;
+%    }
+%  }
+%  check($error);
+%}
+%
+%foreach($cgi->param) {
+%  if($cgi->param($_) eq 'ON' and /^svcpart_(\d+)$/) {
+%    my $svcpart = $1;
+%    if(grep {$_->svcpart == $svcpart} @old_psr) {
+%      # do nothing
+%    } else {
+%      my $new_psr = new FS::part_svc_router { svcpart   => $svcpart,
+%                                              routernum => $routernum };
+%      $error = $new_psr->insert;
+%    }
+%    check($error);
+%  }
+%}
+%
+%
+%# Yay, everything worked!
+%dbh->commit or die dbh->errstr;
+%print $cgi->redirect(popurl(3). "browse/router.cgi");
+%
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/svc_Common.html b/httemplate/edit/process/svc_Common.html
new file mode 100644 (file)
index 0000000..cf5f01f
--- /dev/null
@@ -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 (executable)
index 0000000..0a89e25
--- /dev/null
@@ -0,0 +1,64 @@
+%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>
+
+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 passwd
+if ( $cgi->param('_password') eq '*HIDDEN*' ) {
+  die "fatal: no previous account to recall hidden password from!" unless $old;
+  $cgi->param('_password',$old->getfield('_password'));
+}
+
+#unmunge usergroup
+$cgi->param('usergroup', [ $cgi->param('radius_usergroup') ] );
+
+#unmunge bytecounts
+foreach (map { $_,$_."_threshold" } qw( upbytes downbytes totalbytes )) {
+  $cgi->param($_, FS::UI::bytecount::parse_bytecount($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;
+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 (executable)
index 0000000..75b89c8
--- /dev/null
@@ -0,0 +1,30 @@
+%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>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('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 (file)
index 0000000..8600da3
--- /dev/null
@@ -0,0 +1,38 @@
+%if ( $error ) {
+%  $cgi->param('error', $error);
+%  $cgi->param('ip_addr', $new->ip_addr);
+<% $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_broadband.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_broadband', { 'svcnum' => $svcnum } )
+    or die "fatal: can't find broadband service (svcnum $svcnum)!";
+} else {
+  $old = '';
+}
+
+my $new = new FS::svc_broadband ( {
+  map {
+    ($_, scalar($cgi->param($_)));
+  } ( fields('svc_broadband'), qw( pkgnum svcpart ) )
+} );
+
+my $error;
+if ( $svcnum ) {
+  $error = $new->replace($old);
+} else {
+  $error = $new->insert;
+  $svcnum = $new->svcnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_domain.cgi b/httemplate/edit/process/svc_domain.cgi
new file mode 100755 (executable)
index 0000000..9993a87
--- /dev/null
@@ -0,0 +1,33 @@
+%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;
+
+my $new = new FS::svc_domain ( {
+  map {
+    $_, scalar($cgi->param($_));
+  #} qw(svcnum pkgnum svcpart domain action purpose)
+  } ( fields('svc_domain'), qw( pkgnum svcpart action purpose ) )
+} );
+
+my $error = '';
+if ($cgi->param('svcnum')) {
+  $error="Can't modify a domain!";
+} else {
+  $error=$new->insert;
+  $svcnum=$new->svcnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_external.cgi b/httemplate/edit/process/svc_external.cgi
new file mode 100755 (executable)
index 0000000..673e5a5
--- /dev/null
@@ -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_forward.cgi b/httemplate/edit/process/svc_forward.cgi
new file mode 100755 (executable)
index 0000000..fffad84
--- /dev/null
@@ -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_phone.html b/httemplate/edit/process/svc_phone.html
new file mode 100644 (file)
index 0000000..27a703c
--- /dev/null
@@ -0,0 +1,10 @@
+<% include( 'elements/svc_Common.html',
+               'table'    => 'svc_phone',
+           )
+%>
+<%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_www.cgi b/httemplate/edit/process/svc_www.cgi
new file mode 100644 (file)
index 0000000..f02d253
--- /dev/null
@@ -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/quick-charge.html b/httemplate/edit/quick-charge.html
new file mode 100644 (file)
index 0000000..95ec70c
--- /dev/null
@@ -0,0 +1,182 @@
+<% include("/elements/header-popup.html", 'One-time charge entry', '',
+            ( $cgi->param('error') ? '' : 'onload="addRow()"' ),
+          )
+%>
+
+<% 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 enable_quick_charge_desc () {
+  if (  document.QuickChargeForm.amount.value && document.QuickChargeForm.pkg.value ) {
+    document.QuickChargeForm.submit.disabled = false;
+  } else {
+    document.QuickChargeForm.submit.disabled = true;
+  }
+}
+
+function enable_quick_charge_amount () {
+  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;
+}
+
+</SCRIPT>
+
+<FORM ACTION="process/quick-charge.cgi" NAME="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_amount()">
+  </TD>
+<% include('/elements/tr-select-pkg_class.html', '') %>
+<% include('/elements/tr-select-taxclass.html') %>
+</TR>
+  <TD>Description:</TD>
+  <TD>
+    <INPUT TYPE="text" NAME="pkg" SIZE="60" MAXLENGTH="65" VALUE="<% $pkg %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge_desc()">
+  </TD>
+</TR>
+<TR>
+  <TD></TD>
+  <TD><FONT SIZE="-1">Optional additional description: </FONT></TD>
+</TR>
+
+% my $row = 0;
+%   if ( $cgi->param('error') ) {
+%     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" 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');
+
+$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('pkg') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ 
+  or die 'illegal description';
+my $pkg = $1;
+
+</%init>
diff --git a/httemplate/edit/rate.cgi b/httemplate/edit/rate.cgi
new file mode 100644 (file)
index 0000000..4c0abfe
--- /dev/null
@@ -0,0 +1,43 @@
+<% include("/elements/header.html","$action Rate plan", menubar(
+      'View all rate plans' => "${p}browse/rate.cgi",
+    ))
+%>
+
+<% include('/elements/progress-init.html',
+              'OneTrueForm',
+              [ '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 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 (file)
index 0000000..b9eaf65
--- /dev/null
@@ -0,0 +1,59 @@
+<% 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',
+                   'min_charge'          => 'Charge per minute',
+                   'sec_granularity'     => 'Granularity',
+                 },
+     '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=>'min_charge',          type=>'money', size=>4 },
+                   { field         =>'sec_granularity',
+                     type          =>'select',
+                     options       => [qw( 1 6 30 60 )],
+                     labels        => \%granularity,
+                     disable_empty => 1,
+                   },
+
+                 ],
+   )
+%>
+<%once>
+
+tie my %granularity, 'Tie::IxHash',
+  '1', => '1 second',
+  '6'  => '6 second',
+  '30' => '30 second', # '1/2 minute',
+  '60' => 'minute',
+;
+
+</%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';
+if (    $cgi->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 (file)
index 0000000..04f285f
--- /dev/null
@@ -0,0 +1,153 @@
+<% 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, @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>Charge per<BR>minute/call</FONT>
+    </TH>
+    <TH CLASS="grid" BGCOLOR="#cccccc">
+      <FONT SIZE=-1>Granularity</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 %>">
+      $<INPUT TYPE="text" SIZE=6 NAME="min_charge<%$n%>" VALUE="<% sprintf('%.2f', $cgi->param("min_charge$n") || $rate_detail->min_charge ) %>">
+    </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>
+
+  </TR>
+
+% } 
+
+</TABLE>
+
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="<% $rate_region->regionnum ? "Apply changes" : "Add region" %>">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+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',
+  '1', => '1 second',
+  '6'  => '6 second',
+  '30' => '30 second', # '1/2 minute',
+  '60' => 'minute',
+  '0'  => 'call',
+;
+
+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 (file)
index 0000000..620a2ea
--- /dev/null
@@ -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 (file)
index 0000000..ea5650e
--- /dev/null
@@ -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 (file)
index 0000000..e57ac09
--- /dev/null
@@ -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 %> - <% $part_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 (executable)
index 0000000..c08e544
--- /dev/null
@@ -0,0 +1,78 @@
+<% include('/elements/header.html', "$action Router", menubar(
+     'View all routers' => "${p}browse/router.cgi",
+   ))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/router.cgi" METHOD=POST>
+  <INPUT TYPE="hidden" NAME="table" VALUE="router">
+  <INPUT TYPE="hidden" NAME="redirect_ok" VALUE="<%$p3%>/browse/router.cgi">
+  <INPUT TYPE="hidden" NAME="redirect_error" VALUE="<%$p3%>/edit/router.cgi">
+  <INPUT TYPE="hidden" NAME="routernum" VALUE="<%$routernum%>">
+  <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$router->svcnum%>">
+    Router #<%$routernum or "(NEW)"%>
+
+<BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%$router->routername%>">
+
+<BR><BR>
+Custom fields:
+<BR>
+<%table() %>
+%
+%foreach my $field ($router->virtual_fields) {
+%  print $router->pvf($field)->widget('HTML', 'edit', 
+%        $router->getfield($field));
+%}
+%
+
+</TABLE>
+%
+%unless ($router->svcnum) {
+%
+
+<BR><BR>Select the service types available on this router<BR>
+%
+%
+%  foreach my $part_svc ( qsearch('part_svc', { svcdb    => 'svc_broadband',
+%                                               disabled => '' }) ) {
+%  
+
+  <BR>
+  <INPUT TYPE="checkbox" NAME="svcpart_<%$part_svc->svcpart%>"<%
+      qsearchs('part_svc_router', { svcpart   => $part_svc->svcpart, 
+                                    routernum => $routernum } ) ? ' CHECKED' : ''%> VALUE="ON">
+  <A HREF="<%${p}%>edit/part_svc.cgi?<%$part_svc->svcpart%>">
+    <%$part_svc->svcpart%>: <%$part_svc->svc%></A>
+% } 
+% } 
+
+
+  <BR><BR><INPUT TYPE="submit" VALUE="Apply changes">
+  </FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $router;
+if ( $cgi->keywords ) {
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/;
+  $router = qsearchs('router', { routernum => $1 }) 
+      or print $cgi->redirect(popurl(2)."browse/router.cgi") ;
+} else {
+  $router = new FS::router ( {
+    map { $_, scalar($cgi->param($_)) } fields('router')
+  } );
+}
+
+my $routernum = $router->routernum;
+my $action = $routernum ? 'Edit' : 'Add';
+
+my $p3 = popurl(3);
+
+</%init>
diff --git a/httemplate/edit/svc_Common.html b/httemplate/edit/svc_Common.html
new file mode 100644 (file)
index 0000000..6666d97
--- /dev/null
@@ -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 (executable)
index 0000000..58283ef
--- /dev/null
@@ -0,0 +1,452 @@
+<% 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>
+% } 
+
+
+<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="_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>>
+    (blank to generate)
+  </TD>
+</TR>
+%}else{
+    <INPUT TYPE="hidden" NAME="_password" VALUE="<% $password %>">
+%}
+%
+%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>
+% } 
+%
+%#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">GECOS</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 ( $part_svc->part_svc_column('quota')->columnflag eq 'F' ) { 
+
+
+  <INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>">
+% } else { 
+
+
+  <TR>
+    <TD ALIGN="right">Quota:</TD>
+    <TD><INPUT TYPE="text" NAME="quota" VALUE="<% $svc_acct->quota %>"></TD>
+  </TR>
+% } 
+% 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='';
+
+} 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( $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 ( $svc_acct->_password ) {
+  if ( $conf->exists('showpasswords') || ! $svcnum ) {
+    $password = $svc_acct->_password;
+  } else {
+    $password = "*HIDDEN*";
+  }
+} else {
+  $password = '';
+}
+
+my $ulen = 
+  $conf->exists('usernamemax')
+  ? $conf->config('usernamemax')
+  : dbdef->table('svc_acct')->column('username')->length;
+my $ulen2 = $ulen+2;
+
+my $pmax = $conf->config('passwordmax') || 8;
+my $pmax2 = $pmax+2;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/svc_acct_pop.cgi b/httemplate/edit/svc_acct_pop.cgi
new file mode 100755 (executable)
index 0000000..3c16a1f
--- /dev/null
@@ -0,0 +1,50 @@
+<% 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>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('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 (file)
index 0000000..c2fb58d
--- /dev/null
@@ -0,0 +1,254 @@
+<% include('/elements/header.html', "Broadband Service $action") %>
+
+<% include('/elements/error.html') %>
+
+Service #<B><%$svcnum ? $svcnum : "(NEW)"%></B><BR><BR>
+
+<FORM ACTION="<%${p1}%>process/svc_broadband.cgi" METHOD=POST>
+  <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+  <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%$pkgnum%>">
+  <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%$svcpart%>">
+
+  <%&ntable("#cccccc",2)%>
+    <TR>
+      <TD ALIGN="right">Description</TD>
+      <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('description')->columnflag eq 'F' ) { 
+
+        <INPUT TYPE="hidden" NAME="description" VALUE="<%$description%>"><%$description%>
+% } else { 
+
+    <INPUT TYPE="text" NAME="description" VALUE="<%$description%>">
+% } 
+
+      </TD>
+    </TR>
+    <TR>
+      <TD ALIGN="right">IP Address</TD>
+      <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) { 
+
+        <INPUT TYPE="hidden" NAME="ip_addr" VALUE="<%$ip_addr%>"><%$ip_addr%>
+% } else { 
+
+        <INPUT TYPE="text" NAME="ip_addr" VALUE="<%$ip_addr%>">
+% } 
+
+      </TD>
+    </TR>
+    <TR>
+      <TD ALIGN="right">Download speed</TD>
+      <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) { 
+
+        <INPUT TYPE="hidden" NAME="speed_down" VALUE="<%$speed_down%>"><%$speed_down%>Kbps
+% } else { 
+
+    <INPUT TYPE="text" NAME="speed_down" SIZE=5 VALUE="<%$speed_down%>">Kbps
+% } 
+
+      </TD>
+    </TR>
+    <TR>
+      <TD ALIGN="right">Upload speed</TD>
+      <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) { 
+
+        <INPUT TYPE="hidden" NAME="speed_up" VALUE="<%$speed_up%>"><%$speed_up%>Kbps
+% } else { 
+
+        <INPUT TYPE="text" NAME="speed_up" SIZE=5 VALUE="<%$speed_up%>">Kbps
+% } 
+
+      </TD>
+    </TR>
+% if ($action eq 'Add') { 
+
+    <TR>
+      <TD ALIGN="right">Router/Block</TD>
+      <TD BGCOLOR="#ffffff">
+        <SELECT NAME="blocknum">
+%
+%  warn $svc_broadband->svcpart;
+%  foreach my $router ($svc_broadband->allowed_routers) {
+%    warn $router->routername;
+%    foreach my $addr_block ($router->addr_block) {
+%
+
+        <OPTION VALUE="<%$addr_block->blocknum%>"<%($addr_block->blocknum eq $blocknum) ? ' SELECTED' : ''%>>
+          <%$router->routername%>:<%$addr_block->ip_gateway%>/<%$addr_block->ip_netmask%></OPTION>
+%
+%    }
+%  }
+%
+
+        </SELECT>
+      </TD>
+    </TR>
+% } else { 
+
+
+    <TR>
+      <TD ALIGN="right">Router/Block</TD>
+      <TD BGCOLOR="#ffffff">
+        <%$svc_broadband->addr_block->router->routername%>:<%$svc_broadband->addr_block->NetAddr%>
+        <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$svc_broadband->blocknum%>">
+      </TD>
+    </TR>
+% } 
+    <TR>
+      <TD ALIGN="right">MAC Address</TD>
+      <TD BGCOLOR="#ffffff">
+        <INPUT TYPE="text" NAME="mac_addr" VALUE="<%$mac_addr%>">
+      </TD>
+    </TR>
+    <TR>
+      <TD ALIGN="right">Latitude</TD>
+      <TD BGCOLOR="#ffffff">
+        <INPUT TYPE="text" NAME="latitude" VALUE="<%$latitude%>">
+      </TD>
+    </TR>
+    <TR>
+      <TD ALIGN="right">Longitude</TD>
+      <TD BGCOLOR="#ffffff">
+        <INPUT TYPE="text" NAME="longitude" VALUE="<%$longitude%>">
+      </TD>
+    </TR>
+    <TR>
+      <TD ALIGN="right">Altitude</TD>
+      <TD BGCOLOR="#ffffff">
+        <INPUT TYPE="text" NAME="altitude" VALUE="<%$altitude%>">
+      </TD>
+    </TR>
+    <TR>
+      <TD ALIGN="right">VLAN Profile</TD>
+      <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('vlan_profile')->columnflag eq 'F' ) { 
+
+        <INPUT TYPE="hidden" NAME="vlan_profile" VALUE="<%$vlan_profile%>"><%$vlan_profile%>
+% } else { 
+
+        <INPUT TYPE="text" NAME="vlan_profile" VALUE="<%$vlan_profile%>">
+% } 
+
+      </TD>
+    </TR>
+    <TR>
+      <TD ALIGN="right">Authentication Key</TD>
+      <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('auth_key')->columnflag eq 'F' ) { 
+
+        <INPUT TYPE="hidden" NAME="auth_key" VALUE="<%$auth_key%>"><%$auth_key%>
+% } else { 
+
+        <INPUT TYPE="text" NAME="auth_key" VALUE="<%$auth_key%>">
+% } 
+
+      </TD>
+    </TR>
+%
+%foreach my $field ($svc_broadband->virtual_fields) {
+%  if ( $part_svc->part_svc_column($field)->columnflag ne 'F' &&
+%       $part_svc->part_svc_column($field)->columnflag ne 'X') {
+%    print $svc_broadband->pvf($field)->widget('HTML', 'edit',
+%        $svc_broadband->getfield($field));
+%  }
+%} 
+
+  </TABLE>
+  <BR>
+  <INPUT TYPE="submit" NAME="submit" VALUE="Submit">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%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
+
+use HTML::Widgets::SelectLayers;
+use Tie::IxHash;
+
+my( $svcnum,  $pkgnum, $svcpart, $part_svc, $svc_broadband );
+if ( $cgi->param('error') ) {
+
+  $svc_broadband = new FS::svc_broadband ( {
+    map { $_, scalar($cgi->param($_)) } fields('svc_broadband'), qw(svcpart)
+  } );
+  $svcnum = $svc_broadband->svcnum;
+  $pkgnum = $cgi->param('pkgnum');
+  $svcpart = $svc_broadband->svcpart;
+  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+  die "No part_svc entry!" unless $part_svc;
+
+} 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_broadband = new FS::svc_broadband({ svcpart => $svcpart });
+
+  $svcnum='';
+
+  $svc_broadband->set_default_and_fixed;
+
+} else { #editing
+
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/ or die "unparsable svcnum";
+  $svcnum=$1;
+  $svc_broadband=qsearchs('svc_broadband',{'svcnum'=>$svcnum})
+    or die "Unknown (svc_broadband) svcnum!";
+
+  my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+    or die "Unknown (cust_svc) svcnum!";
+
+  $pkgnum=$cust_svc->pkgnum;
+  $svcpart=$cust_svc->svcpart;
+  
+  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+  die "No part_svc entry!" unless $part_svc;
+
+}
+my $action = $svc_broadband->svcnum ? 'Edit' : 'Add';
+
+if ($pkgnum) {
+
+  #Nothing?
+
+} elsif ( $action eq 'Edit' ) {
+
+  #Nothing?
+
+} else {
+  die "\$action eq Add, but \$pkgnum is null!\n";
+}
+
+my $p1 = popurl(1);
+
+my ($ip_addr, $speed_up, $speed_down, $blocknum, $mac_addr,
+    $latitude, $longitude, $altitude, $vlan_profile, $auth_key,
+    $description) =
+    ($svc_broadband->ip_addr,
+     $svc_broadband->speed_up,
+     $svc_broadband->speed_down,
+     $svc_broadband->blocknum,
+     $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/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi
new file mode 100755 (executable)
index 0000000..56ba604
--- /dev/null
@@ -0,0 +1,91 @@
+<% 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 %>">
+
+<INPUT TYPE="radio" NAME="action" VALUE="N"<% $kludge_action eq 'N' ? ' CHECKED' : '' %>>New
+<BR>
+
+<INPUT TYPE="radio" NAME="action" VALUE="M"<% $kludge_action eq 'M' ? ' CHECKED' : '' %>>Transfer
+
+<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="<% $domain %>" SIZE=28 MAXLENGTH=63>
+
+<BR>Purpose/Description: <INPUT TYPE="text" NAME="purpose" VALUE="<% $purpose %>" SIZE=64>
+
+<P><INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc,
+   $svc_domain);
+if ( $cgi->param('error') ) {
+
+  $svc_domain = new FS::svc_domain ( {
+    map { $_, scalar($cgi->param($_)) } fields('svc_domain')
+  } );
+  $svcnum = $svc_domain->svcnum;
+  $pkgnum = $cgi->param('pkgnum');
+  $svcpart = $cgi->param('svcpart');
+  $kludge_action = $cgi->param('action');
+  $purpose = $cgi->param('purpose');
+  $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+  die "No part_svc entry!" unless $part_svc;
+
+} 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 = '';
+  $purpose = '';
+  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 $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 (file)
index 0000000..0df842b
--- /dev/null
@@ -0,0 +1,102 @@
+<% include('/elements/header.html', "External service $action") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/svc_external.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 $id    = $svc_external->id;
+% my $title = $svc_external->title;
+%
+<% &ntable("#cccccc",2) %>
+  <TR>
+    <TD ALIGN="right">External ID</TD>
+    <TD><INPUT TYPE="text" NAME="id" VALUE="<% $id %>"></TD>
+  </TR>
+  <TR>
+    <TD ALIGN="right">Title</TD>
+    <TD><INPUT TYPE="text" NAME="title" VALUE="<% $title %>"></TD>
+  </TR>
+
+% foreach my $field ($svc_external->virtual_fields) {
+%   if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+%     # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+      <% $svc_external->pvf($field)->widget( 'HTML',
+                                             'edit', 
+                                             $svc_external->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( $svcnum,  $pkgnum, $svcpart, $part_svc, $svc_external );
+if ( $cgi->param('error') ) {
+
+  $svc_external = new FS::svc_external ( {
+    map { $_, scalar($cgi->param($_)) } fields('svc_external')
+  } );
+  $svcnum = $svc_external->svcnum;
+  $pkgnum = $cgi->param('pkgnum');
+  $svcpart = $cgi->param('svcpart');
+  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+  die "No part_svc entry!" unless $part_svc;
+
+} 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_external = new FS::svc_external { svcpart => $svcpart };
+
+  $svcnum='';
+
+  $svc_external->set_default_and_fixed;
+
+} else { #adding
+
+  my($query) = $cgi->keywords;
+  $query =~ /^(\d+)$/ or die "unparsable svcnum";
+  $svcnum=$1;
+  $svc_external=qsearchs('svc_external',{'svcnum'=>$svcnum})
+    or die "Unknown (svc_external) svcnum!";
+
+  my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+    or die "Unknown (cust_svc) svcnum!";
+
+  $pkgnum=$cust_svc->pkgnum;
+  $svcpart=$cust_svc->svcpart;
+  
+  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+  die "No part_svc entry!" unless $part_svc;
+
+}
+my $action = $svc_external->svcnum ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi
new file mode 100755 (executable)
index 0000000..96a00a5
--- /dev/null
@@ -0,0 +1,175 @@
+<% 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><SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)">
+% foreach $_ (keys %email) { 
+
+  <OPTION<% $_ eq $srcsvc ? " SELECTED" : "" %> VALUE="<% $_ %>"><% $email{$_} %></OPTION>
+% } 
+% if ( $svc_forward->dbdef_table->column('src') ) { 
+
+  <OPTION <% $src ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION>
+% } 
+
+</SELECT>
+% if ( $svc_forward->dbdef_table->column('src') ) { 
+
+<INPUT TYPE="text" NAME="src" VALUE="<% $src %>" <% ( $src || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>>
+% } 
+
+</TD></TR>
+
+<TR><TD ALIGN="right">Forwards to</TD>
+<TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)">
+% foreach $_ (keys %email) { 
+
+  <OPTION<% $_ eq $dstsvc ? " SELECTED" : "" %> VALUE="<% $_ %>"><% $email{$_} %></OPTION>
+% } 
+
+<OPTION <% $dst ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION>
+</SELECT>
+<INPUT TYPE="text" NAME="dst" VALUE="<% $dst %>" <% ( $dst || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>>
+</TD></TR>
+    </TABLE>
+<BR><INPUT TYPE="submit" VALUE="Submit">
+</FORM>
+
+<% 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_phone.cgi b/httemplate/edit/svc_phone.cgi
new file mode 100644 (file)
index 0000000..78b849c
--- /dev/null
@@ -0,0 +1,17 @@
+<% include( 'elements/svc_Common.html',
+               'name'     => 'Phone number',
+               'table'    => 'svc_phone',
+               'fields'   => [qw( countrycode phonenum )], #pin
+               'labels'   => {
+                               'countrycode' => 'Country code',
+                               'phonenum'    => 'Phone number',
+                               'pin'         => 'PIN',
+                             },
+           )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi
new file mode 100644 (file)
index 0000000..d3416a4
--- /dev/null
@@ -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=$cgi->escapeHTML($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/elements/calendar-en.js b/httemplate/elements/calendar-en.js
new file mode 100644 (file)
index 0000000..0dbde79
--- /dev/null
@@ -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 (file)
index 0000000..b27d9be
--- /dev/null
@@ -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 (file)
index 0000000..6f37b7d
--- /dev/null
@@ -0,0 +1,271 @@
+/* The main calendar widget.  DIV containing a table. */
+
+.calendar {
+  position: relative;
+  display: none;
+  border-top: 2px solid #fff;
+  border-right: 2px solid #000;
+  border-bottom: 2px solid #000;
+  border-left: 2px solid #fff;
+  font-size: 11px;
+  color: #000;
+  cursor: default;
+  background: #d4c8d0;
+  font-family: tahoma,verdana,sans-serif;
+}
+
+.calendar table {
+  border-top: 1px solid #000;
+  border-right: 1px solid #fff;
+  border-bottom: 1px solid #fff;
+  border-left: 1px solid #000;
+  font-size: 11px;
+  color: #000;
+  cursor: default;
+  background: #d4c8d0;
+  font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+  text-align: center;
+  padding: 1px;
+  border-top: 1px solid #fff;
+  border-right: 1px solid #000;
+  border-bottom: 1px solid #000;
+  border-left: 1px solid #fff;
+}
+
+.calendar .nav {
+  background: transparent url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+  font-weight: bold;
+  padding: 1px;
+  border: 1px solid #000;
+  background: #847880;
+  color: #fff;
+  text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+  border-bottom: 1px solid #000;
+  padding: 2px;
+  text-align: center;
+  background: #f4e8f0;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+  color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+  border-top: 2px solid #fff;
+  border-right: 2px solid #000;
+  border-bottom: 2px solid #000;
+  border-left: 2px solid #fff;
+  padding: 0px;
+  background-color: #e4d8e0;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+  padding: 2px 0px 0px 2px;
+  border-top: 1px solid #000;
+  border-right: 1px solid #fff;
+  border-bottom: 1px solid #fff;
+  border-left: 1px solid #000;
+  background-color: #c4b8c0;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+  width: 2em;
+  text-align: right;
+  padding: 2px 4px 2px 2px;
+}
+.calendar tbody .day.othermonth {
+  font-size: 80%;
+  color: #aaa;
+}
+.calendar tbody .day.othermonth.oweekend {
+  color: #faa;
+}
+
+.calendar table .wn {
+  padding: 2px 3px 2px 2px;
+  border-right: 1px solid #000;
+  background: #f4e8f0;
+}
+
+.calendar tbody .rowhilite td {
+  background: #e4d8e0;
+}
+
+.calendar tbody .rowhilite td.wn {
+  background: #d4c8d0;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+  padding: 1px 3px 1px 1px;
+  border-top: 1px solid #fff;
+  border-right: 1px solid #000;
+  border-bottom: 1px solid #000;
+  border-left: 1px solid #fff;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+  padding: 2px 2px 0px 2px;
+  border-top: 1px solid #000;
+  border-right: 1px solid #fff;
+  border-bottom: 1px solid #fff;
+  border-left: 1px solid #000;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+  font-weight: bold;
+  border-top: 1px solid #000;
+  border-right: 1px solid #fff;
+  border-bottom: 1px solid #fff;
+  border-left: 1px solid #000;
+  padding: 2px 2px 0px 2px;
+  background: #e4d8e0;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+  color: #f00;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+  font-weight: bold;
+  color: #00f;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+  visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+  display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+  background: #f4e8f0;
+  padding: 1px;
+  border: 1px solid #000;
+  background: #847880;
+  color: #fff;
+  text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+  border-top: 1px solid #fff;
+  border-right: 1px solid #000;
+  border-bottom: 1px solid #000;
+  border-left: 1px solid #fff;
+  padding: 1px;
+  background: #e4d8e0;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+  padding: 2px 0px 0px 2px;
+  border-top: 1px solid #000;
+  border-right: 1px solid #fff;
+  border-bottom: 1px solid #fff;
+  border-left: 1px solid #000;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.calendar .combo {
+  position: absolute;
+  display: none;
+  width: 4em;
+  top: 0px;
+  left: 0px;
+  cursor: default;
+  border-top: 1px solid #fff;
+  border-right: 1px solid #000;
+  border-bottom: 1px solid #000;
+  border-left: 1px solid #fff;
+  background: #e4d8e0;
+  font-size: 90%;
+  padding: 1px;
+  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 (file)
index 0000000..f5c74f6
--- /dev/null
@@ -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 (file)
index 0000000..4fe03f1
--- /dev/null
@@ -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/checkboxes-table-name.html b/httemplate/elements/checkboxes-table-name.html
new file mode 100644 (file)
index 0000000..1638b9c
--- /dev/null
@@ -0,0 +1,113 @@
+<%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>
+
+<TABLE CELLSPACING=0 CELLPADDING=0>
+
+% foreach my $item ( @{ $opt{'names_list'} } ) {
+%
+%     my $name = ref($item) ? $item->[0] : $item;
+%     ( my $display = $name ) =~ s/ /&nbsp;/g;
+%     $display .= $item->[1]{note} if ref($item) && $item->[1]{note};
+%     my $desc = ref($item) && $item->[1]{desc} ? $item->[1]{desc} : '';
+%
+%     my $checked;
+%     if ( $cgi->param('error') ) {
+%
+%       $checked = $cgi->param($opt{'link_table'}. ".$name" )
+%                    ? 'CHECKED'
+%                    : '';
+%
+%     } else {
+%
+%       $checked =
+%         qsearchs( $opt{'link_table'}, {
+%                                         $source_pkey     => $sourcenum,
+%                                         $opt{'name_col'} => $name,
+%                                         %$link_static,
+%                                       }                                 )
+%                    ? 'CHECKED'
+%                    : ''
+%
+%     }
+
+  <TR>
+    <TD VALIGN="top">
+      <INPUT TYPE="checkbox" NAME="<% $opt{'link_table'}. ".$name" %>" <% $checked %> VALUE="ON">
+    </TD>
+    <TD><% $display %>
+%     if ( $desc ) {
+        <BR><FONT SIZE="-2"><% $desc %></FONT>
+%     }
+    </TD>
+  </TR>
+
+% } 
+
+</TABLE>
+
+<%init>
+
+my( %opt ) = @_;
+
+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'} || {};
+
+</%init>
diff --git a/httemplate/elements/checkboxes-table.html b/httemplate/elements/checkboxes-table.html
new file mode 100644 (file)
index 0000000..cdfa58e
--- /dev/null
@@ -0,0 +1,123 @@
+%
+%
+%  ##
+%  # 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'} ) {
+%
+%    $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'};
+%  }
+%
+%  my $hashref = $opt{'hashref'} || {};
+%
+%  my $extra_sql = '';
+%
+%  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/cssexpr.js b/httemplate/elements/cssexpr.js
new file mode 100644 (file)
index 0000000..c434d8d
--- /dev/null
@@ -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/dashboard-toplist.html b/httemplate/elements/dashboard-toplist.html
new file mode 100644 (file)
index 0000000..7ee6f2d
--- /dev/null
@@ -0,0 +1,109 @@
+% 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 %>" 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) + 3 %>"></TH>
+      </TR>
+
+%     next;
+%     
+%   } elsif ( $line =~ /^\s*$/ ) {
+
+      <TR>
+        <TD CLASS="grid" COLSPAN="<% scalar(@custom_priorities) + 3 %>" BGCOLOR="<% $bgcolor %>">&nbsp;</TD>
+      </TR>
+
+%   } elsif ( $line =~ /^\S/ ) { #label line
+
+      <TR>
+        <TH CLASS="grid" BGCOLOR="#cccccc"><% $line %></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) + 3 %>" 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 (file)
index 0000000..f467de2
--- /dev/null
@@ -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.html b/httemplate/elements/errorpage.html
new file mode 100644 (file)
index 0000000..76a0bf3
--- /dev/null
@@ -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 (file)
index 0000000..8dfb661
--- /dev/null
@@ -0,0 +1,15 @@
+<public:component lightweight="true">\r
+\r
+<script language="javascript">\r
+\r
+function CancelEvent()\r
+{\r
+       return false ;\r
+}\r
+\r
+this.onresizestart = CancelEvent ;\r
+this.onbeforeeditfocus = CancelEvent ;\r
+\r
+</script>\r
+\r
+</public:component>\r
diff --git a/httemplate/elements/fckeditor/editor/css/behaviors/showtableborders.htc b/httemplate/elements/fckeditor/editor/css/behaviors/showtableborders.htc
new file mode 100644 (file)
index 0000000..77418b9
--- /dev/null
@@ -0,0 +1,36 @@
+<public:component lightweight="true">\r
+\r
+<public:attach event="oncontentready" onevent="ShowBorders()" />\r
+<public:attach event="onpropertychange" onevent="OnPropertyChange()" />\r
+\r
+<script language="javascript">\r
+\r
+var oClassRegex = /\s*FCK__ShowTableBorders/ ;\r
+\r
+function ShowBorders()\r
+{\r
+       if ( this.border == 0 )\r
+       {\r
+               if ( !oClassRegex.test( this.className ) )\r
+                       this.className += ' FCK__ShowTableBorders' ;\r
+       }\r
+       else\r
+       {\r
+               if ( oClassRegex.test( this.className ) )\r
+               {\r
+                       this.className = this.className.replace( oClassRegex, '' ) ;\r
+                       if ( this.className.length == 0 )\r
+                               this.removeAttribute( 'className', 0 ) ;\r
+               }\r
+       }\r
+}\r
+\r
+function OnPropertyChange()\r
+{\r
+       if ( event.propertyName == 'border' || event.propertyName == 'className' )\r
+               ShowBorders.call(this) ;\r
+}\r
+\r
+</script>\r
+\r
+</public:component>\r
diff --git a/httemplate/elements/fckeditor/editor/css/fck_editorarea.css b/httemplate/elements/fckeditor/editor/css/fck_editorarea.css
new file mode 100644 (file)
index 0000000..8539aa4
--- /dev/null
@@ -0,0 +1,91 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This is the default CSS file used by the editor area. It defines the\r
+ * initial font of the editor and background color.\r
+ *\r
+ * A user can configure the editor to use another CSS file. Just change\r
+ * the value of the FCKConfig.EditorAreaCSS key in the configuration\r
+ * file.\r
+ */\r
+\r
+/*\r
+    The "body" styles should match your editor web site, mainly regarding\r
+    background color and font family and size.\r
+*/\r
+\r
+body\r
+{\r
+       background-color: #ffffff;\r
+       padding: 5px 5px 5px 5px;\r
+       margin: 0px;\r
+}\r
+\r
+body, td\r
+{\r
+       font-family: Arial, Verdana, Sans-Serif;\r
+       font-size: 12px;\r
+}\r
+\r
+a[href]\r
+{\r
+       color: #0000FF !important;      /* For Firefox... mark as important, otherwise it becomes black */\r
+}\r
+\r
+/*\r
+       Just uncomment the following block if you want to avoid spaces between\r
+       paragraphs. Remember to apply the same style in your output front end page.\r
+*/\r
+\r
+/*\r
+p, ul, li\r
+{\r
+       margin-top: 0px;\r
+       margin-bottom: 0px;\r
+}\r
+*/\r
+\r
+/*\r
+    The following are some sample styles used in the "Styles" toolbar command.\r
+    You should instead remove them, and include the styles used by the site\r
+    you are using the editor in.\r
+*/\r
+\r
+.Bold\r
+{\r
+       font-weight: bold;\r
+}\r
+\r
+.Title\r
+{\r
+       font-weight: bold;\r
+       font-size: 18px;\r
+       color: #cc3300;\r
+}\r
+\r
+.Code\r
+{\r
+       border: #8b4513 1px solid;\r
+       padding-right: 5px;\r
+       padding-left: 5px;\r
+       color: #000066;\r
+       font-family: 'Courier New' , Monospace;\r
+       background-color: #ff9933;\r
+}
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/css/fck_internal.css b/httemplate/elements/fckeditor/editor/css/fck_internal.css
new file mode 100644 (file)
index 0000000..e686560
--- /dev/null
@@ -0,0 +1,111 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This CSS Style Sheet defines rules used by the editor for its internal use.\r
+ */\r
+\r
+/* Fix to allow putting the caret at the end of the\r
+content in Firefox if clicking below the content */\r
+html\r
+{\r
+       min-height: 100%;\r
+}\r
+\r
+\r
+table.FCK__ShowTableBorders, table.FCK__ShowTableBorders td, table.FCK__ShowTableBorders th\r
+{\r
+       border: #d3d3d3 1px solid;\r
+}\r
+\r
+form\r
+{\r
+       border: 1px dotted #FF0000;\r
+       padding: 2px;\r
+}\r
+\r
+.FCK__Flash\r
+{\r
+       border: #a9a9a9 1px solid;\r
+       background-position: center center;\r
+       background-image: url(images/fck_flashlogo.gif);\r
+       background-repeat: no-repeat;\r
+       width: 80px;\r
+       height: 80px;\r
+}\r
+\r
+/* Empty anchors images */\r
+.FCK__Anchor\r
+{\r
+       border: 1px dotted #00F;\r
+       background-position: center center;\r
+       background-image: url(images/fck_anchor.gif);\r
+       background-repeat: no-repeat;\r
+       width: 16px;\r
+       height: 15px;\r
+       vertical-align: middle;\r
+}\r
+\r
+/* Anchors with content */\r
+.FCK__AnchorC\r
+{\r
+       border: 1px dotted #00F;\r
+       background-position: 1px center;\r
+       background-image: url(images/fck_anchor.gif);\r
+       background-repeat: no-repeat;\r
+       padding-left: 18px;\r
+}\r
+\r
+/* Any anchor for non-IE, if we combine it\r
+   with the previous rule IE ignores all. */\r
+a[name]\r
+{\r
+       border: 1px dotted #00F;\r
+       background-position: 0 center;\r
+       background-image: url(images/fck_anchor.gif);\r
+       background-repeat: no-repeat;\r
+       padding-left: 18px;\r
+}\r
+\r
+.FCK__PageBreak\r
+{\r
+       background-position: center center;\r
+       background-image: url(images/fck_pagebreak.gif);\r
+       background-repeat: no-repeat;\r
+       clear: both;\r
+       display: block;\r
+       float: none;\r
+       width: 100%;\r
+       border-top: #999999 1px dotted;\r
+       border-bottom: #999999 1px dotted;\r
+       border-right: 0px;\r
+       border-left: 0px;\r
+       height: 5px;\r
+}\r
+\r
+/* Hidden fields */\r
+.FCK__InputHidden\r
+{\r
+       width: 19px;\r
+       height: 18px;\r
+       background-image: url(images/fck_hiddenfield.gif);\r
+       background-repeat: no-repeat;\r
+       vertical-align: text-bottom;\r
+       background-position: center center;\r
+}\r
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 (file)
index 0000000..5947114
--- /dev/null
@@ -0,0 +1,42 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This CSS Style Sheet defines the rules to show table borders on Gecko.\r
+ */\r
+\r
+/* For tables with the "border" attribute set to "0" */\r
+table[border="0"],\r
+table[border="0"] > tr > td, table[border="0"] > tr > th,\r
+table[border="0"] > tbody > tr > td, table[border="0"] > tbody > tr > th,\r
+table[border="0"] > thead > tr > td, table[border="0"] > thead > tr > th,\r
+table[border="0"] > tfoot > tr > td, table[border="0"] > tfoot > tr > th\r
+{\r
+       border: #d3d3d3 1px dotted ;\r
+}\r
+\r
+/* For tables with no "border" attribute set */\r
+table:not([border]),\r
+table:not([border]) > tr > td, table:not([border]) > tr > th,\r
+table:not([border]) > tbody > tr > td, table:not([border]) > tbody > tr > th,\r
+table:not([border]) > thead > tr > td, table:not([border]) > thead > tr > th,\r
+table:not([border]) > tfoot > tr > td, table:not([border]) > tfoot > tr > th\r
+{\r
+       border: #d3d3d3 1px dotted ;\r
+}\r
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 (file)
index 0000000..5aa797b
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/css/images/fck_anchor.gif 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 (file)
index 0000000..141aac4
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/css/images/fck_flashlogo.gif 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 (file)
index 0000000..953f643
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/css/images/fck_hiddenfield.gif 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 (file)
index 0000000..8d1cffd
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/css/images/fck_pagebreak.gif 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 (file)
index 0000000..c1db114
--- /dev/null
@@ -0,0 +1,83 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This is the CSS file used for interface details in some dialog\r
+ * windows.\r
+ */\r
+\r
+.ImagePreviewArea\r
+{\r
+       border: #000000 1px solid;\r
+       overflow: auto;\r
+       width: 100%;\r
+       height: 170px;\r
+       background-color: #ffffff;\r
+}\r
+\r
+.FlashPreviewArea\r
+{\r
+       border: #000000 1px solid;\r
+       padding: 5px;\r
+       overflow: auto;\r
+       width: 100%;\r
+       height: 170px;\r
+       background-color: #ffffff;\r
+}\r
+\r
+.BtnReset\r
+{\r
+       float: left;\r
+       background-position: center center;\r
+       background-image: url(images/reset.gif);\r
+       width: 16px;\r
+       height: 16px;\r
+       background-repeat: no-repeat;\r
+       border: 1px none;\r
+       font-size: 1px ;\r
+}\r
+\r
+.BtnLocked, .BtnUnlocked\r
+{\r
+       float: left;\r
+       background-position: center center;\r
+       background-image: url(images/locked.gif);\r
+       width: 16px;\r
+       height: 16px;\r
+       background-repeat: no-repeat;\r
+       border: none 1px;\r
+       font-size: 1px ;\r
+}\r
+\r
+.BtnUnlocked\r
+{\r
+       background-image: url(images/unlocked.gif);\r
+}\r
+\r
+.BtnOver\r
+{\r
+       border: outset 1px;\r
+       cursor: pointer;\r
+       cursor: hand;\r
+}\r
+\r
+.FCK__FieldNumeric\r
+{\r
+       behavior: url(common/fcknumericfield.htc) ;\r
+}
\ No newline at end of file
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 (file)
index 0000000..26b5628
--- /dev/null
@@ -0,0 +1,154 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Useful functions used by almost all dialog window pages.\r
+ */\r
+\r
+var GECKO_BOGUS = '<br type="_moz">' ;\r
+\r
+// Gets a element by its Id. Used for shorter coding.\r
+function GetE( elementId )\r
+{\r
+       return document.getElementById( elementId )  ;\r
+}\r
+\r
+function ShowE( element, isVisible )\r
+{\r
+       if ( typeof( element ) == 'string' )\r
+               element = GetE( element ) ;\r
+       element.style.display = isVisible ? '' : 'none' ;\r
+}\r
+\r
+function SetAttribute( element, attName, attValue )\r
+{\r
+       if ( attValue == null || attValue.length == 0 )\r
+               element.removeAttribute( attName, 0 ) ;                 // 0 : Case Insensitive\r
+       else\r
+               element.setAttribute( attName, attValue, 0 ) ;  // 0 : Case Insensitive\r
+}\r
+\r
+function GetAttribute( element, attName, valueIfNull )\r
+{\r
+       var oAtt = element.attributes[attName] ;\r
+\r
+       if ( oAtt == null || !oAtt.specified )\r
+               return valueIfNull ? valueIfNull : '' ;\r
+\r
+       var oValue = element.getAttribute( attName, 2 ) ;\r
+\r
+       if ( oValue == null )\r
+               oValue = oAtt.nodeValue ;\r
+\r
+       return ( oValue == null ? valueIfNull : oValue ) ;\r
+}\r
+\r
+// Functions used by text fiels to accept numbers only.\r
+function IsDigit( e )\r
+{\r
+       if ( !e )\r
+               e = event ;\r
+\r
+       var iCode = ( e.keyCode || e.charCode ) ;\r
+\r
+       return (\r
+                       ( iCode >= 48 && iCode <= 57 )          // Numbers\r
+                       || (iCode >= 37 && iCode <= 40)         // Arrows\r
+                       || iCode == 8                                           // Backspace\r
+                       || iCode == 46                                          // Delete\r
+       ) ;\r
+}\r
+\r
+String.prototype.Trim = function()\r
+{\r
+       return this.replace( /(^\s*)|(\s*$)/g, '' ) ;\r
+}\r
+\r
+String.prototype.StartsWith = function( value )\r
+{\r
+       return ( this.substr( 0, value.length ) == value ) ;\r
+}\r
+\r
+String.prototype.Remove = function( start, length )\r
+{\r
+       var s = '' ;\r
+\r
+       if ( start > 0 )\r
+               s = this.substring( 0, start ) ;\r
+\r
+       if ( start + length < this.length )\r
+               s += this.substring( start + length , this.length ) ;\r
+\r
+       return s ;\r
+}\r
+\r
+String.prototype.ReplaceAll = function( searchArray, replaceArray )\r
+{\r
+       var replaced = this ;\r
+\r
+       for ( var i = 0 ; i < searchArray.length ; i++ )\r
+       {\r
+               replaced = replaced.replace( searchArray[i], replaceArray[i] ) ;\r
+       }\r
+\r
+       return replaced ;\r
+}\r
+\r
+function OpenFileBrowser( url, width, height )\r
+{\r
+       // oEditor must be defined.\r
+\r
+       var iLeft = ( oEditor.FCKConfig.ScreenWidth  - width ) / 2 ;\r
+       var iTop  = ( oEditor.FCKConfig.ScreenHeight - height ) / 2 ;\r
+\r
+       var sOptions = "toolbar=no,status=no,resizable=yes,dependent=yes,scrollbars=yes" ;\r
+       sOptions += ",width=" + width ;\r
+       sOptions += ",height=" + height ;\r
+       sOptions += ",left=" + iLeft ;\r
+       sOptions += ",top=" + iTop ;\r
+\r
+       // The "PreserveSessionOnFileBrowser" because the above code could be\r
+       // blocked by popup blockers.\r
+       if ( oEditor.FCKConfig.PreserveSessionOnFileBrowser && oEditor.FCKBrowserInfo.IsIE )\r
+       {\r
+               // The following change has been made otherwise IE will open the file\r
+               // browser on a different server session (on some cases):\r
+               // http://support.microsoft.com/default.aspx?scid=kb;en-us;831678\r
+               // by Simone Chiaretta.\r
+               var oWindow = oEditor.window.open( url, 'FCKBrowseWindow', sOptions ) ;\r
+\r
+               if ( oWindow )\r
+               {\r
+                       // Detect Yahoo popup blocker.\r
+                       try\r
+                       {\r
+                               var sTest = oWindow.name ; // Yahoo returns "something", but we can't access it, so detect that and avoid strange errors for the user.\r
+                               oWindow.opener = window ;\r
+                       }\r
+                       catch(e)\r
+                       {\r
+                               alert( oEditor.FCKLang.BrowseServerBlocked ) ;\r
+                       }\r
+               }\r
+               else\r
+                       alert( oEditor.FCKLang.BrowseServerBlocked ) ;\r
+    }\r
+    else\r
+               window.open( url, 'FCKBrowseWindow', sOptions ) ;\r
+}
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/dialog/common/fcknumericfield.htc b/httemplate/elements/fckeditor/editor/dialog/common/fcknumericfield.htc
new file mode 100644 (file)
index 0000000..74f26d0
--- /dev/null
@@ -0,0 +1,24 @@
+<public:component lightweight="true">\r
+\r
+<script language="javascript">\r
+\r
+function CheckIsDigit()\r
+{\r
+       var iCode = event.keyCode ;\r
+\r
+       event.returnValue =\r
+               (\r
+                       ( iCode >= 48 && iCode <= 57 )          // Numbers\r
+                       || (iCode >= 37 && iCode <= 40)         // Arrows\r
+                       || iCode == 8                                           // Backspace\r
+                       || iCode == 46                                          // Delete\r
+               ) ;\r
+\r
+       return event.returnValue ;\r
+}\r
+\r
+this.onkeypress = CheckIsDigit ;\r
+\r
+</script>\r
+\r
+</public:component>\r
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 (file)
index 0000000..ea07870
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/dialog/common/images/locked.gif 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 (file)
index 0000000..5e9a2fc
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/dialog/common/images/reset.gif 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 (file)
index 0000000..801e423
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/dialog/common/images/unlocked.gif 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 (file)
index 0000000..a457577
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8" ?>\r
+<bindings xmlns="http://www.mozilla.org/xbl">\r
+       <binding id="numericfield">\r
+               <implementation>\r
+                       <constructor>\r
+                               this.keypress = CheckIsDigit ;\r
+                       </constructor>\r
+                       <method name="CheckIsDigit">\r
+                               <body>\r
+                                       <![CDATA[\r
+                                       var iCode = keyCode ;\r
+\r
+                                       var bAccepted =\r
+                                               (\r
+                                                       ( iCode >= 48 && iCode <= 57 )          // Numbers\r
+                                                       || (iCode >= 37 && iCode <= 40)         // Arrows\r
+                                                       || iCode == 8                                           // Backspace\r
+                                                       || iCode == 46                                          // Delete\r
+                                               ) ;\r
+\r
+                                       return bAccepted ;\r
+                                       ]]>\r
+                               </body>\r
+                       </method>\r
+               </implementation>\r
+               <events>\r
+                       <event type="keypress" value="CheckIsDigit()" />\r
+               </events>\r
+       </binding>\r
+</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 (file)
index 0000000..a5825ce
--- /dev/null
@@ -0,0 +1,155 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * "About" dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+var FCKLang    = oEditor.FCKLang ;\r
+\r
+window.parent.AddTab( 'About', FCKLang.DlgAboutAboutTab ) ;\r
+window.parent.AddTab( 'License', FCKLang.DlgAboutLicenseTab ) ;\r
+window.parent.AddTab( 'BrowserInfo', FCKLang.DlgAboutBrowserInfoTab ) ;\r
+\r
+// Function called when a dialog tag is selected.\r
+function OnDialogTabChange( tabCode )\r
+{\r
+       ShowE('divAbout', ( tabCode == 'About' ) ) ;\r
+       ShowE('divLicense', ( tabCode == 'License' ) ) ;\r
+       ShowE('divInfo' , ( tabCode == 'BrowserInfo' ) ) ;\r
+}\r
+\r
+function SendEMail()\r
+{\r
+       var eMail = 'mailto:' ;\r
+       eMail += 'fredck' ;\r
+       eMail += '@' ;\r
+       eMail += 'fckeditor' ;\r
+       eMail += '.' ;\r
+       eMail += 'net' ;\r
+\r
+       window.location = eMail ;\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       // Translate the dialog box texts.\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden">\r
+       <div id="divAbout">\r
+               <table cellpadding="0" cellspacing="0" border="0" width="100%" style="height: 100%">\r
+                       <tr>\r
+                               <td>\r
+                                       <img alt="" src="fck_about/logo_fckeditor.gif" width="236" height="41" align="left" />\r
+                                       <table width="80" border="0" cellspacing="0" cellpadding="5" bgcolor="#ffffff" align="right">\r
+                                               <tr>\r
+                                                       <td align="center" nowrap="nowrap" style="border-right: #000000 1px solid; border-top: #000000 1px solid;\r
+                                                               border-left: #000000 1px solid; border-bottom: #000000 1px solid">\r
+                                                               <span fcklang="DlgAboutVersion">version</span>\r
+                                                               <br />\r
+                                                               <b>2.4.3</b><br />\r
+                                                               Build 15657</td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+                       <tr style="height: 100%">\r
+                               <td align="center">\r
+                                       &nbsp;<br />\r
+                                       <span style="font-size: 14px" dir="ltr">\r
+                                               <br />\r
+                                               <b><a href="http://www.fckeditor.net/?about" target="_blank" title="Visit the FCKeditor web site">\r
+                                                       Support <b>Open Source</b> Software</a></b> </span>\r
+                                       <br />\r
+                                       <br />\r
+                                       <br />\r
+                                       <span fcklang="DlgAboutInfo">For further information go to</span> <a href="http://www.fckeditor.net/?About"\r
+                                               target="_blank">http://www.fckeditor.net/</a>.\r
+                                       <br />\r
+                                       Copyright &copy; 2003-2007 <a href="#" onclick="SendEMail();">Frederico Caldeira Knabben</a>\r
+                               </td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td align="center">\r
+                                       <img alt="" src="fck_about/logo_fredck.gif" width="87" height="36" />\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </div>\r
+       <div id="divLicense" style="display: none">\r
+                       <p>\r
+                               Licensed under the terms of any of the following licenses at your\r
+                               choice:\r
+                       </p>\r
+                       <ul>\r
+                               <li style="margin-bottom:15px">\r
+                                       <b>GNU General Public License</b> Version 2 or later (the "GPL")<br />\r
+                                       <a href="http://www.gnu.org/licenses/gpl.html" target="_blank">http://www.gnu.org/licenses/gpl.html</a>\r
+                               </li>\r
+                               <li style="margin-bottom:15px">\r
+                                       <b>GNU Lesser General Public License</b> Version 2.1 or later (the "LGPL")<br />\r
+                                       <a href="http://www.gnu.org/licenses/lgpl.html" target="_blank">http://www.gnu.org/licenses/lgpl.html</a>\r
+                               </li>\r
+                               <li>\r
+                                       <b>Mozilla Public License</b> Version 1.1 or later (the "MPL")<br />\r
+                                       <a href="http://www.mozilla.org/MPL/MPL-1.1.html" target="_blank">http://www.mozilla.org/MPL/MPL-1.1.html</a>\r
+                          </li>\r
+                       </ul>\r
+       </div>\r
+       <div id="divInfo" style="display: none" dir="ltr">\r
+               <table align="center" width="80%" border="0">\r
+                       <tr>\r
+                               <td>\r
+                                       <script type="text/javascript">\r
+<!--\r
+document.write( '<b>User Agent<\/b><br />' + window.navigator.userAgent + '<br /><br />' ) ;\r
+document.write( '<b>Browser<\/b><br />' + window.navigator.appName + ' ' + window.navigator.appVersion + '<br /><br />' ) ;\r
+document.write( '<b>Platform<\/b><br />' + window.navigator.platform + '<br /><br />' ) ;\r
+\r
+var sUserLang = '?' ;\r
+\r
+if ( window.navigator.language )\r
+       sUserLang = window.navigator.language.toLowerCase() ;\r
+else if ( window.navigator.userLanguage )\r
+       sUserLang = window.navigator.userLanguage.toLowerCase() ;\r
+\r
+document.write( '<b>User Language<\/b><br />' + sUserLang ) ;\r
+//-->\r
+                                       </script>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </div>\r
+</body>\r
+</html>\r
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 (file)
index 0000000..b7d6bc6
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fckeditor.gif 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 (file)
index 0000000..3108dd9
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/dialog/fck_about/logo_fredck.gif 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 (file)
index 0000000..a9f2f50
--- /dev/null
@@ -0,0 +1,236 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Anchor dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Anchor Properties</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta content="noindex, nofollow" name="robots">\r
+               <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+               <script type="text/javascript">\r
+\r
+var oEditor    = window.parent.InnerDialogLoaded() ;\r
+var FCK                = oEditor.FCK ;\r
+var FCKBrowserInfo = oEditor.FCKBrowserInfo ;\r
+var FCKTools = oEditor.FCKTools ;\r
+var FCKRegexLib = oEditor.FCKRegexLib ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+var oFakeImage = FCK.Selection.GetSelectedElement() ;\r
+var oAnchor ;\r
+\r
+if ( oFakeImage )\r
+{\r
+       if ( oFakeImage.tagName == 'IMG' && oFakeImage.getAttribute('_fckanchor') )\r
+               oAnchor = FCK.GetRealElement( oFakeImage ) ;\r
+       else\r
+               oFakeImage = null ;\r
+}\r
+\r
+//Search for a real anchor\r
+if ( !oFakeImage )\r
+{\r
+       oAnchor = FCK.Selection.MoveToAncestorNode( 'A' ) ;\r
+       if ( oAnchor )\r
+               FCK.Selection.SelectNode( oAnchor ) ;\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( oAnchor )\r
+               GetE('txtName').value = oAnchor.name ;\r
+       else\r
+               oAnchor = null ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       var sNewName = GetE('txtName').value ;\r
+\r
+       // Remove any illegal character in a name attribute:\r
+       // A name should start with a letter, but the validator passes anyway.\r
+       sNewName = sNewName.replace( /[^\w-_\.:]/g, '_' ) ;\r
+\r
+       if ( sNewName.length == 0 )\r
+       {\r
+               // Remove the anchor if the user leaves the name blank\r
+               if ( oAnchor )\r
+               {\r
+                       RemoveAnchor() ;\r
+                       return true ;\r
+               }\r
+\r
+               alert( oEditor.FCKLang.DlgAnchorErrorName ) ;\r
+               return false ;\r
+       }\r
+\r
+       oEditor.FCKUndo.SaveUndoStep() ;\r
+\r
+       if ( oAnchor )  // Modifying an existent anchor.\r
+       {\r
+               ReadjustLinksToAnchor( oAnchor.name, sNewName );\r
+\r
+               // Buggy explorer, bad bad browser. http://alt-tag.com/blog/archives/2006/02/ie-dom-bugs/\r
+               // Instead of just replacing the .name for the existing anchor (in order to preserve the content), we must remove the .name\r
+               // and assign .name, although it won't appear until it's specially processed in fckxhtml.js\r
+\r
+               // We remove the previous name\r
+               oAnchor.removeAttribute( 'name' ) ;\r
+               // Now we set it, but later we must process it specially\r
+               oAnchor.name = sNewName ;\r
+\r
+               return true ;\r
+       }\r
+\r
+       // Create a new anchor preserving the current selection\r
+       var aNewAnchors = oEditor.FCK.CreateLink( '#' ) ;\r
+\r
+       if ( aNewAnchors.length == 0 )\r
+       {\r
+               // Nothing was selected, so now just create a normal A\r
+               aNewAnchors.push( oEditor.FCK.CreateElement( 'a' ) ) ;\r
+       }\r
+       else\r
+       {\r
+               // Remove the fake href\r
+               for ( var i = 0 ; i < aNewAnchors.length ; i++ )\r
+                       aNewAnchors[i].removeAttribute( 'href' ) ;\r
+       }\r
+\r
+       // More than one anchors may have been created, so interact through all of them (see #220).\r
+       for ( var i = 0 ; i < aNewAnchors.length ; i++ )\r
+       {\r
+               oAnchor = aNewAnchors[i] ;\r
+\r
+               // Set the name\r
+               oAnchor.name = sNewName ;\r
+\r
+               // IE does require special processing to show the Anchor's image\r
+               // Opera doesn't allow to select empty anchors\r
+               if ( FCKBrowserInfo.IsIE || FCKBrowserInfo.IsOpera )\r
+               {\r
+                       if ( oAnchor.innerHTML != '' )\r
+                       {\r
+                               if ( FCKBrowserInfo.IsIE )\r
+                                       oAnchor.className += ' FCK__AnchorC' ;\r
+                       }\r
+                       else\r
+                       {\r
+                               // Create a fake image for both IE and Opera\r
+                               var oImg = oEditor.FCKDocumentProcessor_CreateFakeImage( 'FCK__Anchor', oAnchor.cloneNode(true) ) ;\r
+                               oImg.setAttribute( '_fckanchor', 'true', 0 ) ;\r
+\r
+                               oAnchor.parentNode.insertBefore( oImg, oAnchor ) ;\r
+                               oAnchor.parentNode.removeChild( oAnchor ) ;\r
+                       }\r
+\r
+               }\r
+       }\r
+\r
+       return true ;\r
+}\r
+\r
+// Removes the current anchor from the document\r
+function RemoveAnchor()\r
+{\r
+       // If it's also a link, then just remove the name and exit\r
+       if ( oAnchor.href.length != 0 )\r
+       {\r
+               oAnchor.removeAttribute( 'name' ) ;\r
+               // Remove temporary class for IE\r
+               if ( FCKBrowserInfo.IsIE )\r
+                       oAnchor.className = oAnchor.className.replace( FCKRegexLib.FCK_Class, '' ) ;\r
+               return ;\r
+       }\r
+\r
+       // We need to remove the anchor\r
+       // If we got a fake image, then just remove it and we're done\r
+       if ( oFakeImage )\r
+       {\r
+               oFakeImage.parentNode.removeChild( oFakeImage ) ;\r
+               return ;\r
+       }\r
+       // Empty anchor, so just remove it\r
+       if ( oAnchor.innerHTML.length == 0 )\r
+       {\r
+               oAnchor.parentNode.removeChild( oAnchor ) ;\r
+               return ;\r
+       }\r
+       // Anchor with content, leave the content\r
+       FCKTools.RemoveOuterTags( oAnchor ) ;\r
+}\r
+\r
+// Checks all the links in the current page pointing to the current name and changes them to the new name\r
+function ReadjustLinksToAnchor( sCurrent, sNew )\r
+{\r
+       var oDoc = FCK.EditorDocument ;\r
+\r
+       var aLinks = oDoc.getElementsByTagName( 'A' ) ;\r
+\r
+       var sReference = '#' + sCurrent ;\r
+       // The url of the document, so we check absolute and partial references.\r
+       var sFullReference = oDoc.location.href.replace( /(#.*$)/, '') ;\r
+       sFullReference += sReference ;\r
+\r
+       var oLink ;\r
+       var i = aLinks.length - 1 ;\r
+       while ( i >= 0 && ( oLink = aLinks[i--] ) )\r
+       {\r
+               var sHRef = oLink.getAttribute( '_fcksavedurl' ) ;\r
+               if ( sHRef == null )\r
+                       sHRef = oLink.getAttribute( 'href' , 2 ) || '' ;\r
+\r
+               if ( sHRef == sReference || sHRef == sFullReference )\r
+               {\r
+                       oLink.href = '#' + sNew ;\r
+                       SetAttribute( oLink, '_fcksavedurl', '#' + sNew ) ;\r
+               }\r
+       }\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body style="OVERFLOW: hidden" scroll="no">\r
+               <table height="100%" width="100%">\r
+                       <tr>\r
+                               <td align="center">\r
+                                       <table border="0" cellpadding="0" cellspacing="0" width="80%">\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fckLang="DlgAnchorName">Anchor Name</span><BR>\r
+                                                               <input id="txtName" style="WIDTH: 100%" type="text">\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_button.html b/httemplate/elements/fckeditor/editor/dialog/fck_button.html
new file mode 100644 (file)
index 0000000..6e5c2bb
--- /dev/null
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Button dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>Button Properties</title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta content="noindex, nofollow" name="robots" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+var oActiveEl = oEditor.FCKSelection.GetSelectedElement() ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( oActiveEl && oActiveEl.tagName.toUpperCase() == "INPUT" && ( oActiveEl.type == "button" || oActiveEl.type == "submit" || oActiveEl.type == "reset" ) )\r
+       {\r
+               GetE('txtName').value   = oActiveEl.name ;\r
+               GetE('txtValue').value  = oActiveEl.value ;\r
+               GetE('txtType').value   = oActiveEl.type ;\r
+\r
+               GetE('txtType').disabled = true ;\r
+       }\r
+       else\r
+               oActiveEl = null ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( !oActiveEl )\r
+       {\r
+               oActiveEl = oEditor.FCK.EditorDocument.createElement( 'INPUT' ) ;\r
+               oActiveEl.type = GetE('txtType').value ;\r
+               oActiveEl = oEditor.FCK.InsertElementAndGetIt( oActiveEl ) ;\r
+       }\r
+\r
+       oActiveEl.name = GetE('txtName').value ;\r
+       SetAttribute( oActiveEl, 'value', GetE('txtValue').value ) ;\r
+\r
+       return true ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden">\r
+       <table width="100%" style="height: 100%">\r
+               <tr>\r
+                       <td align="center">\r
+                               <table border="0" cellpadding="0" cellspacing="0" width="80%">\r
+                                       <tr>\r
+                                               <td colspan="">\r
+                                                       <span fcklang="DlgCheckboxName">Name</span><br />\r
+                                                       <input type="text" size="20" id="txtName" style="width: 100%" />\r
+                                               </td>\r
+                                       </tr>\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgButtonText">Text (Value)</span><br />\r
+                                                       <input type="text" id="txtValue" style="width: 100%" />\r
+                                               </td>\r
+                                       </tr>\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgButtonType">Type</span><br />\r
+                                                       <select id="txtType">\r
+                                                               <option fcklang="DlgButtonTypeBtn" value="button" selected="selected">Button</option>\r
+                                                               <option fcklang="DlgButtonTypeSbm" value="submit">Submit</option>\r
+                                                               <option fcklang="DlgButtonTypeRst" value="reset">Reset</option>\r
+                                                       </select>\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_checkbox.html b/httemplate/elements/fckeditor/editor/dialog/fck_checkbox.html
new file mode 100644 (file)
index 0000000..ac7b4f3
--- /dev/null
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Checkbox dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Checkbox Properties</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta content="noindex, nofollow" name="robots">\r
+               <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+               <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+var oActiveEl = oEditor.FCKSelection.GetSelectedElement() ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( oActiveEl && oActiveEl.tagName == 'INPUT' && oActiveEl.type == 'checkbox' )\r
+       {\r
+               GetE('txtName').value           = oActiveEl.name ;\r
+               GetE('txtValue').value          = oEditor.FCKBrowserInfo.IsIE ? oActiveEl.value : GetAttribute( oActiveEl, 'value' ) ;\r
+               GetE('txtSelected').checked     = oActiveEl.checked ;\r
+       }\r
+       else\r
+               oActiveEl = null ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( !oActiveEl )\r
+       {\r
+               oActiveEl = oEditor.FCK.EditorDocument.createElement( 'INPUT' ) ;\r
+               oActiveEl.type = 'checkbox' ;\r
+               oActiveEl = oEditor.FCK.InsertElementAndGetIt( oActiveEl ) ;\r
+       }\r
+\r
+       if ( GetE('txtName').value.length > 0 )\r
+               oActiveEl.name = GetE('txtName').value ;\r
+\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+               oActiveEl.value = GetE('txtValue').value ;\r
+       else\r
+               SetAttribute( oActiveEl, 'value', GetE('txtValue').value ) ;\r
+\r
+       var bIsChecked = GetE('txtSelected').checked ;\r
+       SetAttribute( oActiveEl, 'checked', bIsChecked ? 'checked' : null ) ;   // For Firefox\r
+       oActiveEl.checked = bIsChecked ;\r
+\r
+       return true ;\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body style="OVERFLOW: hidden" scroll="no">\r
+               <table height="100%" width="100%">\r
+                       <tr>\r
+                               <td align="center">\r
+                                       <table border="0" cellpadding="0" cellspacing="0" width="80%">\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fckLang="DlgCheckboxName">Name</span><br>\r
+                                                               <input type="text" size="20" id="txtName" style="WIDTH: 100%">\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fckLang="DlgCheckboxValue">Value</span><br>\r
+                                                               <input type="text" size="20" id="txtValue" style="WIDTH: 100%">\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td><input type="checkbox" id="txtSelected"><label for="txtSelected" fckLang="DlgCheckboxSelected">Checked</label></td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_colorselector.html b/httemplate/elements/fckeditor/editor/dialog/fck_colorselector.html
new file mode 100644 (file)
index 0000000..1778f51
--- /dev/null
@@ -0,0 +1,171 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Color Selection dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+               <meta name="robots" content="noindex, nofollow" />\r
+               <style TYPE="text/css">\r
+                       #ColorTable             { cursor: pointer ; cursor: hand ; }\r
+                       #hicolor                { height: 74px ; width: 74px ; border-width: 1px ; border-style: solid ; }\r
+                       #hicolortext    { width: 75px ; text-align: right ; margin-bottom: 7px ; }\r
+                       #selhicolor             { height: 20px ; width: 74px ; border-width: 1px ; border-style: solid ; }\r
+                       #selcolor               { width: 75px ; height: 20px ; margin-top: 0px ; margin-bottom: 7px ; }\r
+                       #btnClear               { width: 75px ; height: 22px ; margin-bottom: 6px ; }\r
+                       .ColorCell              { height: 15px ; width: 15px ; }\r
+               </style>\r
+               <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+function OnLoad()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       CreateColorTable() ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+       window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+function CreateColorTable()\r
+{\r
+       // Get the target table.\r
+       var oTable = document.getElementById('ColorTable') ;\r
+\r
+       // Create the base colors array.\r
+       var aColors = ['00','33','66','99','cc','ff'] ;\r
+\r
+       // This function combines two ranges of three values from the color array into a row.\r
+       function AppendColorRow( rangeA, rangeB )\r
+       {\r
+               for ( var i = rangeA ; i < rangeA + 3 ; i++ )\r
+               {\r
+                       var oRow = oTable.insertRow(-1) ;\r
+\r
+                       for ( var j = rangeB ; j < rangeB + 3 ; j++ )\r
+                       {\r
+                               for ( var n = 0 ; n < 6 ; n++ )\r
+                               {\r
+                                       AppendColorCell( oRow, '#' + aColors[j] + aColors[n] + aColors[i] ) ;\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       // This function create a single color cell in the color table.\r
+       function AppendColorCell( targetRow, color )\r
+       {\r
+               var oCell = targetRow.insertCell(-1) ;\r
+               oCell.className = 'ColorCell' ;\r
+               oCell.bgColor = color ;\r
+\r
+               oCell.onmouseover = function()\r
+               {\r
+                       document.getElementById('hicolor').style.backgroundColor = this.bgColor ;\r
+                       document.getElementById('hicolortext').innerHTML = this.bgColor ;\r
+               }\r
+\r
+               oCell.onclick = function()\r
+               {\r
+                       document.getElementById('selhicolor').style.backgroundColor = this.bgColor ;\r
+                       document.getElementById('selcolor').value = this.bgColor ;\r
+               }\r
+       }\r
+\r
+       AppendColorRow( 0, 0 ) ;\r
+       AppendColorRow( 3, 0 ) ;\r
+       AppendColorRow( 0, 3 ) ;\r
+       AppendColorRow( 3, 3 ) ;\r
+\r
+       // Create the last row.\r
+       var oRow = oTable.insertRow(-1) ;\r
+\r
+       // Create the gray scale colors cells.\r
+       for ( var n = 0 ; n < 6 ; n++ )\r
+       {\r
+               AppendColorCell( oRow, '#' + aColors[n] + aColors[n] + aColors[n] ) ;\r
+       }\r
+\r
+       // Fill the row with black cells.\r
+       for ( var i = 0 ; i < 12 ; i++ )\r
+       {\r
+               AppendColorCell( oRow, '#000000' ) ;\r
+       }\r
+}\r
+\r
+function Clear()\r
+{\r
+       document.getElementById('selhicolor').style.backgroundColor = '' ;\r
+       document.getElementById('selcolor').value = '' ;\r
+}\r
+\r
+function ClearActual()\r
+{\r
+       document.getElementById('hicolor').style.backgroundColor = '' ;\r
+       document.getElementById('hicolortext').innerHTML = '&nbsp;' ;\r
+}\r
+\r
+function UpdateColor()\r
+{\r
+       try               { document.getElementById('selhicolor').style.backgroundColor = document.getElementById('selcolor').value ; }\r
+       catch (e) { Clear() ; }\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( typeof(window.parent.dialogArguments.CustomValue) == 'function' )\r
+               window.parent.dialogArguments.CustomValue( document.getElementById('selcolor').value ) ;\r
+\r
+       return true ;\r
+}\r
+               </script>\r
+       </head>\r
+       <body onload="OnLoad()" scroll="no" style="OVERFLOW: hidden">\r
+               <table cellpadding="0" cellspacing="0" border="0" width="100%" height="100%">\r
+                       <tr>\r
+                               <td align="center" valign="middle">\r
+                                       <table border="0" cellspacing="5" cellpadding="0" width="100%">\r
+                                               <tr>\r
+                                                       <td valign="top" align="center" nowrap width="100%">\r
+                                                               <table id="ColorTable" border="0" cellspacing="0" cellpadding="0" width="270" onmouseout="ClearActual();">\r
+                                                               </table>\r
+                                                       </td>\r
+                                                       <td valign="top" align="left" nowrap>\r
+                                                               <span fckLang="DlgColorHighlight">Highlight</span>\r
+                                                               <div id="hicolor"></div>\r
+                                                               <div id="hicolortext">&nbsp;</div>\r
+                                                               <span fckLang="DlgColorSelected">Selected</span>\r
+                                                               <div id="selhicolor"></div>\r
+                                                               <input id="selcolor" type="text" maxlength="20" onchange="UpdateColor();">\r
+                                                               <br>\r
+                                                               <input id="btnClear" type="button" fckLang="DlgColorBtnClear" value="Clear" onclick="Clear();" />\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_docprops.html b/httemplate/elements/fckeditor/editor/dialog/fck_docprops.html
new file mode 100644 (file)
index 0000000..3083466
--- /dev/null
@@ -0,0 +1,600 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Link dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta content="noindex, nofollow" name="robots" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor            = window.parent.InnerDialogLoaded() ;\r
+var FCK                        = oEditor.FCK ;\r
+var FCKLang            = oEditor.FCKLang ;\r
+var FCKConfig  = oEditor.FCKConfig ;\r
+\r
+//#### Dialog Tabs\r
+\r
+// Set the dialog tabs.\r
+window.parent.AddTab( 'General'                , FCKLang.DlgDocGeneralTab ) ;\r
+window.parent.AddTab( 'Background'     , FCKLang.DlgDocBackTab ) ;\r
+window.parent.AddTab( 'Colors'         , FCKLang.DlgDocColorsTab ) ;\r
+window.parent.AddTab( 'Meta'           , FCKLang.DlgDocMetaTab ) ;\r
+\r
+// Function called when a dialog tag is selected.\r
+function OnDialogTabChange( tabCode )\r
+{\r
+       ShowE( 'divGeneral'             , ( tabCode == 'General' ) ) ;\r
+       ShowE( 'divBackground'  , ( tabCode == 'Background' ) ) ;\r
+       ShowE( 'divColors'              , ( tabCode == 'Colors' ) ) ;\r
+       ShowE( 'divMeta'                , ( tabCode == 'Meta' ) ) ;\r
+\r
+       ShowE( 'ePreview'               , ( tabCode == 'Background' || tabCode == 'Colors' ) ) ;\r
+}\r
+\r
+//#### Get Base elements from the document: BEGIN\r
+\r
+// The HTML element of the document.\r
+var oHTML = FCK.EditorDocument.getElementsByTagName('html')[0] ;\r
+\r
+// The HEAD element of the document.\r
+var oHead = oHTML.getElementsByTagName('head')[0] ;\r
+\r
+var oBody = FCK.EditorDocument.body ;\r
+\r
+// This object contains all META tags defined in the document.\r
+var oMetaTags = new Object() ;\r
+\r
+// Get all META tags defined in the document.\r
+AppendMetaCollection( oMetaTags, oHead.getElementsByTagName('meta') ) ;\r
+AppendMetaCollection( oMetaTags, oHead.getElementsByTagName('fck:meta') ) ;\r
+\r
+function AppendMetaCollection( targetObject, metaCollection )\r
+{\r
+       // Loop throw all METAs and put it in the HashTable.\r
+       for ( var i = 0 ; i < metaCollection.length ; i++ )\r
+       {\r
+               // Try to get the "name" attribute.\r
+               var sName = GetAttribute( metaCollection[i], 'name', GetAttribute( metaCollection[i], '___fcktoreplace:name', '' ) ) ;\r
+\r
+               // If no "name", try with the "http-equiv" attribute.\r
+               if ( sName.length == 0 )\r
+               {\r
+                       if ( oEditor.FCKBrowserInfo.IsIE )\r
+                       {\r
+                               // Get the http-equiv value from the outerHTML.\r
+                               var oHttpEquivMatch = metaCollection[i].outerHTML.match( oEditor.FCKRegexLib.MetaHttpEquiv ) ;\r
+                               if ( oHttpEquivMatch )\r
+                                       sName = oHttpEquivMatch[1] ;\r
+                       }\r
+                       else\r
+                               sName = GetAttribute( metaCollection[i], 'http-equiv', '' ) ;\r
+               }\r
+\r
+               if ( sName.length > 0 )\r
+                       targetObject[ sName.toLowerCase() ] = metaCollection[i] ;\r
+       }\r
+}\r
+\r
+//#### END\r
+\r
+// Set a META tag in the document.\r
+function SetMetadata( name, content, isHttp )\r
+{\r
+       if ( content.length == 0 )\r
+       {\r
+               RemoveMetadata( name ) ;\r
+               return ;\r
+       }\r
+\r
+       var oMeta = oMetaTags[ name.toLowerCase() ] ;\r
+\r
+       if ( !oMeta )\r
+       {\r
+               oMeta = oHead.appendChild( FCK.EditorDocument.createElement('META') ) ;\r
+\r
+               if ( isHttp )\r
+                       SetAttribute( oMeta, 'http-equiv', name ) ;\r
+               else\r
+               {\r
+                       // On IE, it is not possible to set the "name" attribute of the META tag.\r
+                       // So a temporary attribute is used and it is replaced when getting the\r
+                       // editor's HTML/XHTML value. This is sad, I know :(\r
+                       if ( oEditor.FCKBrowserInfo.IsIE )\r
+                               SetAttribute( oMeta, '___fcktoreplace:name', name ) ;\r
+                       else\r
+                               SetAttribute( oMeta, 'name', name ) ;\r
+               }\r
+\r
+               oMetaTags[ name.toLowerCase() ] = oMeta ;\r
+       }\r
+\r
+       SetAttribute( oMeta, 'content', content ) ;\r
+//     oMeta.content = content ;\r
+}\r
+\r
+function RemoveMetadata( name )\r
+{\r
+       var oMeta = oMetaTags[ name.toLowerCase() ] ;\r
+\r
+       if ( oMeta && oMeta != null )\r
+       {\r
+               oMeta.parentNode.removeChild( oMeta ) ;\r
+               oMetaTags[ name.toLowerCase() ] = null ;\r
+       }\r
+}\r
+\r
+function GetMetadata( name )\r
+{\r
+       var oMeta = oMetaTags[ name.toLowerCase() ] ;\r
+\r
+       if ( oMeta && oMeta != null )\r
+               return oMeta.getAttribute( 'content', 2 ) ;\r
+       else\r
+               return '' ;\r
+}\r
+\r
+window.onload = function ()\r
+{\r
+       // Show/Hide the "Browse Server" button.\r
+       GetE('tdBrowse').style.display = oEditor.FCKConfig.ImageBrowser ? "" : "none";\r
+\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage( document ) ;\r
+\r
+       FillFields() ;\r
+\r
+       UpdatePreview() ;\r
+\r
+       // Show the "Ok" button.\r
+       window.parent.SetOkButton( true ) ;\r
+\r
+       window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+function FillFields()\r
+{\r
+       // ### General Info\r
+       GetE('txtPageTitle').value = FCK.EditorDocument.title ;\r
+\r
+       GetE('selDirection').value      = GetAttribute( oHTML, 'dir', '' ) ;\r
+       GetE('txtLang').value           = GetAttribute( oHTML, 'xml:lang', GetAttribute( oHTML, 'lang', '' ) ) ;        // "xml:lang" takes precedence to "lang".\r
+\r
+       // Character Set Encoding.\r
+//     if ( oEditor.FCKBrowserInfo.IsIE )\r
+//             var sCharSet = FCK.EditorDocument.charset ;\r
+//     else\r
+               var sCharSet = GetMetadata( 'Content-Type' ) ;\r
+\r
+       if ( sCharSet != null && sCharSet.length > 0 )\r
+       {\r
+//             if ( !oEditor.FCKBrowserInfo.IsIE )\r
+                       sCharSet = sCharSet.match( /[^=]*$/ ) ;\r
+\r
+               GetE('selCharSet').value = sCharSet ;\r
+\r
+               if ( GetE('selCharSet').selectedIndex == -1 )\r
+               {\r
+                       GetE('selCharSet').value = '...' ;\r
+                       GetE('txtCustomCharSet').value = sCharSet ;\r
+\r
+                       CheckOther( GetE('selCharSet'), 'txtCustomCharSet' ) ;\r
+               }\r
+       }\r
+\r
+       // Document Type.\r
+       if ( FCK.DocTypeDeclaration && FCK.DocTypeDeclaration.length > 0 )\r
+       {\r
+               GetE('selDocType').value = FCK.DocTypeDeclaration ;\r
+\r
+               if ( GetE('selDocType').selectedIndex == -1 )\r
+               {\r
+                       GetE('selDocType').value = '...' ;\r
+                       GetE('txtDocType').value = FCK.DocTypeDeclaration ;\r
+\r
+                       CheckOther( GetE('selDocType'), 'txtDocType' ) ;\r
+               }\r
+       }\r
+\r
+       // Document Type.\r
+       GetE('chkIncXHTMLDecl').checked = ( FCK.XmlDeclaration && FCK.XmlDeclaration.length > 0 ) ;\r
+\r
+       // ### Background\r
+       GetE('txtBackColor').value = GetAttribute( oBody, 'bgColor'             , '' ) ;\r
+       GetE('txtBackImage').value = GetAttribute( oBody, 'background'  , '' ) ;\r
+       GetE('chkBackNoScroll').checked = ( GetAttribute( oBody, 'bgProperties', '' ).toLowerCase() == 'fixed' ) ;\r
+\r
+       // ### Colors\r
+       GetE('txtColorText').value              = GetAttribute( oBody, 'text'   , '' ) ;\r
+       GetE('txtColorLink').value              = GetAttribute( oBody, 'link'   , '' ) ;\r
+       GetE('txtColorVisited').value   = GetAttribute( oBody, 'vLink'  , '' ) ;\r
+       GetE('txtColorActive').value    = GetAttribute( oBody, 'aLink'  , '' ) ;\r
+\r
+       // ### Margins\r
+       GetE('txtMarginTop').value              = GetAttribute( oBody, 'topMargin'              , '' ) ;\r
+       GetE('txtMarginLeft').value             = GetAttribute( oBody, 'leftMargin'             , '' ) ;\r
+       GetE('txtMarginRight').value    = GetAttribute( oBody, 'rightMargin'    , '' ) ;\r
+       GetE('txtMarginBottom').value   = GetAttribute( oBody, 'bottomMargin'   , '' ) ;\r
+\r
+       // ### Meta Data\r
+       GetE('txtMetaKeywords').value           = GetMetadata( 'keywords' ) ;\r
+       GetE('txtMetaDescription').value        = GetMetadata( 'description' ) ;\r
+       GetE('txtMetaAuthor').value                     = GetMetadata( 'author' ) ;\r
+       GetE('txtMetaCopyright').value          = GetMetadata( 'copyright' ) ;\r
+}\r
+\r
+// Called when the "Ok" button is clicked.\r
+function Ok()\r
+{\r
+       // ### General Info\r
+       FCK.EditorDocument.title = GetE('txtPageTitle').value ;\r
+\r
+       var oHTML = FCK.EditorDocument.getElementsByTagName('html')[0] ;\r
+\r
+       SetAttribute( oHTML, 'dir'              , GetE('selDirection').value ) ;\r
+       SetAttribute( oHTML, 'lang'             , GetE('txtLang').value ) ;\r
+       SetAttribute( oHTML, 'xml:lang' , GetE('txtLang').value ) ;\r
+\r
+       // Character Set Enconding.\r
+       var sCharSet = GetE('selCharSet').value ;\r
+       if ( sCharSet == '...' )\r
+               sCharSet = GetE('txtCustomCharSet').value ;\r
+\r
+       if ( sCharSet.length > 0 )\r
+                       sCharSet = 'text/html; charset=' + sCharSet ;\r
+\r
+//     if ( oEditor.FCKBrowserInfo.IsIE )\r
+//             FCK.EditorDocument.charset = sCharSet ;\r
+//     else\r
+               SetMetadata( 'Content-Type', sCharSet, true ) ;\r
+\r
+       // Document Type\r
+       var sDocType = GetE('selDocType').value ;\r
+       if ( sDocType == '...' )\r
+               sDocType = GetE('txtDocType').value ;\r
+\r
+       FCK.DocTypeDeclaration = sDocType ;\r
+\r
+       // XHTML Declarations.\r
+       if ( GetE('chkIncXHTMLDecl').checked )\r
+       {\r
+               if ( sCharSet.length == 0 )\r
+                       sCharSet = 'utf-8' ;\r
+\r
+               FCK.XmlDeclaration = '<?xml version="1.0" encoding="' + sCharSet + '"?>' ;\r
+\r
+               SetAttribute( oHTML, 'xmlns', 'http://www.w3.org/1999/xhtml' ) ;\r
+       }\r
+       else\r
+       {\r
+               FCK.XmlDeclaration = null ;\r
+               oHTML.removeAttribute( 'xmlns', 0 ) ;\r
+       }\r
+\r
+       // ### Background\r
+       SetAttribute( oBody, 'bgcolor'          , GetE('txtBackColor').value ) ;\r
+       SetAttribute( oBody, 'background'       , GetE('txtBackImage').value ) ;\r
+       SetAttribute( oBody, 'bgproperties'     , GetE('chkBackNoScroll').checked ? 'fixed' : '' ) ;\r
+\r
+       // ### Colors\r
+       SetAttribute( oBody, 'text'     , GetE('txtColorText').value ) ;\r
+       SetAttribute( oBody, 'link'     , GetE('txtColorLink').value ) ;\r
+       SetAttribute( oBody, 'vlink', GetE('txtColorVisited').value ) ;\r
+       SetAttribute( oBody, 'alink', GetE('txtColorActive').value ) ;\r
+\r
+       // ### Margins\r
+       SetAttribute( oBody, 'topmargin'        , GetE('txtMarginTop').value ) ;\r
+       SetAttribute( oBody, 'leftmargin'       , GetE('txtMarginLeft').value ) ;\r
+       SetAttribute( oBody, 'rightmargin'      , GetE('txtMarginRight').value ) ;\r
+       SetAttribute( oBody, 'bottommargin'     , GetE('txtMarginBottom').value ) ;\r
+\r
+       // ### Meta data\r
+       SetMetadata( 'keywords'         , GetE('txtMetaKeywords').value ) ;\r
+       SetMetadata( 'description'      , GetE('txtMetaDescription').value ) ;\r
+       SetMetadata( 'author'           , GetE('txtMetaAuthor').value ) ;\r
+       SetMetadata( 'copyright'        , GetE('txtMetaCopyright').value ) ;\r
+\r
+       return true ;\r
+}\r
+\r
+var bPreviewIsLoaded = false ;\r
+var oPreviewWindow ;\r
+var oPreviewBody ;\r
+\r
+// Called by the Preview page when loaded.\r
+function OnPreviewLoad( previewWindow, previewBody )\r
+{\r
+       oPreviewWindow  = previewWindow ;\r
+       oPreviewBody    = previewBody ;\r
+\r
+       bPreviewIsLoaded = true ;\r
+       UpdatePreview() ;\r
+}\r
+\r
+function UpdatePreview()\r
+{\r
+       if ( !bPreviewIsLoaded )\r
+               return ;\r
+\r
+       // ### Background\r
+       SetAttribute( oPreviewBody, 'bgcolor'           , GetE('txtBackColor').value ) ;\r
+       SetAttribute( oPreviewBody, 'background'        , GetE('txtBackImage').value ) ;\r
+       SetAttribute( oPreviewBody, 'bgproperties'      , GetE('chkBackNoScroll').checked ? 'fixed' : '' ) ;\r
+\r
+       // ### Colors\r
+       SetAttribute( oPreviewBody, 'text', GetE('txtColorText').value ) ;\r
+\r
+       oPreviewWindow.SetLinkColor( GetE('txtColorLink').value ) ;\r
+       oPreviewWindow.SetVisitedColor( GetE('txtColorVisited').value ) ;\r
+       oPreviewWindow.SetActiveColor( GetE('txtColorActive').value ) ;\r
+}\r
+\r
+function CheckOther( combo, txtField )\r
+{\r
+       var bNotOther = ( combo.value != '...' ) ;\r
+\r
+       GetE(txtField).style.backgroundColor = ( bNotOther ? '#cccccc' : '' ) ;\r
+       GetE(txtField).disabled = bNotOther ;\r
+}\r
+\r
+function SetColor( inputId, color )\r
+{\r
+       GetE( inputId ).value = color + '' ;\r
+       UpdatePreview() ;\r
+}\r
+\r
+function SelectBackColor( color )              { SetColor('txtBackColor', color ) ; }\r
+function SelectColorText( color )              { SetColor('txtColorText', color ) ; }\r
+function SelectColorLink( color )              { SetColor('txtColorLink', color ) ; }\r
+function SelectColorVisited( color )   { SetColor('txtColorVisited', color ) ; }\r
+function SelectColorActive( color )            { SetColor('txtColorActive', color ) ; }\r
+\r
+function SelectColor( wich )\r
+{\r
+       switch ( wich )\r
+       {\r
+               case 'Back'                     : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 400, 330, SelectBackColor, window ) ; return ;\r
+               case 'ColorText'        : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 400, 330, SelectColorText, window ) ; return ;\r
+               case 'ColorLink'        : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 400, 330, SelectColorLink, window ) ; return ;\r
+               case 'ColorVisited'     : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 400, 330, SelectColorVisited, window ) ; return ;\r
+               case 'ColorActive'      : oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 400, 330, SelectColorActive, window ) ; return ;\r
+       }\r
+}\r
+\r
+function BrowseServerBack()\r
+{\r
+       OpenFileBrowser( FCKConfig.ImageBrowserURL, FCKConfig.ImageBrowserWindowWidth, FCKConfig.ImageBrowserWindowHeight ) ;\r
+}\r
+\r
+function SetUrl( url )\r
+{\r
+       GetE('txtBackImage').value = url ;\r
+       UpdatePreview() ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden">\r
+       <table cellspacing="0" cellpadding="0" width="100%" border="0" style="height: 100%">\r
+               <tr>\r
+                       <td valign="top" style="height: 100%">\r
+                               <div id="divGeneral">\r
+                                       <span fcklang="DlgDocPageTitle">Page Title</span><br />\r
+                                       <input id="txtPageTitle" style="width: 100%" type="text" />\r
+                                       <br />\r
+                                       <table cellspacing="0" cellpadding="0" border="0">\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fcklang="DlgDocLangDir">Language Direction</span><br />\r
+                                                               <select id="selDirection">\r
+                                                                       <option value="" selected="selected"></option>\r
+                                                                       <option value="ltr" fcklang="DlgDocLangDirLTR">Left to Right (LTR)</option>\r
+                                                                       <option value="rtl" fcklang="DlgDocLangDirRTL">Right to Left (RTL)</option>\r
+                                                               </select>\r
+                                                       </td>\r
+                                                       <td>\r
+                                                               &nbsp;&nbsp;&nbsp;</td>\r
+                                                       <td>\r
+                                                               <span fcklang="DlgDocLangCode">Language Code</span><br />\r
+                                                               <input id="txtLang" type="text" />\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                                       <br />\r
+                                       <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                                               <tr>\r
+                                                       <td style="white-space: nowrap">\r
+                                                               <span fcklang="DlgDocCharSet">Character Set Encoding</span><br />\r
+                                                               <select id="selCharSet" onchange="CheckOther( this, 'txtCustomCharSet' );">\r
+                                                                       <option value="" selected="selected"></option>\r
+                                                                       <option value="us-ascii">ASCII</option>\r
+                                                                       <option fcklang="DlgDocCharSetCE" value="iso-8859-2">Central European</option>\r
+                                                                       <option fcklang="DlgDocCharSetCT" value="big5">Chinese Traditional (Big5)</option>\r
+                                                                       <option fcklang="DlgDocCharSetCR" value="iso-8859-5">Cyrillic</option>\r
+                                                                       <option fcklang="DlgDocCharSetGR" value="iso-8859-7">Greek</option>\r
+                                                                       <option fcklang="DlgDocCharSetJP" value="iso-2022-jp">Japanese</option>\r
+                                                                       <option fcklang="DlgDocCharSetKR" value="iso-2022-kr">Korean</option>\r
+                                                                       <option fcklang="DlgDocCharSetTR" value="iso-8859-9">Turkish</option>\r
+                                                                       <option fcklang="DlgDocCharSetUN" value="utf-8">Unicode (UTF-8)</option>\r
+                                                                       <option fcklang="DlgDocCharSetWE" value="iso-8859-1">Western European</option>\r
+                                                                       <option fcklang="DlgOpOther" value="...">&lt;Other&gt;</option>\r
+                                                               </select>\r
+                                                       </td>\r
+                                                       <td>\r
+                                                               &nbsp;&nbsp;&nbsp;</td>\r
+                                                       <td width="100%">\r
+                                                               <span fcklang="DlgDocCharSetOther">Other Character Set Encoding</span><br />\r
+                                                               <input id="txtCustomCharSet" style="width: 100%; background-color: #cccccc" disabled="disabled"\r
+                                                                       type="text" />\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td colspan="3">\r
+                                                               &nbsp;</td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td nowrap="nowrap">\r
+                                                               <span fcklang="DlgDocDocType">Document Type Heading</span><br />\r
+                                                               <select id="selDocType" name="selDocType" onchange="CheckOther( this, 'txtDocType' );">\r
+                                                                       <option value="" selected="selected"></option>\r
+                                                                       <option value='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'>HTML\r
+                                                                               4.01 Transitional</option>\r
+                                                                       <option value='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'>\r
+                                                                               HTML 4.01 Strict</option>\r
+                                                                       <option value='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'>\r
+                                                                               HTML 4.01 Frameset</option>\r
+                                                                       <option value='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'>\r
+                                                                               XHTML 1.0 Transitional</option>\r
+                                                                       <option value='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'>\r
+                                                                               XHTML 1.0 Strict</option>\r
+                                                                       <option value='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'>\r
+                                                                               XHTML 1.0 Frameset</option>\r
+                                                                       <option value='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'>\r
+                                                                               XHTML 1.1</option>\r
+                                                                       <option value='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'>HTML 3.2</option>\r
+                                                                       <option value='<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">'>HTML 2.0</option>\r
+                                                                       <option value="..." fcklang="DlgOpOther">&lt;Other&gt;</option>\r
+                                                               </select>\r
+                                                       </td>\r
+                                                       <td>\r
+                                                       </td>\r
+                                                       <td width="100%">\r
+                                                               <span fcklang="DlgDocDocTypeOther">Other Document Type Heading</span><br />\r
+                                                               <input id="txtDocType" style="width: 100%; background-color: #cccccc" disabled="disabled"\r
+                                                                       type="text" />\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                                       <br />\r
+                                       <input id="chkIncXHTMLDecl" type="checkbox" />\r
+                                       <label for="chkIncXHTMLDecl" fcklang="DlgDocIncXHTML">\r
+                                               Include XHTML Declarations</label>\r
+                               </div>\r
+                               <div id="divBackground" style="display: none">\r
+                                       <span fcklang="DlgDocBgColor">Background Color</span><br />\r
+                                       <input id="txtBackColor" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" />&nbsp;<input\r
+                                               id="btnSelBackColor" onclick="SelectColor( 'Back' )" type="button" value="Select..."\r
+                                               fcklang="DlgCellBtnSelect" /><br />\r
+                                       <br />\r
+                                       <span fcklang="DlgDocBgImage">Background Image URL</span><br />\r
+                                       <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                                               <tr>\r
+                                                       <td width="100%">\r
+                                                               <input id="txtBackImage" style="width: 100%" type="text" onchange="UpdatePreview();"\r
+                                                                       onkeyup="UpdatePreview();" /></td>\r
+                                                       <td id="tdBrowse" nowrap="nowrap">\r
+                                                               &nbsp;<input id="btnBrowse" onclick="BrowseServerBack();" type="button" fcklang="DlgBtnBrowseServer"\r
+                                                                       value="Browse Server" /></td>\r
+                                               </tr>\r
+                                       </table>\r
+                                       <input id="chkBackNoScroll" type="checkbox" onclick="UpdatePreview();" />\r
+                                       <label for="chkBackNoScroll" fcklang="DlgDocBgNoScroll">\r
+                                               Nonscrolling Background</label>\r
+                               </div>\r
+                               <div id="divColors" style="display: none">\r
+                                       <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fcklang="DlgDocCText">Text</span><br />\r
+                                                               <input id="txtColorText" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" /><input\r
+                                                                       onclick="SelectColor( 'ColorText' )" type="button" value="Select..." fcklang="DlgCellBtnSelect" />\r
+                                                               <br />\r
+                                                               <span fcklang="DlgDocCLink">Link</span><br />\r
+                                                               <input id="txtColorLink" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" /><input\r
+                                                                       onclick="SelectColor( 'ColorLink' )" type="button" value="Select..." fcklang="DlgCellBtnSelect" />\r
+                                                               <br />\r
+                                                               <span fcklang="DlgDocCVisited">Visited Link</span><br />\r
+                                                               <input id="txtColorVisited" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" /><input\r
+                                                                       onclick="SelectColor( 'ColorVisited' )" type="button" value="Select..." fcklang="DlgCellBtnSelect" />\r
+                                                               <br />\r
+                                                               <span fcklang="DlgDocCActive">Active Link</span><br />\r
+                                                               <input id="txtColorActive" type="text" onchange="UpdatePreview();" onkeyup="UpdatePreview();" /><input\r
+                                                                       onclick="SelectColor( 'ColorActive' )" type="button" value="Select..." fcklang="DlgCellBtnSelect" />\r
+                                                       </td>\r
+                                                       <td valign="middle" align="center">\r
+                                                               <table cellspacing="2" cellpadding="0" border="0">\r
+                                                                       <tr>\r
+                                                                               <td>\r
+                                                                                       <span fcklang="DlgDocMargins">Page Margins</span></td>\r
+                                                                       </tr>\r
+                                                                       <tr>\r
+                                                                               <td style="border: #000000 1px solid; padding: 5px">\r
+                                                                                       <table cellpadding="0" cellspacing="0" border="0" dir="ltr">\r
+                                                                                               <tr>\r
+                                                                                                       <td align="center" colspan="3">\r
+                                                                                                               <span fcklang="DlgDocMaTop">Top</span><br />\r
+                                                                                                               <input id="txtMarginTop" type="text" size="3" />\r
+                                                                                                       </td>\r
+                                                                                               </tr>\r
+                                                                                               <tr>\r
+                                                                                                       <td align="left">\r
+                                                                                                               <span fcklang="DlgDocMaLeft">Left</span><br />\r
+                                                                                                               <input id="txtMarginLeft" type="text" size="3" />\r
+                                                                                                       </td>\r
+                                                                                                       <td>\r
+                                                                                                               &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>\r
+                                                                                                       <td align="right">\r
+                                                                                                               <span fcklang="DlgDocMaRight">Right</span><br />\r
+                                                                                                               <input id="txtMarginRight" type="text" size="3" />\r
+                                                                                                       </td>\r
+                                                                                               </tr>\r
+                                                                                               <tr>\r
+                                                                                                       <td align="center" colspan="3">\r
+                                                                                                               <span fcklang="DlgDocMaBottom">Bottom</span><br />\r
+                                                                                                               <input id="txtMarginBottom" type="text" size="3" />\r
+                                                                                                       </td>\r
+                                                                                               </tr>\r
+                                                                                       </table>\r
+                                                                               </td>\r
+                                                                       </tr>\r
+                                                               </table>\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </div>\r
+                               <div id="divMeta" style="display: none">\r
+                                       <span fcklang="DlgDocMeIndex">Document Indexing Keywords (comma separated)</span><br />\r
+                                       <textarea id="txtMetaKeywords" style="width: 100%" rows="2" cols="20"></textarea>\r
+                                       <br />\r
+                                       <span fcklang="DlgDocMeDescr">Document Description</span><br />\r
+                                       <textarea id="txtMetaDescription" style="width: 100%" rows="4" cols="20"></textarea>\r
+                                       <br />\r
+                                       <span fcklang="DlgDocMeAuthor">Author</span><br />\r
+                                       <input id="txtMetaAuthor" style="width: 100%" type="text" /><br />\r
+                                       <br />\r
+                                       <span fcklang="DlgDocMeCopy">Copyright</span><br />\r
+                                       <input id="txtMetaCopyright" type="text" style="width: 100%" />\r
+                               </div>\r
+                       </td>\r
+               </tr>\r
+               <tr id="ePreview" style="display: none">\r
+                       <td>\r
+                               <span fcklang="DlgDocPreview">Preview</span><br />\r
+                               <iframe id="frmPreview" src="fck_docprops/fck_document_preview.html" width="100%"\r
+                                       height="100"></iframe>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
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 (file)
index 0000000..2092775
--- /dev/null
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Preview shown in the "Document Properties" dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Document Properties - Preview</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta name="robots" content="noindex, nofollow">\r
+               <script language="javascript">\r
+\r
+var eBase = parent.FCK.EditorDocument.getElementsByTagName( 'BASE' ) ;\r
+if ( eBase.length > 0 && eBase[0].href.length > 0 )\r
+{\r
+       document.write( '<base href="' + eBase[0].href + '">' ) ;\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       if ( typeof( parent.OnPreviewLoad ) == 'function' )\r
+               parent.OnPreviewLoad( window, document.body ) ;\r
+}\r
+\r
+function SetBaseHRef( baseHref )\r
+{\r
+       var eBase = document.createElement( 'BASE' ) ;\r
+       eBase.href = baseHref ;\r
+\r
+       var eHead = document.getElementsByTagName( 'HEAD' )[0] ;\r
+       eHead.appendChild( eBase ) ;\r
+}\r
+\r
+function SetLinkColor( color )\r
+{\r
+       if ( color && color.length > 0 )\r
+               document.getElementById('eLink').style.color = color ;\r
+       else\r
+               document.getElementById('eLink').style.color = window.document.linkColor ;\r
+}\r
+\r
+function SetVisitedColor( color )\r
+{\r
+       if ( color && color.length > 0 )\r
+               document.getElementById('eVisited').style.color = color ;\r
+       else\r
+               document.getElementById('eVisited').style.color = window.document.vlinkColor ;\r
+}\r
+\r
+function SetActiveColor( color )\r
+{\r
+       if ( color && color.length > 0 )\r
+               document.getElementById('eActive').style.color = color ;\r
+       else\r
+               document.getElementById('eActive').style.color = window.document.alinkColor ;\r
+}\r
+               </script>\r
+       </head>\r
+       <body>\r
+               <table width="100%" height="100%" cellpadding="0" cellspacing="0" border="0">\r
+                       <tr>\r
+                               <td align="center" valign="middle">\r
+                                       Normal Text\r
+                               </td>\r
+                               <td id="eLink" align="center" valign="middle">\r
+                                       <u>Link Text</u>\r
+                               </td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td id="eVisited" valign="middle" align="center">\r
+                                       <u>Visited Link</u>\r
+                               </td>\r
+                               <td id="eActive" valign="middle" align="center">\r
+                                       <u>Active Link</u>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+               <br>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_find.html b/httemplate/elements/fckeditor/editor/dialog/fck_find.html
new file mode 100644 (file)
index 0000000..eba7f90
--- /dev/null
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * "Find" dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta content="noindex, nofollow" name="robots" />\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+function OnLoad()\r
+{\r
+       // Whole word is available on IE only.\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+               document.getElementById('divWord').style.display = '' ;\r
+\r
+       // First of all, translate the dialog box texts.\r
+       oEditor.FCKLanguageManager.TranslatePage( document ) ;\r
+\r
+       window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+function btnStat(frm)\r
+{\r
+       document.getElementById('btnFind').disabled =\r
+               ( document.getElementById('txtFind').value.length == 0 ) ;\r
+}\r
+\r
+function ReplaceTextNodes( parentNode, regex, replaceValue, replaceAll )\r
+{\r
+       for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )\r
+       {\r
+               var oNode = parentNode.childNodes[i] ;\r
+               if ( oNode.nodeType == 3 )\r
+               {\r
+                       var sReplaced = oNode.nodeValue.replace( regex, replaceValue ) ;\r
+                       if ( oNode.nodeValue != sReplaced )\r
+                       {\r
+                               oNode.nodeValue = sReplaced ;\r
+                               if ( ! replaceAll )\r
+                                       return true ;\r
+                       }\r
+               }\r
+               else\r
+               {\r
+                       if ( ReplaceTextNodes( oNode, regex, replaceValue ) )\r
+                               return true ;\r
+               }\r
+       }\r
+       return false ;\r
+}\r
+\r
+function GetRegexExpr()\r
+{\r
+       var sExpr ;\r
+\r
+       if ( document.getElementById('chkWord').checked )\r
+               sExpr = '\\b' + document.getElementById('txtFind').value + '\\b' ;\r
+       else\r
+               sExpr = document.getElementById('txtFind').value ;\r
+\r
+       return sExpr ;\r
+}\r
+\r
+function GetCase()\r
+{\r
+       return ( document.getElementById('chkCase').checked ? '' : 'i' ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( document.getElementById('txtFind').value.length == 0 )\r
+               return ;\r
+\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+               FindIE() ;\r
+       else\r
+               FindGecko() ;\r
+}\r
+\r
+var oRange ;\r
+\r
+if ( oEditor.FCKBrowserInfo.IsIE )\r
+       oRange = oEditor.FCK.EditorDocument.body.createTextRange() ;\r
+\r
+function FindIE()\r
+{\r
+       var iFlags = 0 ;\r
+\r
+       if ( chkCase.checked )\r
+               iFlags = iFlags | 4 ;\r
+\r
+       if ( chkWord.checked )\r
+               iFlags = iFlags | 2 ;\r
+\r
+       var bFound = oRange.findText( document.getElementById('txtFind').value, 1, iFlags ) ;\r
+\r
+       if ( bFound )\r
+       {\r
+               oRange.scrollIntoView() ;\r
+               oRange.select() ;\r
+               oRange.collapse(false) ;\r
+               oLastRangeFound = oRange ;\r
+       }\r
+       else\r
+       {\r
+               oRange = oEditor.FCK.EditorDocument.body.createTextRange() ;\r
+               alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ;\r
+       }\r
+}\r
+\r
+function FindGecko()\r
+{\r
+       var bCase = document.getElementById('chkCase').checked ;\r
+       var bWord = document.getElementById('chkWord').checked ;\r
+\r
+       // window.find( searchString, caseSensitive, backwards, wrapAround, wholeWord, searchInFrames, showDialog ) ;\r
+       if ( !oEditor.FCK.EditorWindow.find( document.getElementById('txtFind').value, bCase, false, false, bWord, false, false ) )\r
+               alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ;\r
+}\r
+       </script>\r
+</head>\r
+<body onload="OnLoad()" style="overflow: hidden">\r
+       <table cellspacing="3" cellpadding="2" width="100%" border="0">\r
+               <tr>\r
+                       <td nowrap="nowrap">\r
+                               <label for="txtFind" fcklang="DlgReplaceFindLbl">\r
+                                       Find what:</label>&nbsp;\r
+                       </td>\r
+                       <td width="100%">\r
+                               <input id="txtFind" style="width: 100%" tabindex="1" type="text" />\r
+                       </td>\r
+                       <td>\r
+                               <input id="btnFind" style="padding-right: 5px; padding-left: 5px" onclick="Ok();"\r
+                                       type="button" value="Find" fcklang="DlgFindFindBtn" />\r
+                       </td>\r
+               </tr>\r
+               <tr>\r
+                       <td valign="bottom" colspan="3">\r
+                               &nbsp;<input id="chkCase" tabindex="3" type="checkbox" /><label for="chkCase" fcklang="DlgReplaceCaseChk">Match\r
+                                       case</label>\r
+                               <br />\r
+                               <div id="divWord" style="display: none">\r
+                                       &nbsp;<input id="chkWord" tabindex="4" type="checkbox" /><label for="chkWord" fcklang="DlgReplaceWordChk">Match\r
+                                               whole word</label>\r
+                               </div>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_flash.html b/httemplate/elements/fckeditor/editor/dialog/fck_flash.html
new file mode 100644 (file)
index 0000000..be529e3
--- /dev/null
@@ -0,0 +1,146 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Flash Properties dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Flash Properties</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta content="noindex, nofollow" name="robots">\r
+               <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+               <script src="fck_flash/fck_flash.js" type="text/javascript"></script>\r
+               <link href="common/fck_dialog_common.css" type="text/css" rel="stylesheet">\r
+       </head>\r
+       <body scroll="no" style="OVERFLOW: hidden">\r
+               <div id="divInfo">\r
+                       <table cellSpacing="1" cellPadding="1" width="100%" border="0">\r
+                               <tr>\r
+                                       <td>\r
+                                               <table cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                                                       <tr>\r
+                                                               <td width="100%"><span fckLang="DlgImgURL">URL</span>\r
+                                                               </td>\r
+                                                               <td id="tdBrowse" style="DISPLAY: none" noWrap rowSpan="2">&nbsp; <input id="btnBrowse" onclick="BrowseServer();" type="button" value="Browse Server" fckLang="DlgBtnBrowseServer">\r
+                                                               </td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td vAlign="top"><input id="txtUrl" onblur="UpdatePreview();" style="WIDTH: 100%" type="text">\r
+                                                               </td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                               </tr>\r
+                               <TR>\r
+                                       <TD>\r
+                                               <table cellSpacing="0" cellPadding="0" border="0">\r
+                                                       <TR>\r
+                                                               <TD nowrap>\r
+                                                                       <span fckLang="DlgImgWidth">Width</span><br>\r
+                                                                       <input id="txtWidth" class="FCK__FieldNumeric" type="text" size="3">\r
+                                                               </TD>\r
+                                                               <TD>&nbsp;</TD>\r
+                                                               <TD>\r
+                                                                       <span fckLang="DlgImgHeight">Height</span><br>\r
+                                                                       <input id="txtHeight" class="FCK__FieldNumeric" type="text" size="3">\r
+                                                               </TD>\r
+                                                       </TR>\r
+                                               </table>\r
+                                       </TD>\r
+                               </TR>\r
+                               <tr>\r
+                                       <td vAlign="top">\r
+                                               <table cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                                                       <tr>\r
+                                                               <td valign="top" width="100%">\r
+                                                                       <table cellSpacing="0" cellPadding="0" width="100%">\r
+                                                                               <tr>\r
+                                                                                       <td><span fckLang="DlgImgPreview">Preview</span></td>\r
+                                                                               </tr>\r
+                                                                               <tr>\r
+                                                                                       <td id="ePreviewCell" valign="top" class="FlashPreviewArea"><iframe src="fck_flash/fck_flash_preview.html" frameborder="0" marginheight="0" marginwidth="0"></iframe></td>\r
+                                                                               </tr>\r
+                                                                       </table>\r
+                                                               </td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                               </tr>\r
+                       </table>\r
+               </div>\r
+               <div id="divUpload" style="DISPLAY: none">\r
+                       <form id="frmUpload" method="post" target="UploadWindow" enctype="multipart/form-data" action="" onsubmit="return CheckUpload();">\r
+                               <span fckLang="DlgLnkUpload">Upload</span><br />\r
+                               <input id="txtUploadFile" style="WIDTH: 100%" type="file" size="40" name="NewFile" /><br />\r
+                               <br />\r
+                               <input id="btnUpload" type="submit" value="Send it to the Server" fckLang="DlgLnkBtnUpload" />\r
+                               <iframe name="UploadWindow" style="DISPLAY: none" src="javascript:void(0)"></iframe>\r
+                       </form>\r
+               </div>\r
+               <div id="divAdvanced" style="DISPLAY: none">\r
+                       <TABLE cellSpacing="0" cellPadding="0" border="0">\r
+                               <TR>\r
+                                       <TD nowrap>\r
+                                               <span fckLang="DlgFlashScale">Scale</span><BR>\r
+                                               <select id="cmbScale">\r
+                                                       <option value="" selected></option>\r
+                                                       <option value="showall" fckLang="DlgFlashScaleAll">Show all</option>\r
+                                                       <option value="noborder" fckLang="DlgFlashScaleNoBorder">No Border</option>\r
+                                                       <option value="exactfit" fckLang="DlgFlashScaleFit">Exact Fit</option>\r
+                                               </select></TD>\r
+                                       <TD>&nbsp;&nbsp;&nbsp; &nbsp;\r
+                                       </TD>\r
+                                       <td valign="bottom">\r
+                                               <table>\r
+                                                       <tr>\r
+                                                               <td><input id="chkAutoPlay" type="checkbox" checked></td>\r
+                                                               <td><label for="chkAutoPlay" nowrap fckLang="DlgFlashChkPlay">Auto Play</label>&nbsp;&nbsp;</td>\r
+                                                               <td><input id="chkLoop" type="checkbox" checked></td>\r
+                                                               <td><label for="chkLoop" nowrap fckLang="DlgFlashChkLoop">Loop</label>&nbsp;&nbsp;</td>\r
+                                                               <td><input id="chkMenu" type="checkbox" checked></td>\r
+                                                               <td><label for="chkMenu" nowrap fckLang="DlgFlashChkMenu">Enable Flash Menu</label></td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                               </TR>\r
+                       </TABLE>\r
+                       <br>\r
+                       &nbsp;\r
+                       <table cellSpacing="0" cellPadding="0" width="100%" align="center" border="0">\r
+                               <tr>\r
+                                       <td vAlign="top" width="50%"><span fckLang="DlgGenId">Id</span><br>\r
+                                               <input id="txtAttId" style="WIDTH: 100%" type="text">\r
+                                       </td>\r
+                                       <td>&nbsp;&nbsp;</td>\r
+                                       <td vAlign="top" nowrap><span fckLang="DlgGenClass">Stylesheet Classes</span><br>\r
+                                               <input id="txtAttClasses" style="WIDTH: 100%" type="text">\r
+                                       </td>\r
+                                       <td>&nbsp;&nbsp;</td>\r
+                                       <td vAlign="top" nowrap width="50%">&nbsp;<span fckLang="DlgGenTitle">Advisory Title</span><br>\r
+                                               <input id="txtAttTitle" style="WIDTH: 100%" type="text">\r
+                                       </td>\r
+                               </tr>\r
+                       </table>\r
+                       <span fckLang="DlgGenStyle">Style</span><br>\r
+                       <input id="txtAttStyle" style="WIDTH: 100%" type="text">\r
+               </div>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..ee97bc5
--- /dev/null
@@ -0,0 +1,286 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Scripts related to the Flash dialog window (see fck_flash.html).\r
+ */\r
+\r
+var oEditor            = window.parent.InnerDialogLoaded() ;\r
+var FCK                        = oEditor.FCK ;\r
+var FCKLang            = oEditor.FCKLang ;\r
+var FCKConfig  = oEditor.FCKConfig ;\r
+\r
+//#### Dialog Tabs\r
+\r
+// Set the dialog tabs.\r
+window.parent.AddTab( 'Info', oEditor.FCKLang.DlgInfoTab ) ;\r
+\r
+if ( FCKConfig.FlashUpload )\r
+       window.parent.AddTab( 'Upload', FCKLang.DlgLnkUpload ) ;\r
+\r
+if ( !FCKConfig.FlashDlgHideAdvanced )\r
+       window.parent.AddTab( 'Advanced', oEditor.FCKLang.DlgAdvancedTag ) ;\r
+\r
+// Function called when a dialog tag is selected.\r
+function OnDialogTabChange( tabCode )\r
+{\r
+       ShowE('divInfo'         , ( tabCode == 'Info' ) ) ;\r
+       ShowE('divUpload'       , ( tabCode == 'Upload' ) ) ;\r
+       ShowE('divAdvanced'     , ( tabCode == 'Advanced' ) ) ;\r
+}\r
+\r
+// Get the selected flash embed (if available).\r
+var oFakeImage = FCK.Selection.GetSelectedElement() ;\r
+var oEmbed ;\r
+\r
+if ( oFakeImage )\r
+{\r
+       if ( oFakeImage.tagName == 'IMG' && oFakeImage.getAttribute('_fckflash') )\r
+               oEmbed = FCK.GetRealElement( oFakeImage ) ;\r
+       else\r
+               oFakeImage = null ;\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       // Translate the dialog box texts.\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       // Load the selected element information (if any).\r
+       LoadSelection() ;\r
+\r
+       // Show/Hide the "Browse Server" button.\r
+       GetE('tdBrowse').style.display = FCKConfig.FlashBrowser ? '' : 'none' ;\r
+\r
+       // Set the actual uploader URL.\r
+       if ( FCKConfig.FlashUpload )\r
+               GetE('frmUpload').action = FCKConfig.FlashUploadURL ;\r
+\r
+       window.parent.SetAutoSize( true ) ;\r
+\r
+       // Activate the "OK" button.\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function LoadSelection()\r
+{\r
+       if ( ! oEmbed ) return ;\r
+\r
+       GetE('txtUrl').value    = GetAttribute( oEmbed, 'src', '' ) ;\r
+       GetE('txtWidth').value  = GetAttribute( oEmbed, 'width', '' ) ;\r
+       GetE('txtHeight').value = GetAttribute( oEmbed, 'height', '' ) ;\r
+\r
+       // Get Advances Attributes\r
+       GetE('txtAttId').value          = oEmbed.id ;\r
+       GetE('chkAutoPlay').checked     = GetAttribute( oEmbed, 'play', 'true' ) == 'true' ;\r
+       GetE('chkLoop').checked         = GetAttribute( oEmbed, 'loop', 'true' ) == 'true' ;\r
+       GetE('chkMenu').checked         = GetAttribute( oEmbed, 'menu', 'true' ) == 'true' ;\r
+       GetE('cmbScale').value          = GetAttribute( oEmbed, 'scale', '' ).toLowerCase() ;\r
+\r
+       GetE('txtAttTitle').value               = oEmbed.title ;\r
+\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+       {\r
+               GetE('txtAttClasses').value = oEmbed.getAttribute('className') || '' ;\r
+               GetE('txtAttStyle').value = oEmbed.style.cssText ;\r
+       }\r
+       else\r
+       {\r
+               GetE('txtAttClasses').value = oEmbed.getAttribute('class',2) || '' ;\r
+               GetE('txtAttStyle').value = oEmbed.getAttribute('style',2) || '' ;\r
+       }\r
+\r
+       UpdatePreview() ;\r
+}\r
+\r
+//#### The OK button was hit.\r
+function Ok()\r
+{\r
+       if ( GetE('txtUrl').value.length == 0 )\r
+       {\r
+               window.parent.SetSelectedTab( 'Info' ) ;\r
+               GetE('txtUrl').focus() ;\r
+\r
+               alert( oEditor.FCKLang.DlgAlertUrl ) ;\r
+\r
+               return false ;\r
+       }\r
+\r
+       if ( !oEmbed )\r
+       {\r
+               oEmbed          = FCK.EditorDocument.createElement( 'EMBED' ) ;\r
+               oFakeImage  = null ;\r
+       }\r
+       UpdateEmbed( oEmbed ) ;\r
+\r
+       if ( !oFakeImage )\r
+       {\r
+               oFakeImage      = oEditor.FCKDocumentProcessor_CreateFakeImage( 'FCK__Flash', oEmbed ) ;\r
+               oFakeImage.setAttribute( '_fckflash', 'true', 0 ) ;\r
+               oFakeImage      = FCK.InsertElementAndGetIt( oFakeImage ) ;\r
+       }\r
+       else\r
+               oEditor.FCKUndo.SaveUndoStep() ;\r
+\r
+       oEditor.FCKFlashProcessor.RefreshView( oFakeImage, oEmbed ) ;\r
+\r
+       return true ;\r
+}\r
+\r
+function UpdateEmbed( e )\r
+{\r
+       SetAttribute( e, 'type'                 , 'application/x-shockwave-flash' ) ;\r
+       SetAttribute( e, 'pluginspage'  , 'http://www.macromedia.com/go/getflashplayer' ) ;\r
+\r
+       SetAttribute( e, 'src', GetE('txtUrl').value ) ;\r
+       SetAttribute( e, "width" , GetE('txtWidth').value ) ;\r
+       SetAttribute( e, "height", GetE('txtHeight').value ) ;\r
+\r
+       // Advances Attributes\r
+\r
+       SetAttribute( e, 'id'   , GetE('txtAttId').value ) ;\r
+       SetAttribute( e, 'scale', GetE('cmbScale').value ) ;\r
+\r
+       SetAttribute( e, 'play', GetE('chkAutoPlay').checked ? 'true' : 'false' ) ;\r
+       SetAttribute( e, 'loop', GetE('chkLoop').checked ? 'true' : 'false' ) ;\r
+       SetAttribute( e, 'menu', GetE('chkMenu').checked ? 'true' : 'false' ) ;\r
+\r
+       SetAttribute( e, 'title'        , GetE('txtAttTitle').value ) ;\r
+\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+       {\r
+               SetAttribute( e, 'className', GetE('txtAttClasses').value ) ;\r
+               e.style.cssText = GetE('txtAttStyle').value ;\r
+       }\r
+       else\r
+       {\r
+               SetAttribute( e, 'class', GetE('txtAttClasses').value ) ;\r
+               SetAttribute( e, 'style', GetE('txtAttStyle').value ) ;\r
+       }\r
+}\r
+\r
+var ePreview ;\r
+\r
+function SetPreviewElement( previewEl )\r
+{\r
+       ePreview = previewEl ;\r
+\r
+       if ( GetE('txtUrl').value.length > 0 )\r
+               UpdatePreview() ;\r
+}\r
+\r
+function UpdatePreview()\r
+{\r
+       if ( !ePreview )\r
+               return ;\r
+\r
+       while ( ePreview.firstChild )\r
+               ePreview.removeChild( ePreview.firstChild ) ;\r
+\r
+       if ( GetE('txtUrl').value.length == 0 )\r
+               ePreview.innerHTML = '&nbsp;' ;\r
+       else\r
+       {\r
+               var oDoc        = ePreview.ownerDocument || ePreview.document ;\r
+               var e           = oDoc.createElement( 'EMBED' ) ;\r
+\r
+               SetAttribute( e, 'src', GetE('txtUrl').value ) ;\r
+               SetAttribute( e, 'type', 'application/x-shockwave-flash' ) ;\r
+               SetAttribute( e, 'width', '100%' ) ;\r
+               SetAttribute( e, 'height', '100%' ) ;\r
+\r
+               ePreview.appendChild( e ) ;\r
+       }\r
+}\r
+\r
+// <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">\r
+\r
+function BrowseServer()\r
+{\r
+       OpenFileBrowser( FCKConfig.FlashBrowserURL, FCKConfig.FlashBrowserWindowWidth, FCKConfig.FlashBrowserWindowHeight ) ;\r
+}\r
+\r
+function SetUrl( url, width, height )\r
+{\r
+       GetE('txtUrl').value = url ;\r
+\r
+       if ( width )\r
+               GetE('txtWidth').value = width ;\r
+\r
+       if ( height )\r
+               GetE('txtHeight').value = height ;\r
+\r
+       UpdatePreview() ;\r
+\r
+       window.parent.SetSelectedTab( 'Info' ) ;\r
+}\r
+\r
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )\r
+{\r
+       switch ( errorNumber )\r
+       {\r
+               case 0 :        // No errors\r
+                       alert( 'Your file has been successfully uploaded' ) ;\r
+                       break ;\r
+               case 1 :        // Custom error\r
+                       alert( customMsg ) ;\r
+                       return ;\r
+               case 101 :      // Custom warning\r
+                       alert( customMsg ) ;\r
+                       break ;\r
+               case 201 :\r
+                       alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;\r
+                       break ;\r
+               case 202 :\r
+                       alert( 'Invalid file type' ) ;\r
+                       return ;\r
+               case 203 :\r
+                       alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;\r
+                       return ;\r
+               default :\r
+                       alert( 'Error on file upload. Error number: ' + errorNumber ) ;\r
+                       return ;\r
+       }\r
+\r
+       SetUrl( fileUrl ) ;\r
+       GetE('frmUpload').reset() ;\r
+}\r
+\r
+var oUploadAllowedExtRegex     = new RegExp( FCKConfig.FlashUploadAllowedExtensions, 'i' ) ;\r
+var oUploadDeniedExtRegex      = new RegExp( FCKConfig.FlashUploadDeniedExtensions, 'i' ) ;\r
+\r
+function CheckUpload()\r
+{\r
+       var sFile = GetE('txtUploadFile').value ;\r
+\r
+       if ( sFile.length == 0 )\r
+       {\r
+               alert( 'Please select a file to upload' ) ;\r
+               return false ;\r
+       }\r
+\r
+       if ( ( FCKConfig.FlashUploadAllowedExtensions.length > 0 && !oUploadAllowedExtRegex.test( sFile ) ) ||\r
+               ( FCKConfig.FlashUploadDeniedExtensions.length > 0 && oUploadDeniedExtRegex.test( sFile ) ) )\r
+       {\r
+               OnUploadCompleted( 202 ) ;\r
+               return false ;\r
+       }\r
+\r
+       return true ;\r
+}
\ No newline at end of file
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 (file)
index 0000000..ad3a0a1
--- /dev/null
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Preview page for the Flash dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title></title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta name="robots" content="noindex, nofollow">\r
+               <link href="../common/fck_dialog_common.css" rel="stylesheet" type="text/css" />\r
+               <script language="javascript">\r
+\r
+// Sets the Skin CSS\r
+document.write( '<link href="' + window.parent.FCKConfig.SkinPath + 'fck_dialog.css" type="text/css" rel="stylesheet">' ) ;\r
+\r
+if ( window.parent.FCKConfig.BaseHref.length > 0 )\r
+       document.write( '<base href="' + window.parent.FCKConfig.BaseHref + '">' ) ;\r
+\r
+window.onload = function()\r
+{\r
+       window.parent.SetPreviewElement( document.body ) ;\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body style="COLOR: #000000; BACKGROUND-COLOR: #ffffff"></body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_form.html b/httemplate/elements/fckeditor/editor/dialog/fck_form.html
new file mode 100644 (file)
index 0000000..66e56d9
--- /dev/null
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Form dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta content="noindex, nofollow" name="robots" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+var oActiveEl = oEditor.FCKSelection.MoveToAncestorNode( 'FORM' ) ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( oActiveEl )\r
+       {\r
+               GetE('txtName').value   = oActiveEl.name ;\r
+               GetE('txtAction').value = oActiveEl.getAttribute( 'action', 2 ) ;\r
+               GetE('txtMethod').value = oActiveEl.method ;\r
+       }\r
+       else\r
+               oActiveEl = null ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( !oActiveEl )\r
+       {\r
+               oActiveEl = oEditor.FCK.EditorDocument.createElement( 'FORM' ) ;\r
+               oActiveEl = oEditor.FCK.InsertElementAndGetIt( oActiveEl ) ;\r
+               oActiveEl.innerHTML = '&nbsp;' ;\r
+       }\r
+\r
+       oActiveEl.name = GetE('txtName').value ;\r
+       SetAttribute( oActiveEl, 'action'       , GetE('txtAction').value ) ;\r
+       oActiveEl.method = GetE('txtMethod').value ;\r
+\r
+       return true ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden">\r
+       <table width="100%" style="height: 100%">\r
+               <tr>\r
+                       <td align="center">\r
+                               <table cellspacing="0" cellpadding="0" width="80%" border="0">\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgFormName">Name</span><br />\r
+                                                       <input style="width: 100%" type="text" id="txtName" />\r
+                                               </td>\r
+                                       </tr>\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgFormAction">Action</span><br />\r
+                                                       <input style="width: 100%" type="text" id="txtAction" />\r
+                                               </td>\r
+                                       </tr>\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgFormMethod">Method</span><br />\r
+                                                       <select id="txtMethod">\r
+                                                               <option value="get" selected="selected">GET</option>\r
+                                                               <option value="post">POST</option>\r
+                                                       </select>\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_hiddenfield.html b/httemplate/elements/fckeditor/editor/dialog/fck_hiddenfield.html
new file mode 100644 (file)
index 0000000..1ae8ae6
--- /dev/null
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Hidden Field dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>Hidden Field Properties</title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta content="noindex, nofollow" name="robots" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+var FCK = oEditor.FCK ;\r
+\r
+// Gets the document DOM\r
+var oDOM = FCK.EditorDocument ;\r
+\r
+// Get the selected flash embed (if available).\r
+var oFakeImage = FCK.Selection.GetSelectedElement() ;\r
+var oActiveEl ;\r
+\r
+if ( oFakeImage )\r
+{\r
+       if ( oFakeImage.tagName == 'IMG' && oFakeImage.getAttribute('_fckinputhidden') )\r
+               oActiveEl = FCK.GetRealElement( oFakeImage ) ;\r
+       else\r
+               oFakeImage = null ;\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( oActiveEl )\r
+       {\r
+               GetE('txtName').value           = oActiveEl.name ;\r
+               GetE('txtValue').value          = oActiveEl.value ;\r
+       }\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+\r
+function Ok()\r
+{\r
+       if ( !oActiveEl )\r
+       {\r
+               oActiveEl = FCK.EditorDocument.createElement( 'INPUT' ) ;\r
+               oActiveEl.type = 'hidden' ;\r
+\r
+               oFakeImage = null ;\r
+       }\r
+\r
+       oActiveEl.name = GetE('txtName').value ;\r
+       SetAttribute( oActiveEl, 'value', GetE('txtValue').value ) ;\r
+\r
+       if ( !oFakeImage )\r
+       {\r
+               oFakeImage      = oEditor.FCKDocumentProcessor_CreateFakeImage( 'FCK__InputHidden', oActiveEl ) ;\r
+               oFakeImage.setAttribute( '_fckinputhidden', 'true', 0 ) ;\r
+               oFakeImage      = FCK.InsertElementAndGetIt( oFakeImage ) ;\r
+       }\r
+       else\r
+               oEditor.FCKUndo.SaveUndoStep() ;\r
+\r
+       oEditor.FCKFlashProcessor.RefreshView( oFakeImage, oActiveEl ) ;\r
+\r
+       return true ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden" scroll="no">\r
+       <table height="100%" width="100%">\r
+               <tr>\r
+                       <td align="center">\r
+                               <table border="0" class="inhoud" cellpadding="0" cellspacing="0" width="80%">\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgHiddenName">Name</span><br />\r
+                                                       <input type="text" size="20" id="txtName" style="width: 100%" />\r
+                                               </td>\r
+                                       </tr>\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgHiddenValue">Value</span><br />\r
+                                                       <input type="text" size="30" id="txtValue" style="width: 100%" />\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_image.html b/httemplate/elements/fckeditor/editor/dialog/fck_image.html
new file mode 100644 (file)
index 0000000..e4a8b03
--- /dev/null
@@ -0,0 +1,252 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Image Properties dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>Image Properties</title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script src="fck_image/fck_image.js" type="text/javascript"></script>\r
+       <link href="common/fck_dialog_common.css" rel="stylesheet" type="text/css" />\r
+</head>\r
+<body scroll="no" style="overflow: hidden">\r
+       <div id="divInfo">\r
+               <table cellspacing="1" cellpadding="1" border="0" width="100%" height="100%">\r
+                       <tr>\r
+                               <td>\r
+                                       <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                                               <tr>\r
+                                                       <td width="100%">\r
+                                                               <span fcklang="DlgImgURL">URL</span>\r
+                                                       </td>\r
+                                                       <td id="tdBrowse" style="display: none" nowrap="nowrap" rowspan="2">\r
+                                                               &nbsp;\r
+                                                               <input id="btnBrowse" onclick="BrowseServer();" type="button" value="Browse Server"\r
+                                                                       fcklang="DlgBtnBrowseServer" />\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td valign="top">\r
+                                                               <input id="txtUrl" style="width: 100%" type="text" onblur="UpdatePreview();" />\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td>\r
+                                       <span fcklang="DlgImgAlt">Short Description</span><br />\r
+                                       <input id="txtAlt" style="width: 100%" type="text" /><br />\r
+                               </td>\r
+                       </tr>\r
+                       <tr height="100%">\r
+                               <td valign="top">\r
+                                       <table cellspacing="0" cellpadding="0" width="100%" border="0" height="100%">\r
+                                               <tr>\r
+                                                       <td valign="top">\r
+                                                               <br />\r
+                                                               <table cellspacing="0" cellpadding="0" border="0">\r
+                                                                       <tr>\r
+                                                                               <td nowrap="nowrap">\r
+                                                                                       <span fcklang="DlgImgWidth">Width</span>&nbsp;</td>\r
+                                                                               <td>\r
+                                                                                       <input type="text" size="3" id="txtWidth" onkeyup="OnSizeChanged('Width',this.value);" /></td>\r
+                                                                               <td rowspan="2">\r
+                                                                                       <div id="btnLockSizes" class="BtnLocked" onmouseover="this.className = (bLockRatio ? 'BtnLocked' : 'BtnUnlocked' ) + ' BtnOver';"\r
+                                                                                               onmouseout="this.className = (bLockRatio ? 'BtnLocked' : 'BtnUnlocked' );" title="Lock Sizes"\r
+                                                                                               onclick="SwitchLock(this);">\r
+                                                                                       </div>\r
+                                                                               </td>\r
+                                                                               <td rowspan="2">\r
+                                                                                       <div id="btnResetSize" class="BtnReset" onmouseover="this.className='BtnReset BtnOver';"\r
+                                                                                               onmouseout="this.className='BtnReset';" title="Reset Size" onclick="ResetSizes();">\r
+                                                                                       </div>\r
+                                                                               </td>\r
+                                                                       </tr>\r
+                                                                       <tr>\r
+                                                                               <td nowrap="nowrap">\r
+                                                                                       <span fcklang="DlgImgHeight">Height</span>&nbsp;</td>\r
+                                                                               <td>\r
+                                                                                       <input type="text" size="3" id="txtHeight" onkeyup="OnSizeChanged('Height',this.value);" /></td>\r
+                                                                       </tr>\r
+                                                               </table>\r
+                                                               <br />\r
+                                                               <table cellspacing="0" cellpadding="0" border="0">\r
+                                                                       <tr>\r
+                                                                               <td nowrap="nowrap">\r
+                                                                                       <span fcklang="DlgImgBorder">Border</span>&nbsp;</td>\r
+                                                                               <td>\r
+                                                                                       <input type="text" size="2" value="" id="txtBorder" onkeyup="UpdatePreview();" /></td>\r
+                                                                       </tr>\r
+                                                                       <tr>\r
+                                                                               <td nowrap="nowrap">\r
+                                                                                       <span fcklang="DlgImgHSpace">HSpace</span>&nbsp;</td>\r
+                                                                               <td>\r
+                                                                                       <input type="text" size="2" id="txtHSpace" onkeyup="UpdatePreview();" /></td>\r
+                                                                       </tr>\r
+                                                                       <tr>\r
+                                                                               <td nowrap="nowrap">\r
+                                                                                       <span fcklang="DlgImgVSpace">VSpace</span>&nbsp;</td>\r
+                                                                               <td>\r
+                                                                                       <input type="text" size="2" id="txtVSpace" onkeyup="UpdatePreview();" /></td>\r
+                                                                       </tr>\r
+                                                                       <tr>\r
+                                                                               <td nowrap="nowrap">\r
+                                                                                       <span fcklang="DlgImgAlign">Align</span>&nbsp;</td>\r
+                                                                               <td>\r
+                                                                                       <select id="cmbAlign" onchange="UpdatePreview();">\r
+                                                                                               <option value="" selected="selected"></option>\r
+                                                                                               <option fcklang="DlgImgAlignLeft" value="left">Left</option>\r
+                                                                                               <option fcklang="DlgImgAlignAbsBottom" value="absBottom">Abs Bottom</option>\r
+                                                                                               <option fcklang="DlgImgAlignAbsMiddle" value="absMiddle">Abs Middle</option>\r
+                                                                                               <option fcklang="DlgImgAlignBaseline" value="baseline">Baseline</option>\r
+                                                                                               <option fcklang="DlgImgAlignBottom" value="bottom">Bottom</option>\r
+                                                                                               <option fcklang="DlgImgAlignMiddle" value="middle">Middle</option>\r
+                                                                                               <option fcklang="DlgImgAlignRight" value="right">Right</option>\r
+                                                                                               <option fcklang="DlgImgAlignTextTop" value="textTop">Text Top</option>\r
+                                                                                               <option fcklang="DlgImgAlignTop" value="top">Top</option>\r
+                                                                                       </select>\r
+                                                                               </td>\r
+                                                                       </tr>\r
+                                                               </table>\r
+                                                       </td>\r
+                                                       <td>\r
+                                                               &nbsp;&nbsp;&nbsp;</td>\r
+                                                       <td width="100%" valign="top">\r
+                                                               <table cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed">\r
+                                                                       <tr>\r
+                                                                               <td>\r
+                                                                                       <span fcklang="DlgImgPreview">Preview</span></td>\r
+                                                                       </tr>\r
+                                                                       <tr>\r
+                                                                               <td valign="top">\r
+                                                                                       <iframe class="ImagePreviewArea" src="fck_image/fck_image_preview.html" frameborder="0"\r
+                                                                                               marginheight="0" marginwidth="0"></iframe>\r
+                                                                               </td>\r
+                                                                       </tr>\r
+                                                               </table>\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </div>\r
+       <div id="divUpload" style="display: none">\r
+               <form id="frmUpload" method="post" target="UploadWindow" enctype="multipart/form-data"\r
+                       action="" onsubmit="return CheckUpload();">\r
+                       <span fcklang="DlgLnkUpload">Upload</span><br />\r
+                       <input id="txtUploadFile" style="width: 100%" type="file" size="40" name="NewFile" /><br />\r
+                       <br />\r
+                       <input id="btnUpload" type="submit" value="Send it to the Server" fcklang="DlgLnkBtnUpload" />\r
+                       <iframe name="UploadWindow" style="display: none" src="javascript:void(0)"></iframe>\r
+               </form>\r
+       </div>\r
+       <div id="divLink" style="display: none">\r
+               <table cellspacing="1" cellpadding="1" border="0" width="100%">\r
+                       <tr>\r
+                               <td>\r
+                                       <div>\r
+                                               <span fcklang="DlgLnkURL">URL</span><br />\r
+                                               <input id="txtLnkUrl" style="width: 100%" type="text" onblur="UpdatePreview();" />\r
+                                       </div>\r
+                                       <div id="divLnkBrowseServer" align="right">\r
+                                               <input type="button" value="Browse Server" fcklang="DlgBtnBrowseServer" onclick="LnkBrowseServer();" />\r
+                                       </div>\r
+                                       <div>\r
+                                               <span fcklang="DlgLnkTarget">Target</span><br />\r
+                                               <select id="cmbLnkTarget">\r
+                                                       <option value="" fcklang="DlgGenNotSet" selected="selected">&lt;not set&gt;</option>\r
+                                                       <option value="_blank" fcklang="DlgLnkTargetBlank">New Window (_blank)</option>\r
+                                                       <option value="_top" fcklang="DlgLnkTargetTop">Topmost Window (_top)</option>\r
+                                                       <option value="_self" fcklang="DlgLnkTargetSelf">Same Window (_self)</option>\r
+                                                       <option value="_parent" fcklang="DlgLnkTargetParent">Parent Window (_parent)</option>\r
+                                               </select>\r
+                                       </div>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </div>\r
+       <div id="divAdvanced" style="display: none">\r
+               <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">\r
+                       <tr>\r
+                               <td valign="top" width="50%">\r
+                                       <span fcklang="DlgGenId">Id</span><br />\r
+                                       <input id="txtAttId" style="width: 100%" type="text" />\r
+                               </td>\r
+                               <td width="1">\r
+                                       &nbsp;&nbsp;</td>\r
+                               <td valign="top">\r
+                                       <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">\r
+                                               <tr>\r
+                                                       <td width="60%">\r
+                                                               <span fcklang="DlgGenLangDir">Language Direction</span><br />\r
+                                                               <select id="cmbAttLangDir" style="width: 100%">\r
+                                                                       <option value="" fcklang="DlgGenNotSet" selected="selected">&lt;not set&gt;</option>\r
+                                                                       <option value="ltr" fcklang="DlgGenLangDirLtr">Left to Right (LTR)</option>\r
+                                                                       <option value="rtl" fcklang="DlgGenLangDirRtl">Right to Left (RTL)</option>\r
+                                                               </select>\r
+                                                       </td>\r
+                                                       <td width="1%">\r
+                                                               &nbsp;&nbsp;</td>\r
+                                                       <td nowrap="nowrap">\r
+                                                               <span fcklang="DlgGenLangCode">Language Code</span><br />\r
+                                                               <input id="txtAttLangCode" style="width: 100%" type="text" />&nbsp;\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td colspan="3">\r
+                                       &nbsp;</td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td colspan="3">\r
+                                       <span fcklang="DlgGenLongDescr">Long Description URL</span><br />\r
+                                       <input id="txtLongDesc" style="width: 100%" type="text" />\r
+                               </td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td colspan="3">\r
+                                       &nbsp;</td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td valign="top">\r
+                                       <span fcklang="DlgGenClass">Stylesheet Classes</span><br />\r
+                                       <input id="txtAttClasses" style="width: 100%" type="text" />\r
+                               </td>\r
+                               <td>\r
+                               </td>\r
+                               <td valign="top">\r
+                                       &nbsp;<span fcklang="DlgGenTitle">Advisory Title</span><br />\r
+                                       <input id="txtAttTitle" style="width: 100%" type="text" />\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+               <span fcklang="DlgGenStyle">Style</span><br />\r
+               <input id="txtAttStyle" style="width: 100%" type="text" />\r
+       </div>\r
+</body>\r
+</html>\r
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 (file)
index 0000000..89b0f95
--- /dev/null
@@ -0,0 +1,493 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Scripts related to the Image dialog window (see fck_image.html).\r
+ */\r
+\r
+var oEditor            = window.parent.InnerDialogLoaded() ;\r
+var FCK                        = oEditor.FCK ;\r
+var FCKLang            = oEditor.FCKLang ;\r
+var FCKConfig  = oEditor.FCKConfig ;\r
+var FCKDebug   = oEditor.FCKDebug ;\r
+\r
+var bImageButton = ( document.location.search.length > 0 && document.location.search.substr(1) == 'ImageButton' ) ;\r
+\r
+//#### Dialog Tabs\r
+\r
+// Set the dialog tabs.\r
+window.parent.AddTab( 'Info', FCKLang.DlgImgInfoTab ) ;\r
+\r
+if ( !bImageButton && !FCKConfig.ImageDlgHideLink )\r
+       window.parent.AddTab( 'Link', FCKLang.DlgImgLinkTab ) ;\r
+\r
+if ( FCKConfig.ImageUpload )\r
+       window.parent.AddTab( 'Upload', FCKLang.DlgLnkUpload ) ;\r
+\r
+if ( !FCKConfig.ImageDlgHideAdvanced )\r
+       window.parent.AddTab( 'Advanced', FCKLang.DlgAdvancedTag ) ;\r
+\r
+// Function called when a dialog tag is selected.\r
+function OnDialogTabChange( tabCode )\r
+{\r
+       ShowE('divInfo'         , ( tabCode == 'Info' ) ) ;\r
+       ShowE('divLink'         , ( tabCode == 'Link' ) ) ;\r
+       ShowE('divUpload'       , ( tabCode == 'Upload' ) ) ;\r
+       ShowE('divAdvanced'     , ( tabCode == 'Advanced' ) ) ;\r
+}\r
+\r
+// Get the selected image (if available).\r
+var oImage = FCK.Selection.GetSelectedElement() ;\r
+\r
+if ( oImage && oImage.tagName != 'IMG' && !( oImage.tagName == 'INPUT' && oImage.type == 'image' ) )\r
+       oImage = null ;\r
+\r
+// Get the active link.\r
+var oLink = FCK.Selection.MoveToAncestorNode( 'A' ) ;\r
+\r
+var oImageOriginal ;\r
+\r
+function UpdateOriginal( resetSize )\r
+{\r
+       if ( !eImgPreview )\r
+               return ;\r
+\r
+       if ( GetE('txtUrl').value.length == 0 )\r
+       {\r
+               oImageOriginal = null ;\r
+               return ;\r
+       }\r
+\r
+       oImageOriginal = document.createElement( 'IMG' ) ;      // new Image() ;\r
+\r
+       if ( resetSize )\r
+       {\r
+               oImageOriginal.onload = function()\r
+               {\r
+                       this.onload = null ;\r
+                       ResetSizes() ;\r
+               }\r
+       }\r
+\r
+       oImageOriginal.src = eImgPreview.src ;\r
+}\r
+\r
+var bPreviewInitialized ;\r
+\r
+window.onload = function()\r
+{\r
+       // Translate the dialog box texts.\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       GetE('btnLockSizes').title = FCKLang.DlgImgLockRatio ;\r
+       GetE('btnResetSize').title = FCKLang.DlgBtnResetSize ;\r
+\r
+       // Load the selected element information (if any).\r
+       LoadSelection() ;\r
+\r
+       // Show/Hide the "Browse Server" button.\r
+       GetE('tdBrowse').style.display                          = FCKConfig.ImageBrowser        ? '' : 'none' ;\r
+       GetE('divLnkBrowseServer').style.display        = FCKConfig.LinkBrowser         ? '' : 'none' ;\r
+\r
+       UpdateOriginal() ;\r
+\r
+       // Set the actual uploader URL.\r
+       if ( FCKConfig.ImageUpload )\r
+               GetE('frmUpload').action = FCKConfig.ImageUploadURL ;\r
+\r
+       window.parent.SetAutoSize( true ) ;\r
+\r
+       // Activate the "OK" button.\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function LoadSelection()\r
+{\r
+       if ( ! oImage ) return ;\r
+\r
+       var sUrl = oImage.getAttribute( '_fcksavedurl' ) ;\r
+       if ( sUrl == null )\r
+               sUrl = GetAttribute( oImage, 'src', '' ) ;\r
+\r
+       GetE('txtUrl').value    = sUrl ;\r
+       GetE('txtAlt').value    = GetAttribute( oImage, 'alt', '' ) ;\r
+       GetE('txtVSpace').value = GetAttribute( oImage, 'vspace', '' ) ;\r
+       GetE('txtHSpace').value = GetAttribute( oImage, 'hspace', '' ) ;\r
+       GetE('txtBorder').value = GetAttribute( oImage, 'border', '' ) ;\r
+       GetE('cmbAlign').value  = GetAttribute( oImage, 'align', '' ) ;\r
+\r
+       var iWidth, iHeight ;\r
+\r
+       var regexSize = /^\s*(\d+)px\s*$/i ;\r
+\r
+       if ( oImage.style.width )\r
+       {\r
+               var aMatchW  = oImage.style.width.match( regexSize ) ;\r
+               if ( aMatchW )\r
+               {\r
+                       iWidth = aMatchW[1] ;\r
+                       oImage.style.width = '' ;\r
+                       SetAttribute( oImage, 'width' , iWidth ) ;\r
+               }\r
+       }\r
+\r
+       if ( oImage.style.height )\r
+       {\r
+               var aMatchH  = oImage.style.height.match( regexSize ) ;\r
+               if ( aMatchH )\r
+               {\r
+                       iHeight = aMatchH[1] ;\r
+                       oImage.style.height = '' ;\r
+                       SetAttribute( oImage, 'height', iHeight ) ;\r
+               }\r
+       }\r
+\r
+       GetE('txtWidth').value  = iWidth ? iWidth : GetAttribute( oImage, "width", '' ) ;\r
+       GetE('txtHeight').value = iHeight ? iHeight : GetAttribute( oImage, "height", '' ) ;\r
+\r
+       // Get Advances Attributes\r
+       GetE('txtAttId').value                  = oImage.id ;\r
+       GetE('cmbAttLangDir').value             = oImage.dir ;\r
+       GetE('txtAttLangCode').value    = oImage.lang ;\r
+       GetE('txtAttTitle').value               = oImage.title ;\r
+       GetE('txtLongDesc').value               = oImage.longDesc ;\r
+\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+       {\r
+               GetE('txtAttClasses').value = oImage.className || '' ;\r
+               GetE('txtAttStyle').value = oImage.style.cssText ;\r
+       }\r
+       else\r
+       {\r
+               GetE('txtAttClasses').value = oImage.getAttribute('class',2) || '' ;\r
+               GetE('txtAttStyle').value = oImage.getAttribute('style',2) ;\r
+       }\r
+\r
+       if ( oLink )\r
+       {\r
+               var sLinkUrl = oLink.getAttribute( '_fcksavedurl' ) ;\r
+               if ( sLinkUrl == null )\r
+                       sLinkUrl = oLink.getAttribute('href',2) ;\r
+\r
+               GetE('txtLnkUrl').value         = sLinkUrl ;\r
+               GetE('cmbLnkTarget').value      = oLink.target ;\r
+       }\r
+\r
+       UpdatePreview() ;\r
+}\r
+\r
+//#### The OK button was hit.\r
+function Ok()\r
+{\r
+       if ( GetE('txtUrl').value.length == 0 )\r
+       {\r
+               window.parent.SetSelectedTab( 'Info' ) ;\r
+               GetE('txtUrl').focus() ;\r
+\r
+               alert( FCKLang.DlgImgAlertUrl ) ;\r
+\r
+               return false ;\r
+       }\r
+\r
+       var bHasImage = ( oImage != null ) ;\r
+\r
+       if ( bHasImage && bImageButton && oImage.tagName == 'IMG' )\r
+       {\r
+               if ( confirm( 'Do you want to transform the selected image on a image button?' ) )\r
+                       oImage = null ;\r
+       }\r
+       else if ( bHasImage && !bImageButton && oImage.tagName == 'INPUT' )\r
+       {\r
+               if ( confirm( 'Do you want to transform the selected image button on a simple image?' ) )\r
+                       oImage = null ;\r
+       }\r
+\r
+       if ( !bHasImage )\r
+       {\r
+               if ( bImageButton )\r
+               {\r
+                       oImage = FCK.EditorDocument.createElement( 'INPUT' ) ;\r
+                       oImage.type = 'image' ;\r
+                       oImage = FCK.InsertElementAndGetIt( oImage ) ;\r
+               }\r
+               else\r
+                       oImage = FCK.CreateElement( 'IMG' ) ;\r
+       }\r
+       else\r
+               oEditor.FCKUndo.SaveUndoStep() ;\r
+\r
+       UpdateImage( oImage ) ;\r
+\r
+       var sLnkUrl = GetE('txtLnkUrl').value.Trim() ;\r
+\r
+       if ( sLnkUrl.length == 0 )\r
+       {\r
+               if ( oLink )\r
+                       FCK.ExecuteNamedCommand( 'Unlink' ) ;\r
+       }\r
+       else\r
+       {\r
+               if ( oLink )    // Modifying an existent link.\r
+                       oLink.href = sLnkUrl ;\r
+               else                    // Creating a new link.\r
+               {\r
+                       if ( !bHasImage )\r
+                               oEditor.FCKSelection.SelectNode( oImage ) ;\r
+\r
+                       oLink = oEditor.FCK.CreateLink( sLnkUrl )[0] ;\r
+\r
+                       if ( !bHasImage )\r
+                       {\r
+                               oEditor.FCKSelection.SelectNode( oLink ) ;\r
+                               oEditor.FCKSelection.Collapse( false ) ;\r
+                       }\r
+               }\r
+\r
+               SetAttribute( oLink, '_fcksavedurl', sLnkUrl ) ;\r
+               SetAttribute( oLink, 'target', GetE('cmbLnkTarget').value ) ;\r
+       }\r
+\r
+       return true ;\r
+}\r
+\r
+function UpdateImage( e, skipId )\r
+{\r
+       e.src = GetE('txtUrl').value ;\r
+       SetAttribute( e, "_fcksavedurl", GetE('txtUrl').value ) ;\r
+       SetAttribute( e, "alt"   , GetE('txtAlt').value ) ;\r
+       SetAttribute( e, "width" , GetE('txtWidth').value ) ;\r
+       SetAttribute( e, "height", GetE('txtHeight').value ) ;\r
+       SetAttribute( e, "vspace", GetE('txtVSpace').value ) ;\r
+       SetAttribute( e, "hspace", GetE('txtHSpace').value ) ;\r
+       SetAttribute( e, "border", GetE('txtBorder').value ) ;\r
+       SetAttribute( e, "align" , GetE('cmbAlign').value ) ;\r
+\r
+       // Advances Attributes\r
+\r
+       if ( ! skipId )\r
+               SetAttribute( e, 'id', GetE('txtAttId').value ) ;\r
+\r
+       SetAttribute( e, 'dir'          , GetE('cmbAttLangDir').value ) ;\r
+       SetAttribute( e, 'lang'         , GetE('txtAttLangCode').value ) ;\r
+       SetAttribute( e, 'title'        , GetE('txtAttTitle').value ) ;\r
+       SetAttribute( e, 'longDesc'     , GetE('txtLongDesc').value ) ;\r
+\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+       {\r
+               e.className = GetE('txtAttClasses').value ;\r
+               e.style.cssText = GetE('txtAttStyle').value ;\r
+       }\r
+       else\r
+       {\r
+               SetAttribute( e, 'class'        , GetE('txtAttClasses').value ) ;\r
+               SetAttribute( e, 'style', GetE('txtAttStyle').value ) ;\r
+       }\r
+}\r
+\r
+var eImgPreview ;\r
+var eImgPreviewLink ;\r
+\r
+function SetPreviewElements( imageElement, linkElement )\r
+{\r
+       eImgPreview = imageElement ;\r
+       eImgPreviewLink = linkElement ;\r
+\r
+       UpdatePreview() ;\r
+       UpdateOriginal() ;\r
+\r
+       bPreviewInitialized = true ;\r
+}\r
+\r
+function UpdatePreview()\r
+{\r
+       if ( !eImgPreview || !eImgPreviewLink )\r
+               return ;\r
+\r
+       if ( GetE('txtUrl').value.length == 0 )\r
+               eImgPreviewLink.style.display = 'none' ;\r
+       else\r
+       {\r
+               UpdateImage( eImgPreview, true ) ;\r
+\r
+               if ( GetE('txtLnkUrl').value.Trim().length > 0 )\r
+                       eImgPreviewLink.href = 'javascript:void(null);' ;\r
+               else\r
+                       SetAttribute( eImgPreviewLink, 'href', '' ) ;\r
+\r
+               eImgPreviewLink.style.display = '' ;\r
+       }\r
+}\r
+\r
+var bLockRatio = true ;\r
+\r
+function SwitchLock( lockButton )\r
+{\r
+       bLockRatio = !bLockRatio ;\r
+       lockButton.className = bLockRatio ? 'BtnLocked' : 'BtnUnlocked' ;\r
+       lockButton.title = bLockRatio ? 'Lock sizes' : 'Unlock sizes' ;\r
+\r
+       if ( bLockRatio )\r
+       {\r
+               if ( GetE('txtWidth').value.length > 0 )\r
+                       OnSizeChanged( 'Width', GetE('txtWidth').value ) ;\r
+               else\r
+                       OnSizeChanged( 'Height', GetE('txtHeight').value ) ;\r
+       }\r
+}\r
+\r
+// Fired when the width or height input texts change\r
+function OnSizeChanged( dimension, value )\r
+{\r
+       // Verifies if the aspect ration has to be mantained\r
+       if ( oImageOriginal && bLockRatio )\r
+       {\r
+               var e = dimension == 'Width' ? GetE('txtHeight') : GetE('txtWidth') ;\r
+\r
+               if ( value.length == 0 || isNaN( value ) )\r
+               {\r
+                       e.value = '' ;\r
+                       return ;\r
+               }\r
+\r
+               if ( dimension == 'Width' )\r
+                       value = value == 0 ? 0 : Math.round( oImageOriginal.height * ( value  / oImageOriginal.width ) ) ;\r
+               else\r
+                       value = value == 0 ? 0 : Math.round( oImageOriginal.width  * ( value / oImageOriginal.height ) ) ;\r
+\r
+               if ( !isNaN( value ) )\r
+                       e.value = value ;\r
+       }\r
+\r
+       UpdatePreview() ;\r
+}\r
+\r
+// Fired when the Reset Size button is clicked\r
+function ResetSizes()\r
+{\r
+       if ( ! oImageOriginal ) return ;\r
+\r
+       GetE('txtWidth').value  = oImageOriginal.width ;\r
+       GetE('txtHeight').value = oImageOriginal.height ;\r
+\r
+       UpdatePreview() ;\r
+}\r
+\r
+function BrowseServer()\r
+{\r
+       OpenServerBrowser(\r
+               'Image',\r
+               FCKConfig.ImageBrowserURL,\r
+               FCKConfig.ImageBrowserWindowWidth,\r
+               FCKConfig.ImageBrowserWindowHeight ) ;\r
+}\r
+\r
+function LnkBrowseServer()\r
+{\r
+       OpenServerBrowser(\r
+               'Link',\r
+               FCKConfig.LinkBrowserURL,\r
+               FCKConfig.LinkBrowserWindowWidth,\r
+               FCKConfig.LinkBrowserWindowHeight ) ;\r
+}\r
+\r
+function OpenServerBrowser( type, url, width, height )\r
+{\r
+       sActualBrowser = type ;\r
+       OpenFileBrowser( url, width, height ) ;\r
+}\r
+\r
+var sActualBrowser ;\r
+\r
+function SetUrl( url, width, height, alt )\r
+{\r
+       if ( sActualBrowser == 'Link' )\r
+       {\r
+               GetE('txtLnkUrl').value = url ;\r
+               UpdatePreview() ;\r
+       }\r
+       else\r
+       {\r
+               GetE('txtUrl').value = url ;\r
+               GetE('txtWidth').value = width ? width : '' ;\r
+               GetE('txtHeight').value = height ? height : '' ;\r
+\r
+               if ( alt )\r
+                       GetE('txtAlt').value = alt;\r
+\r
+               UpdatePreview() ;\r
+               UpdateOriginal( true ) ;\r
+       }\r
+\r
+       window.parent.SetSelectedTab( 'Info' ) ;\r
+}\r
+\r
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )\r
+{\r
+       switch ( errorNumber )\r
+       {\r
+               case 0 :        // No errors\r
+                       alert( 'Your file has been successfully uploaded' ) ;\r
+                       break ;\r
+               case 1 :        // Custom error\r
+                       alert( customMsg ) ;\r
+                       return ;\r
+               case 101 :      // Custom warning\r
+                       alert( customMsg ) ;\r
+                       break ;\r
+               case 201 :\r
+                       alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;\r
+                       break ;\r
+               case 202 :\r
+                       alert( 'Invalid file type' ) ;\r
+                       return ;\r
+               case 203 :\r
+                       alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;\r
+                       return ;\r
+               default :\r
+                       alert( 'Error on file upload. Error number: ' + errorNumber ) ;\r
+                       return ;\r
+       }\r
+\r
+       sActualBrowser = '' ;\r
+       SetUrl( fileUrl ) ;\r
+       GetE('frmUpload').reset() ;\r
+}\r
+\r
+var oUploadAllowedExtRegex     = new RegExp( FCKConfig.ImageUploadAllowedExtensions, 'i' ) ;\r
+var oUploadDeniedExtRegex      = new RegExp( FCKConfig.ImageUploadDeniedExtensions, 'i' ) ;\r
+\r
+function CheckUpload()\r
+{\r
+       var sFile = GetE('txtUploadFile').value ;\r
+\r
+       if ( sFile.length == 0 )\r
+       {\r
+               alert( 'Please select a file to upload' ) ;\r
+               return false ;\r
+       }\r
+\r
+       if ( ( FCKConfig.ImageUploadAllowedExtensions.length > 0 && !oUploadAllowedExtRegex.test( sFile ) ) ||\r
+               ( FCKConfig.ImageUploadDeniedExtensions.length > 0 && oUploadDeniedExtRegex.test( sFile ) ) )\r
+       {\r
+               OnUploadCompleted( 202 ) ;\r
+               return false ;\r
+       }\r
+\r
+       return true ;\r
+}
\ No newline at end of file
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 (file)
index 0000000..21bdc25
--- /dev/null
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Preview page for the Image dialog window.\r
+ *\r
+ * Curiosity: http://en.wikipedia.org/wiki/Lorem_ipsum\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <link href="../common/fck_dialog_common.css" rel="stylesheet" type="text/css" />\r
+       <script type="text/javascript">\r
+\r
+// Sets the Skin CSS\r
+document.write( '<link href="' + window.parent.FCKConfig.SkinPath + 'fck_dialog.css" type="text/css" rel="stylesheet">' ) ;\r
+\r
+if ( window.parent.FCKConfig.BaseHref.length > 0 )\r
+       document.write( '<base href="' + window.parent.FCKConfig.BaseHref + '">' ) ;\r
+\r
+window.onload = function()\r
+{\r
+       window.parent.SetPreviewElements(\r
+               document.getElementById( 'imgPreview' ),\r
+               document.getElementById( 'lnkPreview' ) ) ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="color: #000000; background-color: #ffffff">\r
+       <a id="lnkPreview" onclick="return false;" style="cursor: default">\r
+               <img id="imgPreview" onload="window.parent.UpdateOriginal();" style="display: none" /></a>Lorem\r
+       ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas feugiat consequat diam.\r
+       Maecenas metus. Vivamus diam purus, cursus a, commodo non, facilisis vitae, nulla.\r
+       Aenean dictum lacinia tortor. Nunc iaculis, nibh non iaculis aliquam, orci felis\r
+       euismod neque, sed ornare massa mauris sed velit. Nulla pretium mi et risus. Fusce\r
+       mi pede, tempor id, cursus ac, ullamcorper nec, enim. Sed tortor. Curabitur molestie.\r
+       Duis velit augue, condimentum at, ultrices a, luctus ut, orci. Donec pellentesque\r
+       egestas eros. Integer cursus, augue in cursus faucibus, eros pede bibendum sem,\r
+       in tempus tellus justo quis ligula. Etiam eget tortor. Vestibulum rutrum, est ut\r
+       placerat elementum, lectus nisl aliquam velit, tempor aliquam eros nunc nonummy\r
+       metus. In eros metus, gravida a, gravida sed, lobortis id, turpis. Ut ultrices,\r
+       ipsum at venenatis fringilla, sem nulla lacinia tellus, eget aliquet turpis mauris\r
+       non enim. Nam turpis. Suspendisse lacinia. Curabitur ac tortor ut ipsum egestas\r
+       elementum. Nunc imperdiet gravida mauris.\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_link.html b/httemplate/elements/fckeditor/editor/dialog/fck_link.html
new file mode 100644 (file)
index 0000000..c8f37b6
--- /dev/null
@@ -0,0 +1,293 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Link dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Link Properties</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+               <meta name="robots" content="noindex, nofollow" />\r
+               <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+               <script src="fck_link/fck_link.js" type="text/javascript"></script>\r
+       </head>\r
+       <body scroll="no" style="OVERFLOW: hidden">\r
+               <div id="divInfo" style="DISPLAY: none">\r
+                       <span fckLang="DlgLnkType">Link Type</span><br />\r
+                       <select id="cmbLinkType" onchange="SetLinkType(this.value);">\r
+                               <option value="url" fckLang="DlgLnkTypeURL" selected="selected">URL</option>\r
+                               <option value="anchor" fckLang="DlgLnkTypeAnchor">Anchor in this page</option>\r
+                               <option value="email" fckLang="DlgLnkTypeEMail">E-Mail</option>\r
+                       </select>\r
+                       <br />\r
+                       <br />\r
+                       <div id="divLinkTypeUrl">\r
+                               <table cellspacing="0" cellpadding="0" width="100%" border="0" dir="ltr">\r
+                                       <tr>\r
+                                               <td nowrap="nowrap">\r
+                                                       <span fckLang="DlgLnkProto">Protocol</span><br />\r
+                                                       <select id="cmbLinkProtocol">\r
+                                                               <option value="http://" selected="selected">http://</option>\r
+                                                               <option value="https://">https://</option>\r
+                                                               <option value="ftp://">ftp://</option>\r
+                                                               <option value="news://">news://</option>\r
+                                                               <option value="" fckLang="DlgLnkProtoOther">&lt;other&gt;</option>\r
+                                                       </select>\r
+                                               </td>\r
+                                               <td nowrap="nowrap">&nbsp;</td>\r
+                                               <td nowrap="nowrap" width="100%">\r
+                                                       <span fckLang="DlgLnkURL">URL</span><br />\r
+                                                       <input id="txtUrl" style="WIDTH: 100%" type="text" onkeyup="OnUrlChange();" onchange="OnUrlChange();" />\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                               <br />\r
+                               <div id="divBrowseServer">\r
+                               <input type="button" value="Browse Server" fckLang="DlgBtnBrowseServer" onclick="BrowseServer();" />\r
+                               </div>\r
+                       </div>\r
+                       <div id="divLinkTypeAnchor" style="DISPLAY: none" align="center">\r
+                               <div id="divSelAnchor" style="DISPLAY: none">\r
+                                       <table cellspacing="0" cellpadding="0" border="0" width="70%">\r
+                                               <tr>\r
+                                                       <td colspan="3">\r
+                                                               <span fckLang="DlgLnkAnchorSel">Select an Anchor</span>\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td width="50%">\r
+                                                               <span fckLang="DlgLnkAnchorByName">By Anchor Name</span><br />\r
+                                                               <select id="cmbAnchorName" onchange="GetE('cmbAnchorId').value='';" style="WIDTH: 100%">\r
+                                                                       <option value="" selected="selected"></option>\r
+                                                               </select>\r
+                                                       </td>\r
+                                                       <td>&nbsp;&nbsp;&nbsp;</td>\r
+                                                       <td width="50%">\r
+                                                               <span fckLang="DlgLnkAnchorById">By Element Id</span><br />\r
+                                                               <select id="cmbAnchorId" onchange="GetE('cmbAnchorName').value='';" style="WIDTH: 100%">\r
+                                                                       <option value="" selected="selected"></option>\r
+                                                               </select>\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </div>\r
+                               <div id="divNoAnchor" style="DISPLAY: none">\r
+                                       <span fckLang="DlgLnkNoAnchors">&lt;No anchors available in the document&gt;</span>\r
+                               </div>\r
+                       </div>\r
+                       <div id="divLinkTypeEMail" style="DISPLAY: none">\r
+                               <span fckLang="DlgLnkEMail">E-Mail Address</span><br />\r
+                               <input id="txtEMailAddress" style="WIDTH: 100%" type="text" /><br />\r
+                               <span fckLang="DlgLnkEMailSubject">Message Subject</span><br />\r
+                               <input id="txtEMailSubject" style="WIDTH: 100%" type="text" /><br />\r
+                               <span fckLang="DlgLnkEMailBody">Message Body</span><br />\r
+                               <textarea id="txtEMailBody" style="WIDTH: 100%" rows="3" cols="20"></textarea>\r
+                       </div>\r
+               </div>\r
+               <div id="divUpload" style="DISPLAY: none">\r
+                       <form id="frmUpload" method="post" target="UploadWindow" enctype="multipart/form-data" action="" onsubmit="return CheckUpload();">\r
+                               <span fckLang="DlgLnkUpload">Upload</span><br />\r
+                               <input id="txtUploadFile" style="WIDTH: 100%" type="file" size="40" name="NewFile" /><br />\r
+                               <br />\r
+                               <input id="btnUpload" type="submit" value="Send it to the Server" fckLang="DlgLnkBtnUpload" />\r
+                               <iframe name="UploadWindow" style="DISPLAY: none" src="javascript:void(0)"></iframe>\r
+                       </form>\r
+               </div>\r
+               <div id="divTarget" style="DISPLAY: none">\r
+                       <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                               <tr>\r
+                                       <td nowrap="nowrap">\r
+                                               <span fckLang="DlgLnkTarget">Target</span><br />\r
+                                               <select id="cmbTarget" onchange="SetTarget(this.value);">\r
+                                                       <option value="" fckLang="DlgGenNotSet" selected="selected">&lt;not set&gt;</option>\r
+                                                       <option value="frame" fckLang="DlgLnkTargetFrame">&lt;frame&gt;</option>\r
+                                                       <option value="popup" fckLang="DlgLnkTargetPopup">&lt;popup window&gt;</option>\r
+                                                       <option value="_blank" fckLang="DlgLnkTargetBlank">New Window (_blank)</option>\r
+                                                       <option value="_top" fckLang="DlgLnkTargetTop">Topmost Window (_top)</option>\r
+                                                       <option value="_self" fckLang="DlgLnkTargetSelf">Same Window (_self)</option>\r
+                                                       <option value="_parent" fckLang="DlgLnkTargetParent">Parent Window (_parent)</option>\r
+                                               </select>\r
+                                       </td>\r
+                                       <td>&nbsp;</td>\r
+                                       <td id="tdTargetFrame" nowrap="nowrap" width="100%">\r
+                                               <span fckLang="DlgLnkTargetFrameName">Target Frame Name</span><br />\r
+                                               <input id="txtTargetFrame" style="WIDTH: 100%" type="text" onkeyup="OnTargetNameChange();"\r
+                                                       onchange="OnTargetNameChange();" />\r
+                                       </td>\r
+                                       <td id="tdPopupName" style="DISPLAY: none" nowrap="nowrap" width="100%">\r
+                                               <span fckLang="DlgLnkPopWinName">Popup Window Name</span><br />\r
+                                               <input id="txtPopupName" style="WIDTH: 100%" type="text" />\r
+                                       </td>\r
+                               </tr>\r
+                       </table>\r
+                       <br />\r
+                       <table id="tablePopupFeatures" style="DISPLAY: none" cellspacing="0" cellpadding="0" align="center"\r
+                               border="0">\r
+                               <tr>\r
+                                       <td>\r
+                                               <span fckLang="DlgLnkPopWinFeat">Popup Window Features</span><br />\r
+                                               <table cellspacing="0" cellpadding="0" border="0">\r
+                                                       <tr>\r
+                                                               <td valign="top" nowrap="nowrap" width="50%">\r
+                                                                       <input id="chkPopupResizable" name="chkFeature" value="resizable" type="checkbox" /><label for="chkPopupResizable" fckLang="DlgLnkPopResize">Resizable</label><br />\r
+                                                                       <input id="chkPopupLocationBar" name="chkFeature" value="location" type="checkbox" /><label for="chkPopupLocationBar" fckLang="DlgLnkPopLocation">Location\r
+                                                                               Bar</label><br />\r
+                                                                       <input id="chkPopupManuBar" name="chkFeature" value="menubar" type="checkbox" /><label for="chkPopupManuBar" fckLang="DlgLnkPopMenu">Menu\r
+                                                                               Bar</label><br />\r
+                                                                       <input id="chkPopupScrollBars" name="chkFeature" value="scrollbars" type="checkbox" /><label for="chkPopupScrollBars" fckLang="DlgLnkPopScroll">Scroll\r
+                                                                               Bars</label>\r
+                                                               </td>\r
+                                                               <td></td>\r
+                                                               <td valign="top" nowrap="nowrap" width="50%">\r
+                                                                       <input id="chkPopupStatusBar" name="chkFeature" value="status" type="checkbox" /><label for="chkPopupStatusBar" fckLang="DlgLnkPopStatus">Status\r
+                                                                               Bar</label><br />\r
+                                                                       <input id="chkPopupToolbar" name="chkFeature" value="toolbar" type="checkbox" /><label for="chkPopupToolbar" fckLang="DlgLnkPopToolbar">Toolbar</label><br />\r
+                                                                       <input id="chkPopupFullScreen" name="chkFeature" value="fullscreen" type="checkbox" /><label for="chkPopupFullScreen" fckLang="DlgLnkPopFullScrn">Full\r
+                                                                               Screen (IE)</label><br />\r
+                                                                       <input id="chkPopupDependent" name="chkFeature" value="dependent" type="checkbox" /><label for="chkPopupDependent" fckLang="DlgLnkPopDependent">Dependent\r
+                                                                               (Netscape)</label>\r
+                                                               </td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td valign="top" nowrap="nowrap" width="50%">&nbsp;</td>\r
+                                                               <td></td>\r
+                                                               <td valign="top" nowrap="nowrap" width="50%"></td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td valign="top">\r
+                                                                       <table cellspacing="0" cellpadding="0" border="0">\r
+                                                                               <tr>\r
+                                                                                       <td nowrap="nowrap"><span fckLang="DlgLnkPopWidth">Width</span></td>\r
+                                                                                       <td>&nbsp;<input id="txtPopupWidth" type="text" maxlength="4" size="4" /></td>\r
+                                                                               </tr>\r
+                                                                               <tr>\r
+                                                                                       <td nowrap="nowrap"><span fckLang="DlgLnkPopHeight">Height</span></td>\r
+                                                                                       <td>&nbsp;<input id="txtPopupHeight" type="text" maxlength="4" size="4" /></td>\r
+                                                                               </tr>\r
+                                                                       </table>\r
+                                                               </td>\r
+                                                               <td>&nbsp;&nbsp;</td>\r
+                                                               <td valign="top">\r
+                                                                       <table cellspacing="0" cellpadding="0" border="0">\r
+                                                                               <tr>\r
+                                                                                       <td nowrap="nowrap"><span fckLang="DlgLnkPopLeft">Left Position</span></td>\r
+                                                                                       <td>&nbsp;<input id="txtPopupLeft" type="text" maxlength="4" size="4" /></td>\r
+                                                                               </tr>\r
+                                                                               <tr>\r
+                                                                                       <td nowrap="nowrap"><span fckLang="DlgLnkPopTop">Top Position</span></td>\r
+                                                                                       <td>&nbsp;<input id="txtPopupTop" type="text" maxlength="4" size="4" /></td>\r
+                                                                               </tr>\r
+                                                                       </table>\r
+                                                               </td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                               </tr>\r
+                       </table>\r
+               </div>\r
+               <div id="divAttribs" style="DISPLAY: none">\r
+                       <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">\r
+                               <tr>\r
+                                       <td valign="top" width="50%">\r
+                                               <span fckLang="DlgGenId">Id</span><br />\r
+                                               <input id="txtAttId" style="WIDTH: 100%" type="text" />\r
+                                       </td>\r
+                                       <td width="1"></td>\r
+                                       <td valign="top">\r
+                                               <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">\r
+                                                       <tr>\r
+                                                               <td width="60%">\r
+                                                                       <span fckLang="DlgGenLangDir">Language Direction</span><br />\r
+                                                                       <select id="cmbAttLangDir" style="WIDTH: 100%">\r
+                                                                               <option value="" fckLang="DlgGenNotSet" selected>&lt;not set&gt;</option>\r
+                                                                               <option value="ltr" fckLang="DlgGenLangDirLtr">Left to Right (LTR)</option>\r
+                                                                               <option value="rtl" fckLang="DlgGenLangDirRtl">Right to Left (RTL)</option>\r
+                                                                       </select>\r
+                                                               </td>\r
+                                                               <td width="1%">&nbsp;&nbsp;&nbsp;</td>\r
+                                                               <td nowrap="nowrap"><span fckLang="DlgGenAccessKey">Access Key</span><br />\r
+                                                                       <input id="txtAttAccessKey" style="WIDTH: 100%" type="text" maxlength="1" size="1" />\r
+                                                               </td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                               </tr>\r
+                               <tr>\r
+                                       <td valign="top" width="50%">\r
+                                               <span fckLang="DlgGenName">Name</span><br />\r
+                                               <input id="txtAttName" style="WIDTH: 100%" type="text" />\r
+                                       </td>\r
+                                       <td width="1"></td>\r
+                                       <td valign="top">\r
+                                               <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">\r
+                                                       <tr>\r
+                                                               <td width="60%">\r
+                                                                       <span fckLang="DlgGenLangCode">Language Code</span><br />\r
+                                                                       <input id="txtAttLangCode" style="WIDTH: 100%" type="text" />\r
+                                                               </td>\r
+                                                               <td width="1%">&nbsp;&nbsp;&nbsp;</td>\r
+                                                               <td nowrap="nowrap">\r
+                                                                       <span fckLang="DlgGenTabIndex">Tab Index</span><br />\r
+                                                                       <input id="txtAttTabIndex" style="WIDTH: 100%" type="text" maxlength="5" size="5" />\r
+                                                               </td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                               </tr>\r
+                               <tr>\r
+                                       <td valign="top" width="50%">&nbsp;</td>\r
+                                       <td width="1"></td>\r
+                                       <td valign="top"></td>\r
+                               </tr>\r
+                               <tr>\r
+                                       <td valign="top" width="50%">\r
+                                               <span fckLang="DlgGenTitle">Advisory Title</span><br />\r
+                                               <input id="txtAttTitle" style="WIDTH: 100%" type="text" />\r
+                                       </td>\r
+                                       <td width="1">&nbsp;&nbsp;&nbsp;</td>\r
+                                       <td valign="top">\r
+                                               <span fckLang="DlgGenContType">Advisory Content Type</span><br />\r
+                                               <input id="txtAttContentType" style="WIDTH: 100%" type="text" />\r
+                                       </td>\r
+                               </tr>\r
+                               <tr>\r
+                                       <td valign="top">\r
+                                               <span fckLang="DlgGenClass">Stylesheet Classes</span><br />\r
+                                               <input id="txtAttClasses" style="WIDTH: 100%" type="text" />\r
+                                       </td>\r
+                                       <td></td>\r
+                                       <td valign="top">\r
+                                               <span fckLang="DlgGenLinkCharset">Linked Resource Charset</span><br />\r
+                                               <input id="txtAttCharSet" style="WIDTH: 100%" type="text" />\r
+                                       </td>\r
+                               </tr>\r
+                       </table>\r
+                       <table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">\r
+                               <tr>\r
+                                       <td>\r
+                                               <span fckLang="DlgGenStyle">Style</span><br />\r
+                                               <input id="txtAttStyle" style="WIDTH: 100%" type="text" />\r
+                                       </td>\r
+                               </tr>\r
+                       </table>\r
+               </div>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..6d96499
--- /dev/null
@@ -0,0 +1,698 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Scripts related to the Link dialog window (see fck_link.html).\r
+ */\r
+\r
+var oEditor            = window.parent.InnerDialogLoaded() ;\r
+var FCK                        = oEditor.FCK ;\r
+var FCKLang            = oEditor.FCKLang ;\r
+var FCKConfig  = oEditor.FCKConfig ;\r
+var FCKRegexLib        = oEditor.FCKRegexLib ;\r
+\r
+//#### Dialog Tabs\r
+\r
+// Set the dialog tabs.\r
+window.parent.AddTab( 'Info', FCKLang.DlgLnkInfoTab ) ;\r
+\r
+if ( !FCKConfig.LinkDlgHideTarget )\r
+       window.parent.AddTab( 'Target', FCKLang.DlgLnkTargetTab, true ) ;\r
+\r
+if ( FCKConfig.LinkUpload )\r
+       window.parent.AddTab( 'Upload', FCKLang.DlgLnkUpload, true ) ;\r
+\r
+if ( !FCKConfig.LinkDlgHideAdvanced )\r
+       window.parent.AddTab( 'Advanced', FCKLang.DlgAdvancedTag ) ;\r
+\r
+// Function called when a dialog tag is selected.\r
+function OnDialogTabChange( tabCode )\r
+{\r
+       ShowE('divInfo'         , ( tabCode == 'Info' ) ) ;\r
+       ShowE('divTarget'       , ( tabCode == 'Target' ) ) ;\r
+       ShowE('divUpload'       , ( tabCode == 'Upload' ) ) ;\r
+       ShowE('divAttribs'      , ( tabCode == 'Advanced' ) ) ;\r
+\r
+       window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+//#### Regular Expressions library.\r
+var oRegex = new Object() ;\r
+\r
+oRegex.UriProtocol = /^(((http|https|ftp|news):\/\/)|mailto:)/gi ;\r
+\r
+oRegex.UrlOnChangeProtocol = /^(http|https|ftp|news):\/\/(?=.)/gi ;\r
+\r
+oRegex.UrlOnChangeTestOther = /^((javascript:)|[#\/\.])/gi ;\r
+\r
+oRegex.ReserveTarget = /^_(blank|self|top|parent)$/i ;\r
+\r
+oRegex.PopupUri = /^javascript:void\(\s*window.open\(\s*'([^']+)'\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*\)\s*$/ ;\r
+\r
+// Accessible popups\r
+oRegex.OnClickPopup = /^\s*on[cC]lick="\s*window.open\(\s*this\.href\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*;\s*return\s*false;*\s*"$/ ;\r
+\r
+oRegex.PopupFeatures = /(?:^|,)([^=]+)=(\d+|yes|no)/gi ;\r
+\r
+//#### Parser Functions\r
+\r
+var oParser = new Object() ;\r
+\r
+oParser.ParseEMailUrl = function( emailUrl )\r
+{\r
+       // Initializes the EMailInfo object.\r
+       var oEMailInfo = new Object() ;\r
+       oEMailInfo.Address      = '' ;\r
+       oEMailInfo.Subject      = '' ;\r
+       oEMailInfo.Body         = '' ;\r
+\r
+       var oParts = emailUrl.match( /^([^\?]+)\??(.+)?/ ) ;\r
+       if ( oParts )\r
+       {\r
+               // Set the e-mail address.\r
+               oEMailInfo.Address = oParts[1] ;\r
+\r
+               // Look for the optional e-mail parameters.\r
+               if ( oParts[2] )\r
+               {\r
+                       var oMatch = oParts[2].match( /(^|&)subject=([^&]+)/i ) ;\r
+                       if ( oMatch ) oEMailInfo.Subject = decodeURIComponent( oMatch[2] ) ;\r
+\r
+                       oMatch = oParts[2].match( /(^|&)body=([^&]+)/i ) ;\r
+                       if ( oMatch ) oEMailInfo.Body = decodeURIComponent( oMatch[2] ) ;\r
+               }\r
+       }\r
+\r
+       return oEMailInfo ;\r
+}\r
+\r
+oParser.CreateEMailUri = function( address, subject, body )\r
+{\r
+       var sBaseUri = 'mailto:' + address ;\r
+\r
+       var sParams = '' ;\r
+\r
+       if ( subject.length > 0 )\r
+               sParams = '?subject=' + encodeURIComponent( subject ) ;\r
+\r
+       if ( body.length > 0 )\r
+       {\r
+               sParams += ( sParams.length == 0 ? '?' : '&' ) ;\r
+               sParams += 'body=' + encodeURIComponent( body ) ;\r
+       }\r
+\r
+       return sBaseUri + sParams ;\r
+}\r
+\r
+//#### Initialization Code\r
+\r
+// oLink: The actual selected link in the editor.\r
+var oLink = FCK.Selection.MoveToAncestorNode( 'A' ) ;\r
+if ( oLink )\r
+       FCK.Selection.SelectNode( oLink ) ;\r
+\r
+window.onload = function()\r
+{\r
+       // Translate the dialog box texts.\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       // Fill the Anchor Names and Ids combos.\r
+       LoadAnchorNamesAndIds() ;\r
+\r
+       // Load the selected link information (if any).\r
+       LoadSelection() ;\r
+\r
+       // Update the dialog box.\r
+       SetLinkType( GetE('cmbLinkType').value ) ;\r
+\r
+       // Show/Hide the "Browse Server" button.\r
+       GetE('divBrowseServer').style.display = FCKConfig.LinkBrowser ? '' : 'none' ;\r
+\r
+       // Show the initial dialog content.\r
+       GetE('divInfo').style.display = '' ;\r
+\r
+       // Set the actual uploader URL.\r
+       if ( FCKConfig.LinkUpload )\r
+               GetE('frmUpload').action = FCKConfig.LinkUploadURL ;\r
+\r
+       // Set the default target (from configuration).\r
+       SetDefaultTarget() ;\r
+\r
+       // Activate the "OK" button.\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+var bHasAnchors ;\r
+\r
+function LoadAnchorNamesAndIds()\r
+{\r
+       // Since version 2.0, the anchors are replaced in the DOM by IMGs so the user see the icon\r
+       // to edit them. So, we must look for that images now.\r
+       var aAnchors = new Array() ;\r
+       var i ;\r
+       var oImages = oEditor.FCK.EditorDocument.getElementsByTagName( 'IMG' ) ;\r
+       for( i = 0 ; i < oImages.length ; i++ )\r
+       {\r
+               if ( oImages[i].getAttribute('_fckanchor') )\r
+                       aAnchors[ aAnchors.length ] = oEditor.FCK.GetRealElement( oImages[i] ) ;\r
+       }\r
+\r
+       // Add also real anchors\r
+       var oLinks = oEditor.FCK.EditorDocument.getElementsByTagName( 'A' ) ;\r
+       for( i = 0 ; i < oLinks.length ; i++ )\r
+       {\r
+               if ( oLinks[i].name && ( oLinks[i].name.length > 0 ) )\r
+                       aAnchors[ aAnchors.length ] = oLinks[i] ;\r
+       }\r
+\r
+       var aIds = oEditor.FCKTools.GetAllChildrenIds( oEditor.FCK.EditorDocument.body ) ;\r
+\r
+       bHasAnchors = ( aAnchors.length > 0 || aIds.length > 0 ) ;\r
+\r
+       for ( i = 0 ; i < aAnchors.length ; i++ )\r
+       {\r
+               var sName = aAnchors[i].name ;\r
+               if ( sName && sName.length > 0 )\r
+                       oEditor.FCKTools.AddSelectOption( GetE('cmbAnchorName'), sName, sName ) ;\r
+       }\r
+\r
+       for ( i = 0 ; i < aIds.length ; i++ )\r
+       {\r
+               oEditor.FCKTools.AddSelectOption( GetE('cmbAnchorId'), aIds[i], aIds[i] ) ;\r
+       }\r
+\r
+       ShowE( 'divSelAnchor'   , bHasAnchors ) ;\r
+       ShowE( 'divNoAnchor'    , !bHasAnchors ) ;\r
+}\r
+\r
+function LoadSelection()\r
+{\r
+       if ( !oLink ) return ;\r
+\r
+       var sType = 'url' ;\r
+\r
+       // Get the actual Link href.\r
+       var sHRef = oLink.getAttribute( '_fcksavedurl' ) ;\r
+       if ( sHRef == null )\r
+               sHRef = oLink.getAttribute( 'href' , 2 ) || '' ;\r
+\r
+       // Look for a popup javascript link.\r
+       var oPopupMatch = oRegex.PopupUri.exec( sHRef ) ;\r
+       if( oPopupMatch )\r
+       {\r
+               GetE('cmbTarget').value = 'popup' ;\r
+               sHRef = oPopupMatch[1] ;\r
+               FillPopupFields( oPopupMatch[2], oPopupMatch[3] ) ;\r
+               SetTarget( 'popup' ) ;\r
+       }\r
+\r
+       // Accesible popups, the popup data is in the onclick attribute\r
+       if ( !oPopupMatch ) {\r
+               var onclick = oLink.getAttribute( 'onclick_fckprotectedatt' ) ;\r
+               oPopupMatch = oRegex.OnClickPopup.exec( onclick ) ;\r
+               if( oPopupMatch )\r
+               {\r
+                       GetE( 'cmbTarget' ).value = 'popup' ;\r
+                       FillPopupFields( oPopupMatch[1], oPopupMatch[2] ) ;\r
+                       SetTarget( 'popup' ) ;\r
+               }\r
+       }\r
+\r
+       // Search for the protocol.\r
+       var sProtocol = oRegex.UriProtocol.exec( sHRef ) ;\r
+\r
+       if ( sProtocol )\r
+       {\r
+               sProtocol = sProtocol[0].toLowerCase() ;\r
+               GetE('cmbLinkProtocol').value = sProtocol ;\r
+\r
+               // Remove the protocol and get the remainig URL.\r
+               var sUrl = sHRef.replace( oRegex.UriProtocol, '' ) ;\r
+\r
+               if ( sProtocol == 'mailto:' )   // It is an e-mail link.\r
+               {\r
+                       sType = 'email' ;\r
+\r
+                       var oEMailInfo = oParser.ParseEMailUrl( sUrl ) ;\r
+                       GetE('txtEMailAddress').value   = oEMailInfo.Address ;\r
+                       GetE('txtEMailSubject').value   = oEMailInfo.Subject ;\r
+                       GetE('txtEMailBody').value              = oEMailInfo.Body ;\r
+               }\r
+               else                            // It is a normal link.\r
+               {\r
+                       sType = 'url' ;\r
+                       GetE('txtUrl').value = sUrl ;\r
+               }\r
+       }\r
+       else if ( sHRef.substr(0,1) == '#' && sHRef.length > 1 )        // It is an anchor link.\r
+       {\r
+               sType = 'anchor' ;\r
+               GetE('cmbAnchorName').value = GetE('cmbAnchorId').value = sHRef.substr(1) ;\r
+       }\r
+       else                                    // It is another type of link.\r
+       {\r
+               sType = 'url' ;\r
+\r
+               GetE('cmbLinkProtocol').value = '' ;\r
+               GetE('txtUrl').value = sHRef ;\r
+       }\r
+\r
+       if ( !oPopupMatch )\r
+       {\r
+               // Get the target.\r
+               var sTarget = oLink.target ;\r
+\r
+               if ( sTarget && sTarget.length > 0 )\r
+               {\r
+                       if ( oRegex.ReserveTarget.test( sTarget ) )\r
+                       {\r
+                               sTarget = sTarget.toLowerCase() ;\r
+                               GetE('cmbTarget').value = sTarget ;\r
+                       }\r
+                       else\r
+                               GetE('cmbTarget').value = 'frame' ;\r
+                       GetE('txtTargetFrame').value = sTarget ;\r
+               }\r
+       }\r
+\r
+       // Get Advances Attributes\r
+       GetE('txtAttId').value                  = oLink.id ;\r
+       GetE('txtAttName').value                = oLink.name ;\r
+       GetE('cmbAttLangDir').value             = oLink.dir ;\r
+       GetE('txtAttLangCode').value    = oLink.lang ;\r
+       GetE('txtAttAccessKey').value   = oLink.accessKey ;\r
+       GetE('txtAttTabIndex').value    = oLink.tabIndex <= 0 ? '' : oLink.tabIndex ;\r
+       GetE('txtAttTitle').value               = oLink.title ;\r
+       GetE('txtAttContentType').value = oLink.type ;\r
+       GetE('txtAttCharSet').value             = oLink.charset ;\r
+\r
+       var sClass ;\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+       {\r
+               sClass  = oLink.getAttribute('className',2) || '' ;\r
+               // Clean up temporary classes for internal use:\r
+               sClass = sClass.replace( FCKRegexLib.FCK_Class, '' ) ;\r
+\r
+               GetE('txtAttStyle').value       = oLink.style.cssText ;\r
+       }\r
+       else\r
+       {\r
+               sClass  = oLink.getAttribute('class',2) || '' ;\r
+               GetE('txtAttStyle').value       = oLink.getAttribute('style',2) || '' ;\r
+       }\r
+       GetE('txtAttClasses').value     = sClass ;\r
+\r
+       // Update the Link type combo.\r
+       GetE('cmbLinkType').value = sType ;\r
+}\r
+\r
+//#### Link type selection.\r
+function SetLinkType( linkType )\r
+{\r
+       ShowE('divLinkTypeUrl'          , (linkType == 'url') ) ;\r
+       ShowE('divLinkTypeAnchor'       , (linkType == 'anchor') ) ;\r
+       ShowE('divLinkTypeEMail'        , (linkType == 'email') ) ;\r
+\r
+       if ( !FCKConfig.LinkDlgHideTarget )\r
+               window.parent.SetTabVisibility( 'Target'        , (linkType == 'url') ) ;\r
+\r
+       if ( FCKConfig.LinkUpload )\r
+               window.parent.SetTabVisibility( 'Upload'        , (linkType == 'url') ) ;\r
+\r
+       if ( !FCKConfig.LinkDlgHideAdvanced )\r
+               window.parent.SetTabVisibility( 'Advanced'      , (linkType != 'anchor' || bHasAnchors) ) ;\r
+\r
+       if ( linkType == 'email' )\r
+               window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+//#### Target type selection.\r
+function SetTarget( targetType )\r
+{\r
+       GetE('tdTargetFrame').style.display     = ( targetType == 'popup' ? 'none' : '' ) ;\r
+       GetE('tdPopupName').style.display       =\r
+               GetE('tablePopupFeatures').style.display = ( targetType == 'popup' ? '' : 'none' ) ;\r
+\r
+       switch ( targetType )\r
+       {\r
+               case "_blank" :\r
+               case "_self" :\r
+               case "_parent" :\r
+               case "_top" :\r
+                       GetE('txtTargetFrame').value = targetType ;\r
+                       break ;\r
+               case "" :\r
+                       GetE('txtTargetFrame').value = '' ;\r
+                       break ;\r
+       }\r
+\r
+       if ( targetType == 'popup' )\r
+               window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+//#### Called while the user types the URL.\r
+function OnUrlChange()\r
+{\r
+       var sUrl = GetE('txtUrl').value ;\r
+       var sProtocol = oRegex.UrlOnChangeProtocol.exec( sUrl ) ;\r
+\r
+       if ( sProtocol )\r
+       {\r
+               sUrl = sUrl.substr( sProtocol[0].length ) ;\r
+               GetE('txtUrl').value = sUrl ;\r
+               GetE('cmbLinkProtocol').value = sProtocol[0].toLowerCase() ;\r
+       }\r
+       else if ( oRegex.UrlOnChangeTestOther.test( sUrl ) )\r
+       {\r
+               GetE('cmbLinkProtocol').value = '' ;\r
+       }\r
+}\r
+\r
+//#### Called while the user types the target name.\r
+function OnTargetNameChange()\r
+{\r
+       var sFrame = GetE('txtTargetFrame').value ;\r
+\r
+       if ( sFrame.length == 0 )\r
+               GetE('cmbTarget').value = '' ;\r
+       else if ( oRegex.ReserveTarget.test( sFrame ) )\r
+               GetE('cmbTarget').value = sFrame.toLowerCase() ;\r
+       else\r
+               GetE('cmbTarget').value = 'frame' ;\r
+}\r
+\r
+// Accesible popups\r
+function BuildOnClickPopup()\r
+{\r
+       var sWindowName = "'" + GetE('txtPopupName').value.replace(/\W/gi, "") + "'" ;\r
+\r
+       var sFeatures = '' ;\r
+       var aChkFeatures = document.getElementsByName( 'chkFeature' ) ;\r
+       for ( var i = 0 ; i < aChkFeatures.length ; i++ )\r
+       {\r
+               if ( i > 0 ) sFeatures += ',' ;\r
+               sFeatures += aChkFeatures[i].value + '=' + ( aChkFeatures[i].checked ? 'yes' : 'no' ) ;\r
+       }\r
+\r
+       if ( GetE('txtPopupWidth').value.length > 0 )   sFeatures += ',width=' + GetE('txtPopupWidth').value ;\r
+       if ( GetE('txtPopupHeight').value.length > 0 )  sFeatures += ',height=' + GetE('txtPopupHeight').value ;\r
+       if ( GetE('txtPopupLeft').value.length > 0 )    sFeatures += ',left=' + GetE('txtPopupLeft').value ;\r
+       if ( GetE('txtPopupTop').value.length > 0 )             sFeatures += ',top=' + GetE('txtPopupTop').value ;\r
+\r
+       if ( sFeatures != '' )\r
+               sFeatures = sFeatures + ",status" ;\r
+\r
+       return ( "window.open(this.href," + sWindowName + ",'" + sFeatures + "'); return false" ) ;\r
+}\r
+\r
+//#### Fills all Popup related fields.\r
+function FillPopupFields( windowName, features )\r
+{\r
+       if ( windowName )\r
+               GetE('txtPopupName').value = windowName ;\r
+\r
+       var oFeatures = new Object() ;\r
+       var oFeaturesMatch ;\r
+       while( ( oFeaturesMatch = oRegex.PopupFeatures.exec( features ) ) != null )\r
+       {\r
+               var sValue = oFeaturesMatch[2] ;\r
+               if ( sValue == ( 'yes' || '1' ) )\r
+                       oFeatures[ oFeaturesMatch[1] ] = true ;\r
+               else if ( ! isNaN( sValue ) && sValue != 0 )\r
+                       oFeatures[ oFeaturesMatch[1] ] = sValue ;\r
+       }\r
+\r
+       // Update all features check boxes.\r
+       var aChkFeatures = document.getElementsByName('chkFeature') ;\r
+       for ( var i = 0 ; i < aChkFeatures.length ; i++ )\r
+       {\r
+               if ( oFeatures[ aChkFeatures[i].value ] )\r
+                       aChkFeatures[i].checked = true ;\r
+       }\r
+\r
+       // Update position and size text boxes.\r
+       if ( oFeatures['width'] )       GetE('txtPopupWidth').value             = oFeatures['width'] ;\r
+       if ( oFeatures['height'] )      GetE('txtPopupHeight').value    = oFeatures['height'] ;\r
+       if ( oFeatures['left'] )        GetE('txtPopupLeft').value              = oFeatures['left'] ;\r
+       if ( oFeatures['top'] )         GetE('txtPopupTop').value               = oFeatures['top'] ;\r
+}\r
+\r
+//#### The OK button was hit.\r
+function Ok()\r
+{\r
+       var sUri, sInnerHtml ;\r
+\r
+       switch ( GetE('cmbLinkType').value )\r
+       {\r
+               case 'url' :\r
+                       sUri = GetE('txtUrl').value ;\r
+\r
+                       if ( sUri.length == 0 )\r
+                       {\r
+                               alert( FCKLang.DlnLnkMsgNoUrl ) ;\r
+                               return false ;\r
+                       }\r
+\r
+                       sUri = GetE('cmbLinkProtocol').value + sUri ;\r
+\r
+                       break ;\r
+\r
+               case 'email' :\r
+                       sUri = GetE('txtEMailAddress').value ;\r
+\r
+                       if ( sUri.length == 0 )\r
+                       {\r
+                               alert( FCKLang.DlnLnkMsgNoEMail ) ;\r
+                               return false ;\r
+                       }\r
+\r
+                       sUri = oParser.CreateEMailUri(\r
+                               sUri,\r
+                               GetE('txtEMailSubject').value,\r
+                               GetE('txtEMailBody').value ) ;\r
+                       break ;\r
+\r
+               case 'anchor' :\r
+                       var sAnchor = GetE('cmbAnchorName').value ;\r
+                       if ( sAnchor.length == 0 ) sAnchor = GetE('cmbAnchorId').value ;\r
+\r
+                       if ( sAnchor.length == 0 )\r
+                       {\r
+                               alert( FCKLang.DlnLnkMsgNoAnchor ) ;\r
+                               return false ;\r
+                       }\r
+\r
+                       sUri = '#' + sAnchor ;\r
+                       break ;\r
+       }\r
+\r
+       // If no link is selected, create a new one (it may result in more than one link creation - #220).\r
+       var aLinks = oLink ? [ oLink ] : oEditor.FCK.CreateLink( sUri ) ;\r
+\r
+       // If no selection, no links are created, so use the uri as the link text (by dom, 2006-05-26)\r
+       var aHasSelection = ( aLinks.length > 0 ) ;\r
+       if ( !aHasSelection )\r
+       {\r
+               sInnerHtml = sUri;\r
+\r
+               // Built a better text for empty links.\r
+               switch ( GetE('cmbLinkType').value )\r
+               {\r
+                       // anchor: use old behavior --> return true\r
+                       case 'anchor':\r
+                               sInnerHtml = sInnerHtml.replace( /^#/, '' ) ;\r
+                               break ;\r
+\r
+                       // url: try to get path\r
+                       case 'url':\r
+                               var oLinkPathRegEx = new RegExp("//?([^?\"']+)([?].*)?$") ;\r
+                               var asLinkPath = oLinkPathRegEx.exec( sUri ) ;\r
+                               if (asLinkPath != null)\r
+                                       sInnerHtml = asLinkPath[1];  // use matched path\r
+                               break ;\r
+\r
+                       // mailto: try to get email address\r
+                       case 'email':\r
+                               sInnerHtml = GetE('txtEMailAddress').value ;\r
+                               break ;\r
+               }\r
+\r
+               // Create a new (empty) anchor.\r
+               aLinks = [ oEditor.FCK.CreateElement( 'a' ) ] ;\r
+       }\r
+\r
+       oEditor.FCKUndo.SaveUndoStep() ;\r
+\r
+       for ( var i = 0 ; i < aLinks.length ; i++ )\r
+       {\r
+               oLink = aLinks[i] ;\r
+\r
+               if ( aHasSelection )\r
+                       sInnerHtml = oLink.innerHTML ;          // Save the innerHTML (IE changes it if it is like an URL).\r
+\r
+               oLink.href = sUri ;\r
+               SetAttribute( oLink, '_fcksavedurl', sUri ) ;\r
+\r
+               // Accesible popups\r
+               if( GetE('cmbTarget').value == 'popup' )\r
+               {\r
+                       SetAttribute( oLink, 'onclick_fckprotectedatt', " onclick=\"" + BuildOnClickPopup() + "\"") ;\r
+               }\r
+               else\r
+               {\r
+                       // Check if the previous onclick was for a popup:\r
+                       // In that case remove the onclick handler.\r
+                       var onclick = oLink.getAttribute( 'onclick_fckprotectedatt' ) ;\r
+                       if( oRegex.OnClickPopup.test( onclick ) )\r
+                               SetAttribute( oLink, 'onclick_fckprotectedatt', '' ) ;\r
+               }\r
+\r
+               oLink.innerHTML = sInnerHtml ;          // Set (or restore) the innerHTML\r
+\r
+               // Target\r
+               if( GetE('cmbTarget').value != 'popup' )\r
+                       SetAttribute( oLink, 'target', GetE('txtTargetFrame').value ) ;\r
+               else\r
+                       SetAttribute( oLink, 'target', null ) ;\r
+\r
+               // Let's set the "id" only for the first link to avoid duplication.\r
+               if ( i == 0 )\r
+                       SetAttribute( oLink, 'id', GetE('txtAttId').value ) ;\r
+\r
+               // Advances Attributes\r
+               SetAttribute( oLink, 'name'             , GetE('txtAttName').value ) ;\r
+               SetAttribute( oLink, 'dir'              , GetE('cmbAttLangDir').value ) ;\r
+               SetAttribute( oLink, 'lang'             , GetE('txtAttLangCode').value ) ;\r
+               SetAttribute( oLink, 'accesskey', GetE('txtAttAccessKey').value ) ;\r
+               SetAttribute( oLink, 'tabindex' , ( GetE('txtAttTabIndex').value > 0 ? GetE('txtAttTabIndex').value : null ) ) ;\r
+               SetAttribute( oLink, 'title'    , GetE('txtAttTitle').value ) ;\r
+               SetAttribute( oLink, 'type'             , GetE('txtAttContentType').value ) ;\r
+               SetAttribute( oLink, 'charset'  , GetE('txtAttCharSet').value ) ;\r
+\r
+               if ( oEditor.FCKBrowserInfo.IsIE )\r
+               {\r
+                       var sClass = GetE('txtAttClasses').value ;\r
+                       // If it's also an anchor add an internal class\r
+                       if ( GetE('txtAttName').value.length != 0 )\r
+                               sClass += ' FCK__AnchorC' ;\r
+                       SetAttribute( oLink, 'className', sClass ) ;\r
+\r
+                       oLink.style.cssText = GetE('txtAttStyle').value ;\r
+               }\r
+               else\r
+               {\r
+                       SetAttribute( oLink, 'class', GetE('txtAttClasses').value ) ;\r
+                       SetAttribute( oLink, 'style', GetE('txtAttStyle').value ) ;\r
+               }\r
+       }\r
+\r
+       // Select the (first) link.\r
+       oEditor.FCKSelection.SelectNode( aLinks[0] );\r
+\r
+       return true ;\r
+}\r
+\r
+function BrowseServer()\r
+{\r
+       OpenFileBrowser( FCKConfig.LinkBrowserURL, FCKConfig.LinkBrowserWindowWidth, FCKConfig.LinkBrowserWindowHeight ) ;\r
+}\r
+\r
+function SetUrl( url )\r
+{\r
+       document.getElementById('txtUrl').value = url ;\r
+       OnUrlChange() ;\r
+       window.parent.SetSelectedTab( 'Info' ) ;\r
+}\r
+\r
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )\r
+{\r
+       switch ( errorNumber )\r
+       {\r
+               case 0 :        // No errors\r
+                       alert( 'Your file has been successfully uploaded' ) ;\r
+                       break ;\r
+               case 1 :        // Custom error\r
+                       alert( customMsg ) ;\r
+                       return ;\r
+               case 101 :      // Custom warning\r
+                       alert( customMsg ) ;\r
+                       break ;\r
+               case 201 :\r
+                       alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;\r
+                       break ;\r
+               case 202 :\r
+                       alert( 'Invalid file type' ) ;\r
+                       return ;\r
+               case 203 :\r
+                       alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;\r
+                       return ;\r
+               default :\r
+                       alert( 'Error on file upload. Error number: ' + errorNumber ) ;\r
+                       return ;\r
+       }\r
+\r
+       SetUrl( fileUrl ) ;\r
+       GetE('frmUpload').reset() ;\r
+}\r
+\r
+var oUploadAllowedExtRegex     = new RegExp( FCKConfig.LinkUploadAllowedExtensions, 'i' ) ;\r
+var oUploadDeniedExtRegex      = new RegExp( FCKConfig.LinkUploadDeniedExtensions, 'i' ) ;\r
+\r
+function CheckUpload()\r
+{\r
+       var sFile = GetE('txtUploadFile').value ;\r
+\r
+       if ( sFile.length == 0 )\r
+       {\r
+               alert( 'Please select a file to upload' ) ;\r
+               return false ;\r
+       }\r
+\r
+       if ( ( FCKConfig.LinkUploadAllowedExtensions.length > 0 && !oUploadAllowedExtRegex.test( sFile ) ) ||\r
+               ( FCKConfig.LinkUploadDeniedExtensions.length > 0 && oUploadDeniedExtRegex.test( sFile ) ) )\r
+       {\r
+               OnUploadCompleted( 202 ) ;\r
+               return false ;\r
+       }\r
+\r
+       return true ;\r
+}\r
+\r
+function SetDefaultTarget()\r
+{\r
+       var target = FCKConfig.DefaultLinkTarget + '' ;\r
+       \r
+       if ( oLink || target.length == 0 )\r
+               return ;\r
+\r
+       switch ( target )\r
+       {\r
+               case '_blank' :\r
+               case '_self' :\r
+               case '_parent' :\r
+               case '_top' :\r
+                       GetE('cmbTarget').value = target ;\r
+                       break ;\r
+               default :\r
+                       GetE('cmbTarget').value = 'frame' ;\r
+                       break ;\r
+       }\r
+       \r
+       GetE('txtTargetFrame').value = target ;\r
+}\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_listprop.html b/httemplate/elements/fckeditor/editor/dialog/fck_listprop.html
new file mode 100644 (file)
index 0000000..a0a927e
--- /dev/null
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Bulleted List dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta content="noindex, nofollow" name="robots" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+var sListType = ( location.search == '?OL' ? 'OL' : 'UL' ) ;\r
+\r
+var oActiveEl = oEditor.FCKSelection.MoveToAncestorNode( sListType ) ;\r
+var oActiveSel ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( sListType == 'UL' )\r
+               oActiveSel = GetE('selBulleted') ;\r
+       else\r
+       {\r
+               if ( oActiveEl )\r
+               {\r
+                       oActiveSel = GetE('selNumbered') ;\r
+                       GetE('eStart').style.display = '' ;\r
+                       GetE('txtStartPosition').value  = GetAttribute( oActiveEl, 'start' ) ;\r
+               }\r
+       }\r
+\r
+       oActiveSel.style.display = '' ;\r
+\r
+       if ( oActiveEl )\r
+       {\r
+               if ( oActiveEl.getAttribute('type') )\r
+                       oActiveSel.value = oActiveEl.getAttribute('type') ;\r
+       }\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( oActiveEl ){\r
+               SetAttribute( oActiveEl, 'type' , oActiveSel.value ) ;\r
+               if(oActiveEl.tagName == 'OL')\r
+                       SetAttribute( oActiveEl, 'start', GetE('txtStartPosition').value ) ;\r
+       }\r
+\r
+       return true ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden">\r
+       <table width="100%" style="height: 100%">\r
+               <tr>\r
+                       <td style="text-align:center">\r
+                               <table cellspacing="0" cellpadding="0" border="0" style="margin-left: auto; margin-right: auto;">\r
+                                       <tr>\r
+                                               <td id="eStart" style="display: none; padding-right: 5px; padding-left: 5px">\r
+                                                       <span fcklang="DlgLstStart">Start</span><br />\r
+                                                       <input type="text" id="txtStartPosition" size="5" />\r
+                                               </td>\r
+                                               <td style="padding-right: 5px; padding-left: 5px">\r
+                                                       <span fcklang="DlgLstType">List Type</span><br />\r
+                                                       <select id="selBulleted" style="display: none">\r
+                                                               <option value="" selected="selected"></option>\r
+                                                               <option value="circle" fcklang="DlgLstTypeCircle">Circle</option>\r
+                                                               <option value="disc" fcklang="DlgLstTypeDisc">Disc</option>\r
+                                                               <option value="square" fcklang="DlgLstTypeSquare">Square</option>\r
+                                                       </select>\r
+                                                       <select id="selNumbered" style="display: none">\r
+                                                               <option value="" selected="selected"></option>\r
+                                                               <option value="1" fcklang="DlgLstTypeNumbers">Numbers (1, 2, 3)</option>\r
+                                                               <option value="a" fcklang="DlgLstTypeLCase">Lowercase Letters (a, b, c)</option>\r
+                                                               <option value="A" fcklang="DlgLstTypeUCase">Uppercase Letters (A, B, C)</option>\r
+                                                               <option value="i" fcklang="DlgLstTypeSRoman">Small Roman Numerals (i, ii, iii)</option>\r
+                                                               <option value="I" fcklang="DlgLstTypeLRoman">Large Roman Numerals (I, II, III)</option>\r
+                                                       </select>\r
+                                                       &nbsp;\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_paste.html b/httemplate/elements/fckeditor/editor/dialog/fck_paste.html
new file mode 100644 (file)
index 0000000..fd16c31
--- /dev/null
@@ -0,0 +1,285 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This dialog is shown when, for some reason (usually security settings),\r
+ * the user is not able to paste data from the clipboard to the editor using\r
+ * the toolbar buttons or the context menu.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta name="robots" content="noindex, nofollow" />\r
+\r
+       <script type="text/javascript">\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+var FCK = oEditor.FCK;\r
+var FCKTools   = oEditor.FCKTools ;\r
+var FCKConfig  = oEditor.FCKConfig ;\r
+\r
+window.onload = function ()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+       \r
+       var sPastingType = window.parent.dialogArguments.CustomValue ;\r
+\r
+       if ( sPastingType == 'Word' || sPastingType == 'Security' )\r
+       {\r
+               if ( sPastingType == 'Security' )\r
+                       document.getElementById( 'xSecurityMsg' ).style.display = '' ;\r
+\r
+               var oFrame = document.getElementById('frmData') ;\r
+               oFrame.style.display = '' ;\r
+\r
+               if ( oFrame.contentDocument )\r
+                       oFrame.contentDocument.designMode = 'on' ;\r
+               else\r
+                       oFrame.contentWindow.document.body.contentEditable = true ;\r
+       }\r
+       else\r
+       {\r
+               document.getElementById('txtData').style.display = '' ;\r
+       }\r
+\r
+       if ( sPastingType != 'Word' )\r
+               document.getElementById('oWordCommands').style.display = 'none' ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+       window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       var sHtml ;\r
+\r
+       var sPastingType = window.parent.dialogArguments.CustomValue ;\r
+\r
+       if ( sPastingType == 'Word' || sPastingType == 'Security' )\r
+       {\r
+               var oFrame = document.getElementById('frmData') ;\r
+               var oBody ;\r
+\r
+               if ( oFrame.contentDocument )\r
+                       oBody = oFrame.contentDocument.body ;\r
+               else\r
+                       oBody = oFrame.contentWindow.document.body ;\r
+\r
+               if ( sPastingType == 'Word' )\r
+               {\r
+                       // If a plugin creates a FCK.CustomCleanWord function it will be called instead of the default one\r
+                       if ( typeof( FCK.CustomCleanWord ) == 'function' )\r
+                               sHtml = FCK.CustomCleanWord( oBody, document.getElementById('chkRemoveFont').checked, document.getElementById('chkRemoveStyles').checked ) ;\r
+                       else\r
+                               sHtml = CleanWord( oBody, document.getElementById('chkRemoveFont').checked, document.getElementById('chkRemoveStyles').checked ) ;\r
+               }\r
+               else\r
+                       sHtml = oBody.innerHTML ;\r
+\r
+               // Fix relative anchor URLs (IE automatically adds the current page URL).\r
+               var re = new RegExp( window.location + "#", "g" ) ;\r
+               sHtml = sHtml.replace( re, '#') ;\r
+       }\r
+       else\r
+       {\r
+               sHtml = oEditor.FCKTools.HTMLEncode( document.getElementById('txtData').value )  ;\r
+               sHtml = sHtml.replace( /\n/g, '<BR>' ) ;\r
+       }\r
+\r
+       oEditor.FCK.InsertHtml( sHtml ) ;\r
+\r
+       return true ;\r
+}\r
+\r
+function CleanUpBox()\r
+{\r
+       var oFrame = document.getElementById('frmData') ;\r
+\r
+       if ( oFrame.contentDocument )\r
+               oFrame.contentDocument.body.innerHTML = '' ;\r
+       else\r
+               oFrame.contentWindow.document.body.innerHTML = '' ;\r
+}\r
+\r
+\r
+// This function will be called from the PasteFromWord dialog (fck_paste.html)\r
+// Input: oNode a DOM node that contains the raw paste from the clipboard\r
+// bIgnoreFont, bRemoveStyles booleans according to the values set in the dialog\r
+// Output: the cleaned string\r
+function CleanWord( oNode, bIgnoreFont, bRemoveStyles )\r
+{\r
+       var html = oNode.innerHTML ;\r
+\r
+       html = html.replace(/<o:p>\s*<\/o:p>/g, '') ;\r
+       html = html.replace(/<o:p>.*?<\/o:p>/g, '&nbsp;') ;\r
+\r
+       // Remove mso-xxx styles.\r
+       html = html.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '' ) ;\r
+\r
+       // Remove margin styles.\r
+       html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*;/gi, '' ) ;\r
+       html = html.replace( /\s*MARGIN: 0cm 0cm 0pt\s*"/gi, "\"" ) ;\r
+\r
+       html = html.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '' ) ;\r
+       html = html.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"" ) ;\r
+\r
+       html = html.replace( /\s*TEXT-ALIGN: [^\s;]+;?"/gi, "\"" ) ;\r
+\r
+       html = html.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"" ) ;\r
+\r
+       html = html.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" ) ;\r
+\r
+       html = html.replace( /\s*tab-stops:[^;"]*;?/gi, '' ) ;\r
+       html = html.replace( /\s*tab-stops:[^"]*/gi, '' ) ;\r
+\r
+       // Remove FONT face attributes.\r
+       if ( bIgnoreFont )\r
+       {\r
+               html = html.replace( /\s*face="[^"]*"/gi, '' ) ;\r
+               html = html.replace( /\s*face=[^ >]*/gi, '' ) ;\r
+\r
+               html = html.replace( /\s*FONT-FAMILY:[^;"]*;?/gi, '' ) ;\r
+       }\r
+\r
+       // Remove Class attributes\r
+       html = html.replace(/<(\w[^>]*) class=([^ |>]*)([^>]*)/gi, "<$1$3") ;\r
+\r
+       // Remove styles.\r
+       if ( bRemoveStyles )\r
+               html = html.replace( /<(\w[^>]*) style="([^\"]*)"([^>]*)/gi, "<$1$3" ) ;\r
+\r
+       // Remove empty styles.\r
+       html =  html.replace( /\s*style="\s*"/gi, '' ) ;\r
+\r
+       html = html.replace( /<SPAN\s*[^>]*>\s*&nbsp;\s*<\/SPAN>/gi, '&nbsp;' ) ;\r
+\r
+       html = html.replace( /<SPAN\s*[^>]*><\/SPAN>/gi, '' ) ;\r
+\r
+       // Remove Lang attributes\r
+       html = html.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3") ;\r
+\r
+       html = html.replace( /<SPAN\s*>(.*?)<\/SPAN>/gi, '$1' ) ;\r
+\r
+       html = html.replace( /<FONT\s*>(.*?)<\/FONT>/gi, '$1' ) ;\r
+\r
+       // Remove XML elements and declarations\r
+       html = html.replace(/<\\?\?xml[^>]*>/gi, '' ) ;\r
+\r
+       // Remove Tags with XML namespace declarations: <o:p><\/o:p>\r
+       html = html.replace(/<\/?\w+:[^>]*>/gi, '' ) ;\r
+\r
+       // Remove comments [SF BUG-1481861].\r
+       html = html.replace(/<\!--.*-->/g, '' ) ;\r
+\r
+       html = html.replace( /<(U|I|STRIKE)>&nbsp;<\/\1>/g, '&nbsp;' ) ;\r
+\r
+       html = html.replace( /<H\d>\s*<\/H\d>/gi, '' ) ;\r
+\r
+       // Remove "display:none" tags.\r
+       html = html.replace( /<(\w+)[^>]*\sstyle="[^"]*DISPLAY\s?:\s?none(.*?)<\/\1>/ig, '' ) ;\r
+\r
+       if ( FCKConfig.CleanWordKeepsStructure )\r
+       {\r
+               // The original <Hn> tag send from Word is something like this: <Hn style="margin-top:0px;margin-bottom:0px">\r
+               html = html.replace( /<H(\d)([^>]*)>/gi, '<h$1>' ) ;\r
+\r
+               // Word likes to insert extra <font> tags, when using MSIE. (Wierd).\r
+               html = html.replace( /<(H\d)><FONT[^>]*>(.*?)<\/FONT><\/\1>/gi, '<$1>$2<\/$1>' );\r
+               html = html.replace( /<(H\d)><EM>(.*?)<\/EM><\/\1>/gi, '<$1>$2<\/$1>' );\r
+       }\r
+       else\r
+       {\r
+               html = html.replace( /<H1([^>]*)>/gi, '<div$1><b><font size="6">' ) ;\r
+               html = html.replace( /<H2([^>]*)>/gi, '<div$1><b><font size="5">' ) ;\r
+               html = html.replace( /<H3([^>]*)>/gi, '<div$1><b><font size="4">' ) ;\r
+               html = html.replace( /<H4([^>]*)>/gi, '<div$1><b><font size="3">' ) ;\r
+               html = html.replace( /<H5([^>]*)>/gi, '<div$1><b><font size="2">' ) ;\r
+               html = html.replace( /<H6([^>]*)>/gi, '<div$1><b><font size="1">' ) ;\r
+\r
+               html = html.replace( /<\/H\d>/gi, '<\/font><\/b><\/div>' ) ;\r
+\r
+               // Transform <P> to <DIV>\r
+               var re = new RegExp( '(<P)([^>]*>.*?)(<\/P>)', 'gi' ) ; // Different because of a IE 5.0 error\r
+               html = html.replace( re, '<div$2<\/div>' ) ;\r
+\r
+               // Remove empty tags (three times, just to be sure).\r
+               // This also removes any empty anchor\r
+               html = html.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ;\r
+               html = html.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ;\r
+               html = html.replace( /<([^\s>]+)(\s[^>]*)?>\s*<\/\1>/g, '' ) ;\r
+       }\r
+\r
+       return html ;\r
+}\r
+\r
+       </script>\r
+\r
+</head>\r
+<body style="overflow: hidden">\r
+       <table cellspacing="0" cellpadding="0" width="100%" border="0" style="height: 98%">\r
+               <tr>\r
+                       <td>\r
+                               <div id="xSecurityMsg" style="display: none">\r
+                                       <span fcklang="DlgPasteSec">Because of your browser security settings,\r
+                                               the editor is not able to access your clipboard data directly. You are required\r
+                                               to paste it again in this window.</span><br />\r
+                                       &nbsp;\r
+                               </div>\r
+                               <div>\r
+                                       <span fcklang="DlgPasteMsg2">Please paste inside the following box using the keyboard\r
+                                               (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.</span><br />\r
+                                       &nbsp;\r
+                               </div>\r
+                       </td>\r
+               </tr>\r
+               <tr>\r
+                       <td valign="top" height="100%" style="border-right: #000000 1px solid; border-top: #000000 1px solid;\r
+                               border-left: #000000 1px solid; border-bottom: #000000 1px solid">\r
+                               <textarea id="txtData" cols="80" rows="5" style="border: #000000 1px; display: none;\r
+                                       width: 99%; height: 98%"></textarea>\r
+                               <iframe id="frmData" src="javascript:void(0)" height="98%" width="99%" frameborder="0"\r
+                                       style="border-right: #000000 1px; border-top: #000000 1px; display: none; border-left: #000000 1px;\r
+                                       border-bottom: #000000 1px; background-color: #ffffff"></iframe>\r
+                       </td>\r
+               </tr>\r
+               <tr id="oWordCommands">\r
+                       <td>\r
+                               <table border="0" cellpadding="0" cellspacing="0" width="100%">\r
+                                       <tr>\r
+                                               <td nowrap="nowrap">\r
+                                                       <input id="chkRemoveFont" type="checkbox" checked="checked" />\r
+                                                       <label for="chkRemoveFont" fcklang="DlgPasteIgnoreFont">\r
+                                                               Ignore Font Face definitions</label>\r
+                                                       <br />\r
+                                                       <input id="chkRemoveStyles" type="checkbox" />\r
+                                                       <label for="chkRemoveStyles" fcklang="DlgPasteRemoveStyles">\r
+                                                               Remove Styles definitions</label>\r
+                                               </td>\r
+                                               <td align="right" valign="top">\r
+                                                       <input type="button" fcklang="DlgPasteCleanBox" value="Clean Up Box" onclick="CleanUpBox()" />\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_radiobutton.html b/httemplate/elements/fckeditor/editor/dialog/fck_radiobutton.html
new file mode 100644 (file)
index 0000000..f239ad3
--- /dev/null
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Radio Button dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Radio Button Properties</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta content="noindex, nofollow" name="robots">\r
+               <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+               <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+var oActiveEl = oEditor.FCKSelection.GetSelectedElement() ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( oActiveEl && oActiveEl.tagName.toUpperCase() == 'INPUT' && oActiveEl.type == 'radio' )\r
+       {\r
+               GetE('txtName').value           = oActiveEl.name ;\r
+               GetE('txtValue').value          = oEditor.FCKBrowserInfo.IsIE ? oActiveEl.value : GetAttribute( oActiveEl, 'value' ) ;\r
+               GetE('txtSelected').checked     = oActiveEl.checked ;\r
+       }\r
+       else\r
+               oActiveEl = null ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( !oActiveEl )\r
+       {\r
+               oActiveEl = oEditor.FCK.EditorDocument.createElement( 'INPUT' ) ;\r
+               oActiveEl.type = 'radio' ;\r
+               oActiveEl = oEditor.FCK.InsertElementAndGetIt( oActiveEl ) ;\r
+       }\r
+\r
+       if ( GetE('txtName').value.length > 0 )\r
+               oActiveEl.name = GetE('txtName').value ;\r
+\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+               oActiveEl.value = GetE('txtValue').value ;\r
+       else\r
+               SetAttribute( oActiveEl, 'value', GetE('txtValue').value ) ;\r
+\r
+       var bIsChecked = GetE('txtSelected').checked ;\r
+       SetAttribute( oActiveEl, 'checked', bIsChecked ? 'checked' : null ) ;   // For Firefox\r
+       oActiveEl.checked = bIsChecked ;\r
+\r
+       return true ;\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body style="OVERFLOW: hidden" scroll="no">\r
+               <table height="100%" width="100%">\r
+                       <tr>\r
+                               <td align="center">\r
+                                       <table border="0" cellpadding="0" cellspacing="0" width="80%">\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fckLang="DlgCheckboxName">Name</span><br>\r
+                                                               <input type="text" size="20" id="txtName" style="WIDTH: 100%">\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fckLang="DlgCheckboxValue">Value</span><br>\r
+                                                               <input type="text" size="20" id="txtValue" style="WIDTH: 100%">\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td><input type="checkbox" id="txtSelected"><label for="txtSelected" fckLang="DlgCheckboxSelected">Checked</label></td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_replace.html b/httemplate/elements/fckeditor/editor/dialog/fck_replace.html
new file mode 100644 (file)
index 0000000..fe5a788
--- /dev/null
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * "Replace" dialog box window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta content="noindex, nofollow" name="robots" />\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+function OnLoad()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage( document ) ;\r
+\r
+       window.parent.SetAutoSize( true ) ;\r
+\r
+       oEditor.FCKUndo.SaveUndoStep() ;\r
+}\r
+\r
+function btnStat(frm)\r
+{\r
+       document.getElementById('btnReplace').disabled =\r
+               document.getElementById('btnReplaceAll').disabled =\r
+                       ( document.getElementById('txtFind').value.length == 0 ) ;\r
+}\r
+\r
+function ReplaceTextNodes( parentNode, regex, replaceValue, replaceAll, hasFound )\r
+{\r
+       for ( var i = 0 ; i < parentNode.childNodes.length ; i++ )\r
+       {\r
+               var oNode = parentNode.childNodes[i] ;\r
+               if ( oNode.nodeType == 3 )\r
+               {\r
+                       var sReplaced = oNode.nodeValue.replace( regex, replaceValue ) ;\r
+                       if ( oNode.nodeValue != sReplaced )\r
+                       {\r
+                               oNode.nodeValue = sReplaced ;\r
+                               if ( ! replaceAll )\r
+                                       return true ;\r
+                               hasFound = true ;\r
+                       }\r
+               }\r
+\r
+               hasFound = ReplaceTextNodes( oNode, regex, replaceValue, replaceAll, hasFound ) ;\r
+               if ( ! replaceAll && hasFound )\r
+                       return true ;\r
+       }\r
+\r
+       return hasFound ;\r
+}\r
+\r
+function GetRegexExpr()\r
+{\r
+       var sExpr = EscapeRegexString( document.getElementById('txtFind').value ) ;\r
+\r
+       if ( document.getElementById('chkWord').checked )\r
+               sExpr = '\\b' + sExpr + '\\b' ;\r
+\r
+       return sExpr ;\r
+}\r
+\r
+function GetCase()\r
+{\r
+       return ( document.getElementById('chkCase').checked ? '' : 'i' ) ;\r
+}\r
+\r
+function GetReplacement()\r
+{\r
+       return document.getElementById('txtReplace').value.replace( /\$/g, '$$$$' ) ;\r
+}\r
+\r
+function EscapeRegexString( str )\r
+{\r
+       return str.replace( /[\\\^\$\*\+\?\{\}\.\(\)\!\|\[\]\-]/g, '\\$&' ) ;\r
+}\r
+\r
+function Replace()\r
+{\r
+       var oRegex = new RegExp( GetRegexExpr(), GetCase() ) ;\r
+       if ( !ReplaceTextNodes( oEditor.FCK.EditorDocument.body, oRegex, GetReplacement(), false, false ) )\r
+               alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ;\r
+}\r
+\r
+function ReplaceAll()\r
+{\r
+       var oRegex = new RegExp( GetRegexExpr(), GetCase() + 'g' ) ;\r
+       if ( !ReplaceTextNodes( oEditor.FCK.EditorDocument.body, oRegex, GetReplacement(), true, false ) )\r
+               alert( oEditor.FCKLang.DlgFindNotFoundMsg ) ;\r
+       window.parent.Cancel() ;\r
+}\r
+       </script>\r
+</head>\r
+<body onload="OnLoad()" style="overflow: hidden">\r
+       <table cellspacing="3" cellpadding="2" width="100%" border="0">\r
+               <tr>\r
+                       <td nowrap="nowrap">\r
+                               <label for="txtFind" fcklang="DlgReplaceFindLbl">\r
+                                       Find what:</label>\r
+                       </td>\r
+                       <td width="100%">\r
+                               <input id="txtFind" onkeyup="btnStat(this.form)" style="width: 100%" tabindex="1"\r
+                                       type="text" />\r
+                       </td>\r
+                       <td>\r
+                               <input id="btnReplace" style="width: 100%" disabled="disabled" onclick="Replace();"\r
+                                       type="button" value="Replace" fcklang="DlgReplaceReplaceBtn" />\r
+                       </td>\r
+               </tr>\r
+               <tr>\r
+                       <td valign="top" nowrap="nowrap">\r
+                               <label for="txtReplace" fcklang="DlgReplaceReplaceLbl">\r
+                                       Replace with:</label>\r
+                       </td>\r
+                       <td valign="top">\r
+                               <input id="txtReplace" style="width: 100%" tabindex="2" type="text" />\r
+                       </td>\r
+                       <td>\r
+                               <input id="btnReplaceAll" disabled="disabled" onclick="ReplaceAll()" type="button"\r
+                                       value="Replace All" fcklang="DlgReplaceReplAllBtn" />\r
+                       </td>\r
+               </tr>\r
+               <tr>\r
+                       <td valign="bottom" colspan="3">\r
+                               &nbsp;<input id="chkCase" tabindex="3" type="checkbox" /><label for="chkCase" fcklang="DlgReplaceCaseChk">Match\r
+                                       case</label>\r
+                               <br />\r
+                               &nbsp;<input id="chkWord" tabindex="4" type="checkbox" /><label for="chkWord" fcklang="DlgReplaceWordChk">Match\r
+                                       whole word</label>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_select.html b/httemplate/elements/fckeditor/editor/dialog/fck_select.html
new file mode 100644 (file)
index 0000000..cb48b50
--- /dev/null
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Select dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Select Properties</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta content="noindex, nofollow" name="robots">\r
+               <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+               <script type="text/javascript" src="fck_select/fck_select.js"></script>\r
+               <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+var oActiveEl = oEditor.FCKSelection.GetSelectedElement() ;\r
+\r
+var oListText ;\r
+var oListValue ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       oListText       = document.getElementById( 'cmbText' ) ;\r
+       oListValue      = document.getElementById( 'cmbValue' ) ;\r
+\r
+       if ( oActiveEl && oActiveEl.tagName == 'SELECT' )\r
+       {\r
+               GetE('txtName').value           = oActiveEl.name ;\r
+               GetE('txtSelValue').value       = oActiveEl.value ;\r
+               GetE('txtLines').value          = GetAttribute( oActiveEl, 'size' ) ;\r
+               GetE('chkMultiple').checked     = oActiveEl.multiple ;\r
+\r
+               // Load the actual options\r
+               for ( var i = 0 ; i < oActiveEl.options.length ; i++ )\r
+               {\r
+                       var sText       = HTMLDecode( oActiveEl.options[i].innerHTML ) ;\r
+                       var sValue      = oActiveEl.options[i].value ;\r
+\r
+                       AddComboOption( oListText, sText, sText ) ;\r
+                       AddComboOption( oListValue, sValue, sValue ) ;\r
+               }\r
+       }\r
+       else\r
+               oActiveEl = null ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       var sSize = GetE('txtLines').value ;\r
+       if ( sSize == null || isNaN( sSize ) || sSize <= 1 )\r
+               sSize = '' ;\r
+\r
+       if ( !oActiveEl )\r
+       {\r
+               oActiveEl = oEditor.FCK.EditorDocument.createElement( 'SELECT' ) ;\r
+               oActiveEl = oEditor.FCK.InsertElementAndGetIt( oActiveEl ) ;\r
+       }\r
+\r
+       SetAttribute( oActiveEl, 'name' , GetE('txtName').value ) ;\r
+       SetAttribute( oActiveEl, 'size' , sSize ) ;\r
+       oActiveEl.multiple = ( sSize.length > 0 && GetE('chkMultiple').checked ) ;\r
+\r
+       // Remove all options.\r
+       while ( oActiveEl.options.length > 0 )\r
+               oActiveEl.remove(0) ;\r
+\r
+       // Add all available options.\r
+       for ( var i = 0 ; i < oListText.options.length ; i++ )\r
+       {\r
+               var sText       = oListText.options[i].value ;\r
+               var sValue      = oListValue.options[i].value ;\r
+               if ( sValue.length == 0 ) sValue = sText ;\r
+\r
+               var oOption = AddComboOption( oActiveEl, sText, sValue, oDOM ) ;\r
+\r
+               if ( sValue == GetE('txtSelValue').value )\r
+               {\r
+                       SetAttribute( oOption, 'selected', 'selected' ) ;\r
+                       oOption.selected = true ;\r
+               }\r
+       }\r
+\r
+       return true ;\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body style='OVERFLOW: hidden' scroll='no'>\r
+               <table width="100%" height="100%">\r
+                       <tr>\r
+                               <td>\r
+                                       <table width="100%">\r
+                                               <tr>\r
+                                                       <td nowrap><span fckLang="DlgSelectName">Name</span>&nbsp;</td>\r
+                                                       <td width="100%" colSpan="2"><input id="txtName" style="WIDTH: 100%" type="text"></td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td nowrap><span fckLang="DlgSelectValue">Value</span>&nbsp;</td>\r
+                                                       <td width="100%" colSpan="2"><input id="txtSelValue" style="WIDTH: 100%; BACKGROUND-COLOR: buttonface" type="text" readonly></td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td nowrap><span fckLang="DlgSelectSize">Size</span>&nbsp;</td>\r
+                                                       <td nowrap><input id="txtLines" type="text" size="2" value="">&nbsp;<span fckLang="DlgSelectLines">lines</span></td>\r
+                                                       <td nowrap align="right"><input id="chkMultiple" name="chkMultiple" type="checkbox"><label for="chkMultiple" fckLang="DlgSelectChkMulti">Allow\r
+                                                                       multiple selections</label></td>\r
+                                               </tr>\r
+                                       </table>\r
+                                       <br>\r
+                                       <hr style="POSITION: absolute">\r
+                                       <span style="LEFT: 10px; POSITION: relative; TOP: -7px" class="BackColor">&nbsp;<span fckLang="DlgSelectOpAvail">Available\r
+                                                       Options</span>&nbsp;</span>\r
+                                       <table width="100%">\r
+                                               <tr>\r
+                                                       <td width="50%"><span fckLang="DlgSelectOpText">Text</span><br>\r
+                                                               <input id="txtText" style="WIDTH: 100%" type="text" name="txtText">\r
+                                                       </td>\r
+                                                       <td width="50%"><span fckLang="DlgSelectOpValue">Value</span><br>\r
+                                                               <input id="txtValue" style="WIDTH: 100%" type="text" name="txtValue">\r
+                                                       </td>\r
+                                                       <td vAlign="bottom"><input onclick="Add();" type="button" fckLang="DlgSelectBtnAdd" value="Add"></td>\r
+                                                       <td vAlign="bottom"><input onclick="Modify();" type="button" fckLang="DlgSelectBtnModify" value="Modify"></td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td rowSpan="2"><select id="cmbText" style="WIDTH: 100%" onchange="GetE('cmbValue').selectedIndex = this.selectedIndex;Select(this);"\r
+                                                                       size="5" name="cmbText"></select>\r
+                                                       </td>\r
+                                                       <td rowSpan="2"><select id="cmbValue" style="WIDTH: 100%" onchange="GetE('cmbText').selectedIndex = this.selectedIndex;Select(this);"\r
+                                                                       size="5" name="cmbValue"></select>\r
+                                                       </td>\r
+                                                       <td vAlign="top" colSpan="2">\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td vAlign="bottom" colSpan="2"><input style="WIDTH: 100%" onclick="Move(-1);" type="button" fckLang="DlgSelectBtnUp" value="Up">\r
+                                                               <br>\r
+                                                               <input style="WIDTH: 100%" onclick="Move(1);" type="button" fckLang="DlgSelectBtnDown"\r
+                                                                       value="Down">\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <TR>\r
+                                                       <TD vAlign="bottom" colSpan="4"><INPUT onclick="SetSelectedValue();" type="button" fckLang="DlgSelectBtnSetValue" value="Set as selected value">&nbsp;&nbsp;\r
+                                                               <input onclick="Delete();" type="button" fckLang="DlgSelectBtnDelete" value="Delete"></TD>\r
+                                               </TR>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..181b666
--- /dev/null
@@ -0,0 +1,194 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Scripts for the fck_select.html page.\r
+ */\r
+\r
+function Select( combo )\r
+{\r
+       var iIndex = combo.selectedIndex ;\r
+\r
+       oListText.selectedIndex         = iIndex ;\r
+       oListValue.selectedIndex        = iIndex ;\r
+\r
+       var oTxtText    = document.getElementById( "txtText" ) ;\r
+       var oTxtValue   = document.getElementById( "txtValue" ) ;\r
+\r
+       oTxtText.value  = oListText.value ;\r
+       oTxtValue.value = oListValue.value ;\r
+}\r
+\r
+function Add()\r
+{\r
+       var oTxtText    = document.getElementById( "txtText" ) ;\r
+       var oTxtValue   = document.getElementById( "txtValue" ) ;\r
+\r
+       AddComboOption( oListText, oTxtText.value, oTxtText.value ) ;\r
+       AddComboOption( oListValue, oTxtValue.value, oTxtValue.value ) ;\r
+\r
+       oListText.selectedIndex = oListText.options.length - 1 ;\r
+       oListValue.selectedIndex = oListValue.options.length - 1 ;\r
+\r
+       oTxtText.value  = '' ;\r
+       oTxtValue.value = '' ;\r
+\r
+       oTxtText.focus() ;\r
+}\r
+\r
+function Modify()\r
+{\r
+       var iIndex = oListText.selectedIndex ;\r
+\r
+       if ( iIndex < 0 ) return ;\r
+\r
+       var oTxtText    = document.getElementById( "txtText" ) ;\r
+       var oTxtValue   = document.getElementById( "txtValue" ) ;\r
+\r
+       oListText.options[ iIndex ].innerHTML   = HTMLEncode( oTxtText.value ) ;\r
+       oListText.options[ iIndex ].value               = oTxtText.value ;\r
+\r
+       oListValue.options[ iIndex ].innerHTML  = HTMLEncode( oTxtValue.value ) ;\r
+       oListValue.options[ iIndex ].value              = oTxtValue.value ;\r
+\r
+       oTxtText.value  = '' ;\r
+       oTxtValue.value = '' ;\r
+\r
+       oTxtText.focus() ;\r
+}\r
+\r
+function Move( steps )\r
+{\r
+       ChangeOptionPosition( oListText, steps ) ;\r
+       ChangeOptionPosition( oListValue, steps ) ;\r
+}\r
+\r
+function Delete()\r
+{\r
+       RemoveSelectedOptions( oListText ) ;\r
+       RemoveSelectedOptions( oListValue ) ;\r
+}\r
+\r
+function SetSelectedValue()\r
+{\r
+       var iIndex = oListValue.selectedIndex ;\r
+       if ( iIndex < 0 ) return ;\r
+\r
+       var oTxtValue = document.getElementById( "txtSelValue" ) ;\r
+\r
+       oTxtValue.value = oListValue.options[ iIndex ].value ;\r
+}\r
+\r
+// Moves the selected option by a number of steps (also negative)\r
+function ChangeOptionPosition( combo, steps )\r
+{\r
+       var iActualIndex = combo.selectedIndex ;\r
+\r
+       if ( iActualIndex < 0 )\r
+               return ;\r
+\r
+       var iFinalIndex = iActualIndex + steps ;\r
+\r
+       if ( iFinalIndex < 0 )\r
+               iFinalIndex = 0 ;\r
+\r
+       if ( iFinalIndex > ( combo.options.length - 1 ) )\r
+               iFinalIndex = combo.options.length - 1 ;\r
+\r
+       if ( iActualIndex == iFinalIndex )\r
+               return ;\r
+\r
+       var oOption = combo.options[ iActualIndex ] ;\r
+       var sText       = HTMLDecode( oOption.innerHTML ) ;\r
+       var sValue      = oOption.value ;\r
+\r
+       combo.remove( iActualIndex ) ;\r
+\r
+       oOption = AddComboOption( combo, sText, sValue, null, iFinalIndex ) ;\r
+\r
+       oOption.selected = true ;\r
+}\r
+\r
+// Remove all selected options from a SELECT object\r
+function RemoveSelectedOptions(combo)\r
+{\r
+       // Save the selected index\r
+       var iSelectedIndex = combo.selectedIndex ;\r
+\r
+       var oOptions = combo.options ;\r
+\r
+       // Remove all selected options\r
+       for ( var i = oOptions.length - 1 ; i >= 0 ; i-- )\r
+       {\r
+               if (oOptions[i].selected) combo.remove(i) ;\r
+       }\r
+\r
+       // Reset the selection based on the original selected index\r
+       if ( combo.options.length > 0 )\r
+       {\r
+               if ( iSelectedIndex >= combo.options.length ) iSelectedIndex = combo.options.length - 1 ;\r
+               combo.selectedIndex = iSelectedIndex ;\r
+       }\r
+}\r
+\r
+// Add a new option to a SELECT object (combo or list)\r
+function AddComboOption( combo, optionText, optionValue, documentObject, index )\r
+{\r
+       var oOption ;\r
+\r
+       if ( documentObject )\r
+               oOption = documentObject.createElement("OPTION") ;\r
+       else\r
+               oOption = document.createElement("OPTION") ;\r
+\r
+       if ( index != null )\r
+               combo.options.add( oOption, index ) ;\r
+       else\r
+               combo.options.add( oOption ) ;\r
+\r
+       oOption.innerHTML = optionText.length > 0 ? HTMLEncode( optionText ) : '&nbsp;' ;\r
+       oOption.value     = optionValue ;\r
+\r
+       return oOption ;\r
+}\r
+\r
+function HTMLEncode( text )\r
+{\r
+       if ( !text )\r
+               return '' ;\r
+\r
+       text = text.replace( /&/g, '&amp;' ) ;\r
+       text = text.replace( /</g, '&lt;' ) ;\r
+       text = text.replace( />/g, '&gt;' ) ;\r
+\r
+       return text ;\r
+}\r
+\r
+\r
+function HTMLDecode( text )\r
+{\r
+       if ( !text )\r
+               return '' ;\r
+\r
+       text = text.replace( /&gt;/g, '>' ) ;\r
+       text = text.replace( /&lt;/g, '<' ) ;\r
+       text = text.replace( /&amp;/g, '&' ) ;\r
+\r
+       return text ;\r
+}
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_smiley.html b/httemplate/elements/fckeditor/editor/dialog/fck_smiley.html
new file mode 100644 (file)
index 0000000..c8efd0c
--- /dev/null
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Smileys (emoticons) dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <style type="text/css">\r
+               .Hand\r
+               {\r
+                       cursor: pointer;\r
+                       cursor: hand;\r
+               }\r
+       </style>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+window.onload = function ()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+}\r
+\r
+function InsertSmiley( url )\r
+{\r
+       var oImg = oEditor.FCK.CreateElement( 'IMG' ) ;\r
+       oImg.src = url ;\r
+       oImg.setAttribute( '_fcksavedurl', url ) ;\r
+\r
+       // For long smileys list, it seams that IE continues loading the images in\r
+       // the background when you quickly select one image. so, let's clear\r
+       // everything before closing.\r
+       document.body.innerHTML = '' ;\r
+\r
+       window.parent.Cancel() ;\r
+}\r
+\r
+function over(td)\r
+{\r
+       td.className = 'LightBackground Hand' ;\r
+}\r
+\r
+function out(td)\r
+{\r
+       td.className = 'DarkBackground Hand' ;\r
+}\r
+       </script>\r
+</head>\r
+<body scroll="no">\r
+       <table cellpadding="2" cellspacing="2" align="center" border="0" width="100%" height="100%">\r
+               <script type="text/javascript">\r
+\r
+var FCKConfig = oEditor.FCKConfig ;\r
+\r
+var sBasePath  = FCKConfig.SmileyPath ;\r
+var aImages            = FCKConfig.SmileyImages ;\r
+var iCols              = FCKConfig.SmileyColumns ;\r
+var iColWidth  = parseInt( 100 / iCols, 10 ) ;\r
+\r
+var i = 0 ;\r
+while (i < aImages.length)\r
+{\r
+       document.write( '<tr>' ) ;\r
+       for(var j = 0 ; j < iCols ; j++)\r
+       {\r
+               if (aImages[i])\r
+               {\r
+                       var sUrl = sBasePath + aImages[i] ;\r
+                       document.write( '<td width="' + iColWidth + '%" align="center" class="DarkBackground Hand" onclick="InsertSmiley(\'' + sUrl.replace(/'/g, "\\'" ) + '\')" onmouseover="over(this)" onmouseout="out(this)">' ) ;\r
+                       document.write( '<img src="' + sUrl + '" border="0" />' ) ;\r
+               }\r
+               else\r
+                       document.write( '<td width="' + iColWidth + '%" class="DarkBackground">&nbsp;' ) ;\r
+               document.write( '<\/td>' ) ;\r
+               i++ ;\r
+       }\r
+       document.write('<\/tr>') ;\r
+}\r
+\r
+               </script>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_source.html b/httemplate/elements/fckeditor/editor/dialog/fck_source.html
new file mode 100644 (file)
index 0000000..aba9b39
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Source editor dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Source</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta name="robots" content="noindex, nofollow">\r
+               <link href="common/fck_dialog_common.css" rel="stylesheet" type="text/css" />\r
+               <script language="javascript">\r
+\r
+var oEditor            = window.parent.InnerDialogLoaded() ;\r
+var FCK                        = oEditor.FCK ;\r
+var FCKConfig  = oEditor.FCKConfig ;\r
+\r
+window.onload = function()\r
+{\r
+       // EnableXHTML and EnableSourceXHTML has been deprecated\r
+//     document.getElementById('txtSource').value = ( FCKConfig.EnableXHTML && FCKConfig.EnableSourceXHTML ? FCK.GetXHTML( FCKConfig.FormatSource ) : FCK.GetHTML( FCKConfig.FormatSource ) ) ;\r
+       document.getElementById('txtSource').value = FCK.GetXHTML( FCKConfig.FormatSource ) ;\r
+\r
+       // Activate the "OK" button.\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+//#### The OK button was hit.\r
+function Ok()\r
+{\r
+       if ( oEditor.FCKBrowserInfo.IsIE )\r
+               oEditor.FCKUndo.SaveUndoStep() ;\r
+\r
+       FCK.SetHTML( document.getElementById('txtSource').value, false ) ;\r
+\r
+       return true ;\r
+}\r
+               </script>\r
+       </head>\r
+       <body scroll="no" style="OVERFLOW: hidden">\r
+               <table width="100%" height="100%">\r
+                       <tr>\r
+                               <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>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_specialchar.html b/httemplate/elements/fckeditor/editor/dialog/fck_specialchar.html
new file mode 100644 (file)
index 0000000..e6d0a5a
--- /dev/null
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Special Chars Selector dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <meta name="robots" content="noindex, nofollow">\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <style type="text/css">\r
+                               .Hand\r
+                               {\r
+                                       cursor: pointer ;\r
+                                       cursor: hand ;\r
+                               }\r
+                               .Sample { font-size: 24px; }\r
+               </style>\r
+               <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+var oSample ;\r
+\r
+function insertChar(charValue)\r
+{\r
+       oEditor.FCK.InsertHtml( charValue || "" ) ;\r
+       window.parent.Cancel() ;\r
+}\r
+\r
+function over(td)\r
+{\r
+       oSample.innerHTML = td.innerHTML ;\r
+       td.className = 'LightBackground SpecialCharsOver Hand' ;\r
+}\r
+\r
+function out(td)\r
+{\r
+       oSample.innerHTML = "&nbsp;" ;\r
+       td.className = 'DarkBackground SpecialCharsOut Hand' ;\r
+}\r
+\r
+function setDefaults()\r
+{\r
+       // Gets the sample placeholder.\r
+       oSample = document.getElementById("SampleTD") ;\r
+\r
+       // First of all, translates the dialog box texts.\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+}\r
+\r
+               </script>\r
+       </HEAD>\r
+       <BODY onload="setDefaults()" scroll="no">\r
+               <table cellpadding="0" cellspacing="0" width="100%" height="100%">\r
+                       <tr>\r
+                               <td width="100%">\r
+                                       <table cellpadding="1" cellspacing="1" align="center" border="0" width="100%" height="100%">\r
+                                               <script type="text/javascript">\r
+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;","&sbquo;","&#8219;","&bdquo;","&hellip;","&trade;","&#9658;","&bull;","&rarr;","&rArr;","&hArr;","&diams;","&asymp;"] ;\r
+\r
+var cols = 20 ;\r
+\r
+var i = 0 ;\r
+while (i < aChars.length)\r
+{\r
+       document.write("<TR>") ;\r
+       for(var j = 0 ; j < cols ; j++)\r
+       {\r
+               if (aChars[i])\r
+               {\r
+                       document.write('<TD width="1%" class="DarkBackground SpecialCharsOut Hand" align="center" onclick="insertChar(\'' + aChars[i].replace(/&/g, "&amp;") + '\')" onmouseover="over(this)" onmouseout="out(this)">') ;\r
+                       document.write(aChars[i]) ;\r
+               }\r
+               else\r
+                       document.write("<TD class='DarkBackground SpecialCharsOut'>&nbsp;") ;\r
+               document.write("<\/TD>") ;\r
+               i++ ;\r
+       }\r
+       document.write("<\/TR>") ;\r
+}\r
+                                               </script>\r
+                                       </table>\r
+                               </td>\r
+                               <td nowrap>&nbsp;&nbsp;&nbsp;&nbsp;</td>\r
+                               <td valign="top">\r
+                                       <table width="40" cellpadding="0" cellspacing="0" border="0">\r
+                                               <tr>\r
+                                                       <td id="SampleTD" width="40" height="40" align="center" class="DarkBackground SpecialCharsOut Sample">&nbsp;</td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </BODY>\r
+</HTML>
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages.html b/httemplate/elements/fckeditor/editor/dialog/fck_spellerpages.html
new file mode 100644 (file)
index 0000000..66596e1
--- /dev/null
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Spell Check dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Spell Check</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta content="noindex, nofollow" name="robots">\r
+               <script src="fck_spellerpages/spellerpages/spellChecker.js"></script>\r
+               <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+var FCKLang = oEditor.FCKLang ;\r
+\r
+window.onload = function()\r
+{\r
+       document.getElementById('txtHtml').value = oEditor.FCK.EditorDocument.body.innerHTML ;\r
+\r
+       var oSpeller = new spellChecker( document.getElementById('txtHtml') ) ;\r
+       oSpeller.spellCheckScript = oEditor.FCKConfig.SpellerPagesServerScript || 'server-scripts/spellchecker.php' ;\r
+       oSpeller.OnFinished = oSpeller_OnFinished ;\r
+       oSpeller.openChecker() ;\r
+}\r
+\r
+function OnSpellerControlsLoad( controlsWindow )\r
+{\r
+       // Translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage( controlsWindow.document ) ;\r
+}\r
+\r
+function oSpeller_OnFinished( numberOCorrections )\r
+{\r
+       if ( numberOCorrections > 0 )\r
+               oEditor.FCK.SetHTML( document.getElementById('txtHtml').value ) ;\r
+       window.parent.Cancel() ;\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body style="OVERFLOW: hidden" scroll="no" style="padding:0px;">\r
+               <input type="hidden" id="txtHtml" value="">\r
+               <iframe id="frmSpell" src="javascript:void(0)" name="spellchecker" width="100%" height="100%" frameborder="0"></iframe>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..e69de29
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 (file)
index 0000000..80af849
--- /dev/null
@@ -0,0 +1,87 @@
+////////////////////////////////////////////////////\r
+// controlWindow object\r
+////////////////////////////////////////////////////\r
+function controlWindow( controlForm ) {\r
+       // private properties\r
+       this._form = controlForm;\r
+\r
+       // public properties\r
+       this.windowType = "controlWindow";\r
+//     this.noSuggestionSelection = "- No suggestions -";      // by FredCK\r
+       this.noSuggestionSelection = FCKLang.DlgSpellNoSuggestions ;\r
+       // set up the properties for elements of the given control form\r
+       this.suggestionList  = this._form.sugg;\r
+       this.evaluatedText   = this._form.misword;\r
+       this.replacementText = this._form.txtsugg;\r
+       this.undoButton      = this._form.btnUndo;\r
+\r
+       // public methods\r
+       this.addSuggestion = addSuggestion;\r
+       this.clearSuggestions = clearSuggestions;\r
+       this.selectDefaultSuggestion = selectDefaultSuggestion;\r
+       this.resetForm = resetForm;\r
+       this.setSuggestedText = setSuggestedText;\r
+       this.enableUndo = enableUndo;\r
+       this.disableUndo = disableUndo;\r
+}\r
+\r
+function resetForm() {\r
+       if( this._form ) {\r
+               this._form.reset();\r
+       }\r
+}\r
+\r
+function setSuggestedText() {\r
+       var slct = this.suggestionList;\r
+       var txt = this.replacementText;\r
+       var str = "";\r
+       if( (slct.options[0].text) && slct.options[0].text != this.noSuggestionSelection ) {\r
+               str = slct.options[slct.selectedIndex].text;\r
+       }\r
+       txt.value = str;\r
+}\r
+\r
+function selectDefaultSuggestion() {\r
+       var slct = this.suggestionList;\r
+       var txt = this.replacementText;\r
+       if( slct.options.length == 0 ) {\r
+               this.addSuggestion( this.noSuggestionSelection );\r
+       } else {\r
+               slct.options[0].selected = true;\r
+       }\r
+       this.setSuggestedText();\r
+}\r
+\r
+function addSuggestion( sugg_text ) {\r
+       var slct = this.suggestionList;\r
+       if( sugg_text ) {\r
+               var i = slct.options.length;\r
+               var newOption = new Option( sugg_text, 'sugg_text'+i );\r
+               slct.options[i] = newOption;\r
+        }\r
+}\r
+\r
+function clearSuggestions() {\r
+       var slct = this.suggestionList;\r
+       for( var j = slct.length - 1; j > -1; j-- ) {\r
+               if( slct.options[j] ) {\r
+                       slct.options[j] = null;\r
+               }\r
+       }\r
+}\r
+\r
+function enableUndo() {\r
+       if( this.undoButton ) {\r
+               if( this.undoButton.disabled == true ) {\r
+                       this.undoButton.disabled = false;\r
+               }\r
+       }\r
+}\r
+\r
+function disableUndo() {\r
+       if( this.undoButton ) {\r
+               if( this.undoButton.disabled == false ) {\r
+                       this.undoButton.disabled = true;\r
+               }\r
+       }\r
+}\r
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 (file)
index 0000000..d91bcce
--- /dev/null
@@ -0,0 +1,153 @@
+<html>\r
+       <head>\r
+               <link rel="stylesheet" type="text/css" href="spellerStyle.css" />\r
+               <script type="text/javascript" src="controlWindow.js"></script>\r
+               <script type="text/javascript">\r
+var spellerObject;\r
+var controlWindowObj;\r
+\r
+if( parent.opener ) {\r
+       spellerObject = parent.opener.speller;\r
+}\r
+\r
+function ignore_word() {\r
+       if( spellerObject ) {\r
+               spellerObject.ignoreWord();\r
+       }\r
+}\r
+\r
+function ignore_all() {\r
+       if( spellerObject ) {\r
+               spellerObject.ignoreAll();\r
+       }\r
+}\r
+\r
+function replace_word() {\r
+       if( spellerObject ) {\r
+               spellerObject.replaceWord();\r
+       }\r
+}\r
+\r
+function replace_all() {\r
+       if( spellerObject ) {\r
+               spellerObject.replaceAll();\r
+       }\r
+}\r
+\r
+function end_spell() {\r
+       if( spellerObject ) {\r
+               spellerObject.terminateSpell();\r
+       }\r
+}\r
+\r
+function undo() {\r
+       if( spellerObject ) {\r
+               spellerObject.undo();\r
+       }\r
+}\r
+\r
+function suggText() {\r
+       if( controlWindowObj ) {\r
+               controlWindowObj.setSuggestedText();\r
+       }\r
+}\r
+\r
+var FCKLang = window.parent.parent.FCKLang ;   // by FredCK\r
+\r
+function init_spell() {\r
+       // By FredCK (fckLang attributes have been added to the HTML source of this page)\r
+       window.parent.parent.OnSpellerControlsLoad( this ) ;\r
+\r
+       var controlForm = document.spellcheck;\r
+\r
+       // create a new controlWindow object\r
+       controlWindowObj = new controlWindow( controlForm );\r
+\r
+       // call the init_spell() function in the parent frameset\r
+       if( parent.frames.length ) {\r
+               parent.init_spell( controlWindowObj );\r
+       } else {\r
+               alert( 'This page was loaded outside of a frameset. It might not display properly' );\r
+       }\r
+}\r
+\r
+</script>\r
+       </head>\r
+       <body class="controlWindowBody" onLoad="init_spell();" style="OVERFLOW: hidden" scroll="no">    <!-- by FredCK -->\r
+               <form name="spellcheck">\r
+                       <table border="0" cellpadding="0" cellspacing="0" border="0" align="center">\r
+                               <tr>\r
+                                       <td colspan="3" class="normalLabel"><span fckLang="DlgSpellNotInDic">Not in dictionary:</span></td>\r
+                               </tr>\r
+                               <tr>\r
+                                       <td colspan="3"><input class="readonlyInput" type="text" name="misword" readonly /></td>\r
+                               </tr>\r
+                               <tr>\r
+                                       <td colspan="3" height="5"></td>\r
+                               </tr>\r
+                               <tr>\r
+                                       <td class="normalLabel"><span fckLang="DlgSpellChangeTo">Change to:</span></td>\r
+                               </tr>\r
+                               <tr valign="top">\r
+                                       <td>\r
+                                               <table border="0" cellpadding="0" cellspacing="0" border="0">\r
+                                                       <tr>\r
+                                                               <td class="normalLabel">\r
+                                                                       <input class="textDefault" type="text" name="txtsugg" />\r
+                                                               </td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td>\r
+                                                                       <select class="suggSlct" name="sugg" size="7" onChange="suggText();" onDblClick="replace_word();">\r
+                                                                               <option></option>\r
+                                                                       </select>\r
+                                                               </td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                                       <td>&nbsp;&nbsp;</td>\r
+                                       <td>\r
+                                               <table border="0" cellpadding="0" cellspacing="0" border="0">\r
+                                                       <tr>\r
+                                                               <td>\r
+                                                                       <input class="buttonDefault" type="button" fckLang="DlgSpellBtnIgnore" value="Ignore" onClick="ignore_word();">\r
+                                                               </td>\r
+                                                               <td>&nbsp;&nbsp;</td>\r
+                                                               <td>\r
+                                                                       <input class="buttonDefault" type="button" fckLang="DlgSpellBtnIgnoreAll" value="Ignore All" onClick="ignore_all();">\r
+                                                               </td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td colspan="3" height="5"></td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td>\r
+                                                                       <input class="buttonDefault" type="button" fckLang="DlgSpellBtnReplace" value="Replace" onClick="replace_word();">\r
+                                                               </td>\r
+                                                               <td>&nbsp;&nbsp;</td>\r
+                                                               <td>\r
+                                                                       <input class="buttonDefault" type="button" fckLang="DlgSpellBtnReplaceAll" value="Replace All" onClick="replace_all();">\r
+                                                               </td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td colspan="3" height="5"></td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td>\r
+                                                                       <input class="buttonDefault" type="button" name="btnUndo" fckLang="DlgSpellBtnUndo" value="Undo" onClick="undo();"\r
+                                                                               disabled>\r
+                                                               </td>\r
+                                                               <td>&nbsp;&nbsp;</td>\r
+                                                               <td>\r
+                                                                       <!-- by FredCK\r
+                                                                       <input class="buttonDefault" type="button" value="Close" onClick="end_spell();">\r
+                                                                       -->\r
+                                                               </td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                               </tr>\r
+                       </table>\r
+               </form>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..8d3df65
--- /dev/null
@@ -0,0 +1,180 @@
+#!/usr/bin/perl\r
+\r
+use CGI qw/ :standard /;\r
+use File::Temp qw/ tempfile tempdir /;\r
+\r
+# my $spellercss = '/speller/spellerStyle.css';                                        # by FredCK\r
+my $spellercss = '../spellerStyle.css';                                                        # by FredCK\r
+# my $wordWindowSrc = '/speller/wordWindow.js';                                        # by FredCK\r
+my $wordWindowSrc = '../wordWindow.js';                                                        # by FredCK\r
+my @textinputs = param( 'textinputs[]' ); # array\r
+# my $aspell_cmd = 'aspell';                                                                   # by FredCK (for Linux)\r
+my $aspell_cmd = '"C:\Program Files\Aspell\bin\aspell.exe"';   # by FredCK (for Windows)\r
+my $lang = 'en_US';\r
+# my $aspell_opts = "-a --lang=$lang --encoding=utf-8";                        # by FredCK\r
+my $aspell_opts = "-a --lang=$lang --encoding=utf-8 -H --rem-sgml-check=alt";          # by FredCK\r
+my $input_separator = "A";\r
+\r
+# set the 'wordtext' JavaScript variable to the submitted text.\r
+sub printTextVar {\r
+       for( my $i = 0; $i <= $#textinputs; $i++ ) {\r
+               print "textinputs[$i] = decodeURIComponent('" . escapeQuote( $textinputs[$i] ) . "')\n";\r
+       }\r
+}\r
+\r
+sub printTextIdxDecl {\r
+       my $idx = shift;\r
+       print "words[$idx] = [];\n";\r
+       print "suggs[$idx] = [];\n";\r
+}\r
+\r
+sub printWordsElem {\r
+       my( $textIdx, $wordIdx, $word ) = @_;\r
+       print "words[$textIdx][$wordIdx] = '" . escapeQuote( $word ) . "';\n";\r
+}\r
+\r
+sub printSuggsElem {\r
+       my( $textIdx, $wordIdx, @suggs ) = @_;\r
+       print "suggs[$textIdx][$wordIdx] = [";\r
+       for my $i ( 0..$#suggs ) {\r
+               print "'" . escapeQuote( $suggs[$i] ) . "'";\r
+               if( $i < $#suggs ) {\r
+                       print ", ";\r
+               }\r
+       }\r
+       print "];\n";\r
+}\r
+\r
+sub printCheckerResults {\r
+       my $textInputIdx = -1;\r
+       my $wordIdx = 0;\r
+       my $unhandledText;\r
+       # create temp file\r
+       my $dir = tempdir( CLEANUP => 1 );\r
+       my( $fh, $tmpfilename ) = tempfile( DIR => $dir );\r
+\r
+       # temp file was created properly?\r
+\r
+       # open temp file, add the submitted text.\r
+       for( my $i = 0; $i <= $#textinputs; $i++ ) {\r
+               $text = url_decode( $textinputs[$i] );\r
+               @lines = split( /\n/, $text );\r
+               print $fh "\%\n"; # exit terse mode\r
+               print $fh "^$input_separator\n";\r
+               print $fh "!\n";  # enter terse mode\r
+               for my $line ( @lines ) {\r
+                       # use carat on each line to escape possible aspell commands\r
+                       print $fh "^$line\n";\r
+               }\r
+\r
+       }\r
+       # exec aspell command\r
+       my $cmd = "$aspell_cmd $aspell_opts < $tmpfilename 2>&1";\r
+       open ASPELL, "$cmd |" or handleError( "Could not execute `$cmd`\\n$!" ) and return;\r
+       # parse each line of aspell return\r
+       for my $ret ( <ASPELL> ) {\r
+               chomp( $ret );\r
+               # if '&', then not in dictionary but has suggestions\r
+               # if '#', then not in dictionary and no suggestions\r
+               # if '*', then it is a delimiter between text inputs\r
+               if( $ret =~ /^\*/ ) {\r
+                       $textInputIdx++;\r
+                       printTextIdxDecl( $textInputIdx );\r
+                       $wordIdx = 0;\r
+\r
+               } elsif( $ret =~ /^(&|#)/ ) {\r
+                       my @tokens = split( " ", $ret, 5 );\r
+                       printWordsElem( $textInputIdx, $wordIdx, $tokens[1] );\r
+                       my @suggs = ();\r
+                       if( $tokens[4] ) {\r
+                               @suggs = split( ", ", $tokens[4] );\r
+                       }\r
+                       printSuggsElem( $textInputIdx, $wordIdx, @suggs );\r
+                       $wordIdx++;\r
+               } else {\r
+                       $unhandledText .= $ret;\r
+               }\r
+       }\r
+       close ASPELL or handleError( "Error executing `$cmd`\\n$unhandledText" ) and return;\r
+}\r
+\r
+sub escapeQuote {\r
+       my $str = shift;\r
+       $str =~ s/'/\\'/g;\r
+       return $str;\r
+}\r
+\r
+sub handleError {\r
+       my $err = shift;\r
+       print "error = '" . escapeQuote( $err ) . "';\n";\r
+}\r
+\r
+sub url_decode {\r
+       local $_ = @_ ? shift : $_;\r
+       defined or return;\r
+       # change + signs to spaces\r
+       tr/+/ /;\r
+       # change hex escapes to the proper characters\r
+       s/%([a-fA-F0-9]{2})/pack "H2", $1/eg;\r
+       return $_;\r
+}\r
+\r
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\r
+# Display HTML\r
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\r
+\r
+print <<EOF;\r
+Content-type: text/html; charset=utf-8\r
+\r
+<html>\r
+<head>\r
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+<link rel="stylesheet" type="text/css" href="$spellercss"/>\r
+<script src="$wordWindowSrc"></script>\r
+<script type="text/javascript">\r
+var suggs = new Array();\r
+var words = new Array();\r
+var textinputs = new Array();\r
+var error;\r
+EOF\r
+\r
+printTextVar();\r
+\r
+printCheckerResults();\r
+\r
+print <<EOF;\r
+var wordWindowObj = new wordWindow();\r
+wordWindowObj.originalSpellings = words;\r
+wordWindowObj.suggestions = suggs;\r
+wordWindowObj.textInputs = textinputs;\r
+\r
+\r
+function init_spell() {\r
+       // check if any error occured during server-side processing\r
+       if( error ) {\r
+               alert( error );\r
+       } else {\r
+               // call the init_spell() function in the parent frameset\r
+               if (parent.frames.length) {\r
+                       parent.init_spell( wordWindowObj );\r
+               } else {\r
+                       error = "This page was loaded outside of a frameset. ";\r
+                       error += "It might not display properly";\r
+                       alert( error );\r
+               }\r
+       }\r
+}\r
+\r
+</script>\r
+\r
+</head>\r
+<body onLoad="init_spell();">\r
+\r
+<script type="text/javascript">\r
+wordWindowObj.writeBody();\r
+</script>\r
+\r
+</body>\r
+</html>\r
+EOF\r
+\r
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 (file)
index 0000000..b5e55b7
--- /dev/null
@@ -0,0 +1,462 @@
+////////////////////////////////////////////////////\r
+// spellChecker.js\r
+//\r
+// spellChecker object\r
+//\r
+// This file is sourced on web pages that have a textarea object to evaluate\r
+// for spelling. It includes the implementation for the spellCheckObject.\r
+//\r
+////////////////////////////////////////////////////\r
+\r
+\r
+// constructor\r
+function spellChecker( textObject ) {\r
+\r
+       // public properties - configurable\r
+//     this.popUpUrl = '/speller/spellchecker.html';                                                   // by FredCK\r
+       this.popUpUrl = 'fck_spellerpages/spellerpages/spellchecker.html';              // by FredCK\r
+       this.popUpName = 'spellchecker';\r
+//     this.popUpProps = "menu=no,width=440,height=350,top=70,left=120,resizable=yes,status=yes";      // by FredCK\r
+       this.popUpProps = null ;                                                                                                                                        // by FredCK\r
+//     this.spellCheckScript = '/speller/server-scripts/spellchecker.php';             // by FredCK\r
+       //this.spellCheckScript = '/cgi-bin/spellchecker.pl';\r
+\r
+       // values used to keep track of what happened to a word\r
+       this.replWordFlag = "R";        // single replace\r
+       this.ignrWordFlag = "I";        // single ignore\r
+       this.replAllFlag = "RA";        // replace all occurances\r
+       this.ignrAllFlag = "IA";        // ignore all occurances\r
+       this.fromReplAll = "~RA";       // an occurance of a "replace all" word\r
+       this.fromIgnrAll = "~IA";       // an occurance of a "ignore all" word\r
+       // properties set at run time\r
+       this.wordFlags = new Array();\r
+       this.currentTextIndex = 0;\r
+       this.currentWordIndex = 0;\r
+       this.spellCheckerWin = null;\r
+       this.controlWin = null;\r
+       this.wordWin = null;\r
+       this.textArea = textObject;     // deprecated\r
+       this.textInputs = arguments;\r
+\r
+       // private methods\r
+       this._spellcheck = _spellcheck;\r
+       this._getSuggestions = _getSuggestions;\r
+       this._setAsIgnored = _setAsIgnored;\r
+       this._getTotalReplaced = _getTotalReplaced;\r
+       this._setWordText = _setWordText;\r
+       this._getFormInputs = _getFormInputs;\r
+\r
+       // public methods\r
+       this.openChecker = openChecker;\r
+       this.startCheck = startCheck;\r
+       this.checkTextBoxes = checkTextBoxes;\r
+       this.checkTextAreas = checkTextAreas;\r
+       this.spellCheckAll = spellCheckAll;\r
+       this.ignoreWord = ignoreWord;\r
+       this.ignoreAll = ignoreAll;\r
+       this.replaceWord = replaceWord;\r
+       this.replaceAll = replaceAll;\r
+       this.terminateSpell = terminateSpell;\r
+       this.undo = undo;\r
+\r
+       // set the current window's "speller" property to the instance of this class.\r
+       // this object can now be referenced by child windows/frames.\r
+       window.speller = this;\r
+}\r
+\r
+// call this method to check all text boxes (and only text boxes) in the HTML document\r
+function checkTextBoxes() {\r
+       this.textInputs = this._getFormInputs( "^text$" );\r
+       this.openChecker();\r
+}\r
+\r
+// call this method to check all textareas (and only textareas ) in the HTML document\r
+function checkTextAreas() {\r
+       this.textInputs = this._getFormInputs( "^textarea$" );\r
+       this.openChecker();\r
+}\r
+\r
+// call this method to check all text boxes and textareas in the HTML document\r
+function spellCheckAll() {\r
+       this.textInputs = this._getFormInputs( "^text(area)?$" );\r
+       this.openChecker();\r
+}\r
+\r
+// call this method to check text boxe(s) and/or textarea(s) that were passed in to the\r
+// object's constructor or to the textInputs property\r
+function openChecker() {\r
+       this.spellCheckerWin = window.open( this.popUpUrl, this.popUpName, this.popUpProps );\r
+       if( !this.spellCheckerWin.opener ) {\r
+               this.spellCheckerWin.opener = window;\r
+       }\r
+}\r
+\r
+function startCheck( wordWindowObj, controlWindowObj ) {\r
+\r
+       // set properties from args\r
+       this.wordWin = wordWindowObj;\r
+       this.controlWin = controlWindowObj;\r
+\r
+       // reset properties\r
+       this.wordWin.resetForm();\r
+       this.controlWin.resetForm();\r
+       this.currentTextIndex = 0;\r
+       this.currentWordIndex = 0;\r
+       // initialize the flags to an array - one element for each text input\r
+       this.wordFlags = new Array( this.wordWin.textInputs.length );\r
+       // each element will be an array that keeps track of each word in the text\r
+       for( var i=0; i<this.wordFlags.length; i++ ) {\r
+               this.wordFlags[i] = [];\r
+       }\r
+\r
+       // start\r
+       this._spellcheck();\r
+\r
+       return true;\r
+}\r
+\r
+function ignoreWord() {\r
+       var wi = this.currentWordIndex;\r
+       var ti = this.currentTextIndex;\r
+       if( !this.wordWin ) {\r
+               alert( 'Error: Word frame not available.' );\r
+               return false;\r
+       }\r
+       if( !this.wordWin.getTextVal( ti, wi )) {\r
+               alert( 'Error: "Not in dictionary" text is missing.' );\r
+               return false;\r
+       }\r
+       // set as ignored\r
+       if( this._setAsIgnored( ti, wi, this.ignrWordFlag )) {\r
+               this.currentWordIndex++;\r
+               this._spellcheck();\r
+       }\r
+       return true;\r
+}\r
+\r
+function ignoreAll() {\r
+       var wi = this.currentWordIndex;\r
+       var ti = this.currentTextIndex;\r
+       if( !this.wordWin ) {\r
+               alert( 'Error: Word frame not available.' );\r
+               return false;\r
+       }\r
+       // get the word that is currently being evaluated.\r
+       var s_word_to_repl = this.wordWin.getTextVal( ti, wi );\r
+       if( !s_word_to_repl ) {\r
+               alert( 'Error: "Not in dictionary" text is missing' );\r
+               return false;\r
+       }\r
+\r
+       // set this word as an "ignore all" word.\r
+       this._setAsIgnored( ti, wi, this.ignrAllFlag );\r
+\r
+       // loop through all the words after this word\r
+       for( var i = ti; i < this.wordWin.textInputs.length; i++ ) {\r
+               for( var j = 0; j < this.wordWin.totalWords( i ); j++ ) {\r
+                       if(( i == ti && j > wi ) || i > ti ) {\r
+                               // future word: set as "from ignore all" if\r
+                               // 1) do not already have a flag and\r
+                               // 2) have the same value as current word\r
+                               if(( this.wordWin.getTextVal( i, j ) == s_word_to_repl )\r
+                               && ( !this.wordFlags[i][j] )) {\r
+                                       this._setAsIgnored( i, j, this.fromIgnrAll );\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       // finally, move on\r
+       this.currentWordIndex++;\r
+       this._spellcheck();\r
+       return true;\r
+}\r
+\r
+function replaceWord() {\r
+       var wi = this.currentWordIndex;\r
+       var ti = this.currentTextIndex;\r
+       if( !this.wordWin ) {\r
+               alert( 'Error: Word frame not available.' );\r
+               return false;\r
+       }\r
+       if( !this.wordWin.getTextVal( ti, wi )) {\r
+               alert( 'Error: "Not in dictionary" text is missing' );\r
+               return false;\r
+       }\r
+       if( !this.controlWin.replacementText ) {\r
+               return false ;\r
+       }\r
+       var txt = this.controlWin.replacementText;\r
+       if( txt.value ) {\r
+               var newspell = new String( txt.value );\r
+               if( this._setWordText( ti, wi, newspell, this.replWordFlag )) {\r
+                       this.currentWordIndex++;\r
+                       this._spellcheck();\r
+               }\r
+       }\r
+       return true;\r
+}\r
+\r
+function replaceAll() {\r
+       var ti = this.currentTextIndex;\r
+       var wi = this.currentWordIndex;\r
+       if( !this.wordWin ) {\r
+               alert( 'Error: Word frame not available.' );\r
+               return false;\r
+       }\r
+       var s_word_to_repl = this.wordWin.getTextVal( ti, wi );\r
+       if( !s_word_to_repl ) {\r
+               alert( 'Error: "Not in dictionary" text is missing' );\r
+               return false;\r
+       }\r
+       var txt = this.controlWin.replacementText;\r
+       if( !txt.value ) return false;\r
+       var newspell = new String( txt.value );\r
+\r
+       // set this word as a "replace all" word.\r
+       this._setWordText( ti, wi, newspell, this.replAllFlag );\r
+\r
+       // loop through all the words after this word\r
+       for( var i = ti; i < this.wordWin.textInputs.length; i++ ) {\r
+               for( var j = 0; j < this.wordWin.totalWords( i ); j++ ) {\r
+                       if(( i == ti && j > wi ) || i > ti ) {\r
+                               // future word: set word text to s_word_to_repl if\r
+                               // 1) do not already have a flag and\r
+                               // 2) have the same value as s_word_to_repl\r
+                               if(( this.wordWin.getTextVal( i, j ) == s_word_to_repl )\r
+                               && ( !this.wordFlags[i][j] )) {\r
+                                       this._setWordText( i, j, newspell, this.fromReplAll );\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       // finally, move on\r
+       this.currentWordIndex++;\r
+       this._spellcheck();\r
+       return true;\r
+}\r
+\r
+function terminateSpell() {\r
+       // called when we have reached the end of the spell checking.\r
+       var msg = "";           // by FredCK\r
+       var numrepl = this._getTotalReplaced();\r
+       if( numrepl == 0 ) {\r
+               // see if there were no misspellings to begin with\r
+               if( !this.wordWin ) {\r
+                       msg = "";\r
+               } else {\r
+                       if( this.wordWin.totalMisspellings() ) {\r
+//                             msg += "No words changed.";                     // by FredCK\r
+                               msg += FCKLang.DlgSpellNoChanges ;      // by FredCK\r
+                       } else {\r
+//                             msg += "No misspellings found.";        // by FredCK\r
+                               msg += FCKLang.DlgSpellNoMispell ;      // by FredCK\r
+                       }\r
+               }\r
+       } else if( numrepl == 1 ) {\r
+//             msg += "One word changed.";                     // by FredCK\r
+               msg += FCKLang.DlgSpellOneChange ;      // by FredCK\r
+       } else {\r
+//             msg += numrepl + " words changed.";     // by FredCK\r
+               msg += FCKLang.DlgSpellManyChanges.replace( /%1/g, numrepl ) ;\r
+       }\r
+       if( msg ) {\r
+//             msg += "\n";    // by FredCK\r
+               alert( msg );\r
+       }\r
+\r
+       if( numrepl > 0 ) {\r
+               // update the text field(s) on the opener window\r
+               for( var i = 0; i < this.textInputs.length; i++ ) {\r
+                       // this.textArea.value = this.wordWin.text;\r
+                       if( this.wordWin ) {\r
+                               if( this.wordWin.textInputs[i] ) {\r
+                                       this.textInputs[i].value = this.wordWin.textInputs[i];\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       // return back to the calling window\r
+//     this.spellCheckerWin.close();                                   // by FredCK\r
+       if ( typeof( this.OnFinished ) == 'function' )  // by FredCK\r
+               this.OnFinished(numrepl) ;                                      // by FredCK\r
+\r
+       return true;\r
+}\r
+\r
+function undo() {\r
+       // skip if this is the first word!\r
+       var ti = this.currentTextIndex;\r
+       var wi = this.currentWordIndex;\r
+\r
+       if( this.wordWin.totalPreviousWords( ti, wi ) > 0 ) {\r
+               this.wordWin.removeFocus( ti, wi );\r
+\r
+               // go back to the last word index that was acted upon\r
+               do {\r
+                       // if the current word index is zero then reset the seed\r
+                       if( this.currentWordIndex == 0 && this.currentTextIndex > 0 ) {\r
+                               this.currentTextIndex--;\r
+                               this.currentWordIndex = this.wordWin.totalWords( this.currentTextIndex )-1;\r
+                               if( this.currentWordIndex < 0 ) this.currentWordIndex = 0;\r
+                       } else {\r
+                               if( this.currentWordIndex > 0 ) {\r
+                                       this.currentWordIndex--;\r
+                               }\r
+                       }\r
+               } while (\r
+                       this.wordWin.totalWords( this.currentTextIndex ) == 0\r
+                       || this.wordFlags[this.currentTextIndex][this.currentWordIndex] == this.fromIgnrAll\r
+                       || this.wordFlags[this.currentTextIndex][this.currentWordIndex] == this.fromReplAll\r
+               );\r
+\r
+               var text_idx = this.currentTextIndex;\r
+               var idx = this.currentWordIndex;\r
+               var preReplSpell = this.wordWin.originalSpellings[text_idx][idx];\r
+\r
+               // if we got back to the first word then set the Undo button back to disabled\r
+               if( this.wordWin.totalPreviousWords( text_idx, idx ) == 0 ) {\r
+                       this.controlWin.disableUndo();\r
+               }\r
+\r
+               var i, j, origSpell ;\r
+               // examine what happened to this current word.\r
+               switch( this.wordFlags[text_idx][idx] ) {\r
+                       // replace all: go through this and all the future occurances of the word\r
+                       // and revert them all to the original spelling and clear their flags\r
+                       case this.replAllFlag :\r
+                               for( i = text_idx; i < this.wordWin.textInputs.length; i++ ) {\r
+                                       for( j = 0; j < this.wordWin.totalWords( i ); j++ ) {\r
+                                               if(( i == text_idx && j >= idx ) || i > text_idx ) {\r
+                                                       origSpell = this.wordWin.originalSpellings[i][j];\r
+                                                       if( origSpell == preReplSpell ) {\r
+                                                               this._setWordText ( i, j, origSpell, undefined );\r
+                                                       }\r
+                                               }\r
+                                       }\r
+                               }\r
+                               break;\r
+\r
+                       // ignore all: go through all the future occurances of the word\r
+                       // and clear their flags\r
+                       case this.ignrAllFlag :\r
+                               for( i = text_idx; i < this.wordWin.textInputs.length; i++ ) {\r
+                                       for( j = 0; j < this.wordWin.totalWords( i ); j++ ) {\r
+                                               if(( i == text_idx && j >= idx ) || i > text_idx ) {\r
+                                                       origSpell = this.wordWin.originalSpellings[i][j];\r
+                                                       if( origSpell == preReplSpell ) {\r
+                                                               this.wordFlags[i][j] = undefined;\r
+                                                       }\r
+                                               }\r
+                                       }\r
+                               }\r
+                               break;\r
+\r
+                       // replace: revert the word to its original spelling\r
+                       case this.replWordFlag :\r
+                               this._setWordText ( text_idx, idx, preReplSpell, undefined );\r
+                               break;\r
+               }\r
+\r
+               // For all four cases, clear the wordFlag of this word. re-start the process\r
+               this.wordFlags[text_idx][idx] = undefined;\r
+               this._spellcheck();\r
+       }\r
+}\r
+\r
+function _spellcheck() {\r
+       var ww = this.wordWin;\r
+\r
+       // check if this is the last word in the current text element\r
+       if( this.currentWordIndex == ww.totalWords( this.currentTextIndex) ) {\r
+               this.currentTextIndex++;\r
+               this.currentWordIndex = 0;\r
+               // keep going if we're not yet past the last text element\r
+               if( this.currentTextIndex < this.wordWin.textInputs.length ) {\r
+                       this._spellcheck();\r
+                       return;\r
+               } else {\r
+                       this.terminateSpell();\r
+                       return;\r
+               }\r
+       }\r
+\r
+       // if this is after the first one make sure the Undo button is enabled\r
+       if( this.currentWordIndex > 0 ) {\r
+               this.controlWin.enableUndo();\r
+       }\r
+\r
+       // skip the current word if it has already been worked on\r
+       if( this.wordFlags[this.currentTextIndex][this.currentWordIndex] ) {\r
+               // increment the global current word index and move on.\r
+               this.currentWordIndex++;\r
+               this._spellcheck();\r
+       } else {\r
+               var evalText = ww.getTextVal( this.currentTextIndex, this.currentWordIndex );\r
+               if( evalText ) {\r
+                       this.controlWin.evaluatedText.value = evalText;\r
+                       ww.setFocus( this.currentTextIndex, this.currentWordIndex );\r
+                       this._getSuggestions( this.currentTextIndex, this.currentWordIndex );\r
+               }\r
+       }\r
+}\r
+\r
+function _getSuggestions( text_num, word_num ) {\r
+       this.controlWin.clearSuggestions();\r
+       // add suggestion in list for each suggested word.\r
+       // get the array of suggested words out of the\r
+       // three-dimensional array containing all suggestions.\r
+       var a_suggests = this.wordWin.suggestions[text_num][word_num];\r
+       if( a_suggests ) {\r
+               // got an array of suggestions.\r
+               for( var ii = 0; ii < a_suggests.length; ii++ ) {\r
+                       this.controlWin.addSuggestion( a_suggests[ii] );\r
+               }\r
+       }\r
+       this.controlWin.selectDefaultSuggestion();\r
+}\r
+\r
+function _setAsIgnored( text_num, word_num, flag ) {\r
+       // set the UI\r
+       this.wordWin.removeFocus( text_num, word_num );\r
+       // do the bookkeeping\r
+       this.wordFlags[text_num][word_num] = flag;\r
+       return true;\r
+}\r
+\r
+function _getTotalReplaced() {\r
+       var i_replaced = 0;\r
+       for( var i = 0; i < this.wordFlags.length; i++ ) {\r
+               for( var j = 0; j < this.wordFlags[i].length; j++ ) {\r
+                       if(( this.wordFlags[i][j] == this.replWordFlag )\r
+                       || ( this.wordFlags[i][j] == this.replAllFlag )\r
+                       || ( this.wordFlags[i][j] == this.fromReplAll )) {\r
+                               i_replaced++;\r
+                       }\r
+               }\r
+       }\r
+       return i_replaced;\r
+}\r
+\r
+function _setWordText( text_num, word_num, newText, flag ) {\r
+       // set the UI and form inputs\r
+       this.wordWin.setText( text_num, word_num, newText );\r
+       // keep track of what happened to this word:\r
+       this.wordFlags[text_num][word_num] = flag;\r
+       return true;\r
+}\r
+\r
+function _getFormInputs( inputPattern ) {\r
+       var inputs = new Array();\r
+       for( var i = 0; i < document.forms.length; i++ ) {\r
+               for( var j = 0; j < document.forms[i].elements.length; j++ ) {\r
+                       if( document.forms[i].elements[j].type.match( inputPattern )) {\r
+                               inputs[inputs.length] = document.forms[i].elements[j];\r
+                       }\r
+               }\r
+       }\r
+       return inputs;\r
+}\r
+\r
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 (file)
index 0000000..cbcd7db
--- /dev/null
@@ -0,0 +1,71 @@
+\r
+<script>\r
+\r
+var wordWindow = null;\r
+var controlWindow = null;\r
+\r
+function init_spell( spellerWindow ) {\r
+\r
+       if( spellerWindow ) {\r
+               if( spellerWindow.windowType == "wordWindow" ) {\r
+                       wordWindow = spellerWindow;\r
+               } else if ( spellerWindow.windowType == "controlWindow" ) {\r
+                       controlWindow = spellerWindow;\r
+               }\r
+       }\r
+\r
+       if( controlWindow && wordWindow ) {\r
+               // populate the speller object and start it off!\r
+               var speller = opener.speller;\r
+               wordWindow.speller = speller;\r
+               speller.startCheck( wordWindow, controlWindow );\r
+       }\r
+}\r
+\r
+// encodeForPost\r
+function encodeForPost( str ) {\r
+       var s = new String( str );\r
+       s = encodeURIComponent( s );\r
+       // additionally encode single quotes to evade any PHP\r
+       // magic_quotes_gpc setting (it inserts escape characters and\r
+       // therefore skews the btye positions of misspelled words)\r
+       return s.replace( /\'/g, '%27' );\r
+}\r
+\r
+// post the text area data to the script that populates the speller\r
+function postWords() {\r
+       var bodyDoc = window.frames[0].document;\r
+       bodyDoc.open();\r
+       bodyDoc.write('<html>');\r
+       bodyDoc.write('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">');\r
+       bodyDoc.write('<link rel="stylesheet" type="text/css" href="spellerStyle.css"/>');\r
+       if (opener) {\r
+               var speller = opener.speller;\r
+               bodyDoc.write('<body class="normalText" onLoad="document.forms[0].submit();">');\r
+               bodyDoc.write('<p>' + window.parent.FCKLang.DlgSpellProgress + '<\/p>');                // by FredCK\r
+               bodyDoc.write('<form action="'+speller.spellCheckScript+'" method="post">');\r
+               for( var i = 0; i < speller.textInputs.length; i++ ) {\r
+                       bodyDoc.write('<input type="hidden" name="textinputs[]" value="'+encodeForPost(speller.textInputs[i].value)+'">');\r
+               }\r
+               bodyDoc.write('<\/form>');\r
+               bodyDoc.write('<\/body>');\r
+       } else {\r
+               bodyDoc.write('<body class="normalText">');\r
+               bodyDoc.write('<p><b>This page cannot be displayed<\/b><\/p><p>The window was not opened from another window.<\/p>');\r
+               bodyDoc.write('<\/body>');\r
+       }\r
+       bodyDoc.write('<\/html>');\r
+       bodyDoc.close();\r
+}\r
+</script>\r
+\r
+<html>\r
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+<head>\r
+<title>Speller Pages</title>\r
+</head>\r
+<frameset rows="*,201" onLoad="postWords();">\r
+<frame src="blank.html">\r
+<frame src="controls.html">\r
+</frameset>\r
+</html>\r
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 (file)
index 0000000..4df608d
--- /dev/null
@@ -0,0 +1,49 @@
+.blend {\r
+       font-family: courier new;\r
+       font-size: 10pt;\r
+       border: 0;\r
+       margin-bottom:-1;\r
+}\r
+.normalLabel {\r
+       font-size:8pt;\r
+}\r
+.normalText {\r
+       font-family:arial, helvetica, sans-serif;\r
+       font-size:10pt;\r
+       color:000000;\r
+       background-color:FFFFFF;\r
+}\r
+.plainText {\r
+       font-family: courier new, courier, monospace;\r
+       font-size: 10pt;\r
+       color:000000;\r
+       background-color:FFFFFF;\r
+}\r
+.controlWindowBody {\r
+       font-family:arial, helvetica, sans-serif;\r
+       font-size:8pt;\r
+       padding: 7px ;          /* by FredCK */\r
+       margin: 0px ;           /* by FredCK */\r
+       /* color:000000;                                by FredCK */\r
+       /* background-color:DADADA;             by FredCK */\r
+}\r
+.readonlyInput {\r
+       background-color:DADADA;\r
+       color:000000;\r
+       font-size:8pt;\r
+       width:392px;\r
+}\r
+.textDefault {\r
+       font-size:8pt;\r
+       width: 200px;\r
+}\r
+.buttonDefault {\r
+       width:90px;\r
+       height:22px;\r
+       font-size:8pt;\r
+}\r
+.suggSlct {\r
+       width:200px;\r
+       margin-top:2;\r
+       font-size:8pt;\r
+}
\ No newline at end of file
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 (file)
index 0000000..7990296
--- /dev/null
@@ -0,0 +1,272 @@
+////////////////////////////////////////////////////\r
+// wordWindow object\r
+////////////////////////////////////////////////////\r
+function wordWindow() {\r
+       // private properties\r
+       this._forms = [];\r
+\r
+       // private methods\r
+       this._getWordObject = _getWordObject;\r
+       //this._getSpellerObject = _getSpellerObject;\r
+       this._wordInputStr = _wordInputStr;\r
+       this._adjustIndexes = _adjustIndexes;\r
+       this._isWordChar = _isWordChar;\r
+       this._lastPos = _lastPos;\r
+\r
+       // public properties\r
+       this.wordChar = /[a-zA-Z]/;\r
+       this.windowType = "wordWindow";\r
+       this.originalSpellings = new Array();\r
+       this.suggestions = new Array();\r
+       this.checkWordBgColor = "pink";\r
+       this.normWordBgColor = "white";\r
+       this.text = "";\r
+       this.textInputs = new Array();\r
+       this.indexes = new Array();\r
+       //this.speller = this._getSpellerObject();\r
+\r
+       // public methods\r
+       this.resetForm = resetForm;\r
+       this.totalMisspellings = totalMisspellings;\r
+       this.totalWords = totalWords;\r
+       this.totalPreviousWords = totalPreviousWords;\r
+       //this.getTextObjectArray = getTextObjectArray;\r
+       this.getTextVal = getTextVal;\r
+       this.setFocus = setFocus;\r
+       this.removeFocus = removeFocus;\r
+       this.setText = setText;\r
+       //this.getTotalWords = getTotalWords;\r
+       this.writeBody = writeBody;\r
+       this.printForHtml = printForHtml;\r
+}\r
+\r
+function resetForm() {\r
+       if( this._forms ) {\r
+               for( var i = 0; i < this._forms.length; i++ ) {\r
+                       this._forms[i].reset();\r
+               }\r
+       }\r
+       return true;\r
+}\r
+\r
+function totalMisspellings() {\r
+       var total_words = 0;\r
+       for( var i = 0; i < this.textInputs.length; i++ ) {\r
+               total_words += this.totalWords( i );\r
+       }\r
+       return total_words;\r
+}\r
+\r
+function totalWords( textIndex ) {\r
+       return this.originalSpellings[textIndex].length;\r
+}\r
+\r
+function totalPreviousWords( textIndex, wordIndex ) {\r
+       var total_words = 0;\r
+       for( var i = 0; i <= textIndex; i++ ) {\r
+               for( var j = 0; j < this.totalWords( i ); j++ ) {\r
+                       if( i == textIndex && j == wordIndex ) {\r
+                               break;\r
+                       } else {\r
+                               total_words++;\r
+                       }\r
+               }\r
+       }\r
+       return total_words;\r
+}\r
+\r
+//function getTextObjectArray() {\r
+//     return this._form.elements;\r
+//}\r
+\r
+function getTextVal( textIndex, wordIndex ) {\r
+       var word = this._getWordObject( textIndex, wordIndex );\r
+       if( word ) {\r
+               return word.value;\r
+       }\r
+}\r
+\r
+function setFocus( textIndex, wordIndex ) {\r
+       var word = this._getWordObject( textIndex, wordIndex );\r
+       if( word ) {\r
+               if( word.type == "text" ) {\r
+                       word.focus();\r
+                       word.style.backgroundColor = this.checkWordBgColor;\r
+               }\r
+       }\r
+}\r
+\r
+function removeFocus( textIndex, wordIndex ) {\r
+       var word = this._getWordObject( textIndex, wordIndex );\r
+       if( word ) {\r
+               if( word.type == "text" ) {\r
+                       word.blur();\r
+                       word.style.backgroundColor = this.normWordBgColor;\r
+               }\r
+       }\r
+}\r
+\r
+function setText( textIndex, wordIndex, newText ) {\r
+       var word = this._getWordObject( textIndex, wordIndex );\r
+       var beginStr;\r
+       var endStr;\r
+       if( word ) {\r
+               var pos = this.indexes[textIndex][wordIndex];\r
+               var oldText = word.value;\r
+               // update the text given the index of the string\r
+               beginStr = this.textInputs[textIndex].substring( 0, pos );\r
+               endStr = this.textInputs[textIndex].substring(\r
+                       pos + oldText.length,\r
+                       this.textInputs[textIndex].length\r
+               );\r
+               this.textInputs[textIndex] = beginStr + newText + endStr;\r
+\r
+               // adjust the indexes on the stack given the differences in\r
+               // length between the new word and old word.\r
+               var lengthDiff = newText.length - oldText.length;\r
+               this._adjustIndexes( textIndex, wordIndex, lengthDiff );\r
+\r
+               word.size = newText.length;\r
+               word.value = newText;\r
+               this.removeFocus( textIndex, wordIndex );\r
+       }\r
+}\r
+\r
+\r
+function writeBody() {\r
+       var d = window.document;\r
+       var is_html = false;\r
+\r
+       d.open();\r
+\r
+       // iterate through each text input.\r
+       for( var txtid = 0; txtid < this.textInputs.length; txtid++ ) {\r
+               var end_idx = 0;\r
+               var begin_idx = 0;\r
+               d.writeln( '<form name="textInput'+txtid+'">' );\r
+               var wordtxt = this.textInputs[txtid];\r
+               this.indexes[txtid] = [];\r
+\r
+               if( wordtxt ) {\r
+                       var orig = this.originalSpellings[txtid];\r
+                       if( !orig ) break;\r
+\r
+                       //!!! plain text, or HTML mode?\r
+                       d.writeln( '<div class="plainText">' );\r
+                       // iterate through each occurrence of a misspelled word.\r
+                       for( var i = 0; i < orig.length; i++ ) {\r
+                               // find the position of the current misspelled word,\r
+                               // starting at the last misspelled word.\r
+                               // and keep looking if it's a substring of another word\r
+                               do {\r
+                                       begin_idx = wordtxt.indexOf( orig[i], end_idx );\r
+                                       end_idx = begin_idx + orig[i].length;\r
+                                       // word not found? messed up!\r
+                                       if( begin_idx == -1 ) break;\r
+                                       // look at the characters immediately before and after\r
+                                       // the word. If they are word characters we'll keep looking.\r
+                                       var before_char = wordtxt.charAt( begin_idx - 1 );\r
+                                       var after_char = wordtxt.charAt( end_idx );\r
+                               } while (\r
+                                       this._isWordChar( before_char )\r
+                                       || this._isWordChar( after_char )\r
+                               );\r
+\r
+                               // keep track of its position in the original text.\r
+                               this.indexes[txtid][i] = begin_idx;\r
+\r
+                               // write out the characters before the current misspelled word\r
+                               for( var j = this._lastPos( txtid, i ); j < begin_idx; j++ ) {\r
+                                       // !!! html mode? make it html compatible\r
+                                       d.write( this.printForHtml( wordtxt.charAt( j )));\r
+                               }\r
+\r
+                               // write out the misspelled word.\r
+                               d.write( this._wordInputStr( orig[i] ));\r
+\r
+                               // if it's the last word, write out the rest of the text\r
+                               if( i == orig.length-1 ){\r
+                                       d.write( printForHtml( wordtxt.substr( end_idx )));\r
+                               }\r
+                       }\r
+\r
+                       d.writeln( '</div>' );\r
+\r
+               }\r
+               d.writeln( '</form>' );\r
+       }\r
+       //for ( var j = 0; j < d.forms.length; j++ ) {\r
+       //      alert( d.forms[j].name );\r
+       //      for( var k = 0; k < d.forms[j].elements.length; k++ ) {\r
+       //              alert( d.forms[j].elements[k].name + ": " + d.forms[j].elements[k].value );\r
+       //      }\r
+       //}\r
+\r
+       // set the _forms property\r
+       this._forms = d.forms;\r
+       d.close();\r
+}\r
+\r
+// return the character index in the full text after the last word we evaluated\r
+function _lastPos( txtid, idx ) {\r
+       if( idx > 0 )\r
+               return this.indexes[txtid][idx-1] + this.originalSpellings[txtid][idx-1].length;\r
+       else\r
+               return 0;\r
+}\r
+\r
+function printForHtml( n ) {\r
+       return n ;              // by FredCK\r
+/*\r
+       var htmlstr = n;\r
+       if( htmlstr.length == 1 ) {\r
+               // do simple case statement if it's just one character\r
+               switch ( n ) {\r
+                       case "\n":\r
+                               htmlstr = '<br/>';\r
+                               break;\r
+                       case "<":\r
+                               htmlstr = '&lt;';\r
+                               break;\r
+                       case ">":\r
+                               htmlstr = '&gt;';\r
+                               break;\r
+               }\r
+               return htmlstr;\r
+       } else {\r
+               htmlstr = htmlstr.replace( /</g, '&lt' );\r
+               htmlstr = htmlstr.replace( />/g, '&gt' );\r
+               htmlstr = htmlstr.replace( /\n/g, '<br/>' );\r
+               return htmlstr;\r
+       }\r
+*/\r
+}\r
+\r
+function _isWordChar( letter ) {\r
+       if( letter.search( this.wordChar ) == -1 ) {\r
+               return false;\r
+       } else {\r
+               return true;\r
+       }\r
+}\r
+\r
+function _getWordObject( textIndex, wordIndex ) {\r
+       if( this._forms[textIndex] ) {\r
+               if( this._forms[textIndex].elements[wordIndex] ) {\r
+                       return this._forms[textIndex].elements[wordIndex];\r
+               }\r
+       }\r
+       return null;\r
+}\r
+\r
+function _wordInputStr( word ) {\r
+       var str = '<input readonly ';\r
+       str += 'class="blend" type="text" value="' + word + '" size="' + word.length + '">';\r
+       return str;\r
+}\r
+\r
+function _adjustIndexes( textIndex, wordIndex, lengthDiff ) {\r
+       for( var i = wordIndex + 1; i < this.originalSpellings[textIndex].length; i++ ) {\r
+               this.indexes[textIndex][i] = this.indexes[textIndex][i] + lengthDiff;\r
+       }\r
+}\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_table.html b/httemplate/elements/fckeditor/editor/dialog/fck_table.html
new file mode 100644 (file)
index 0000000..6bb9d11
--- /dev/null
@@ -0,0 +1,291 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Table dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>Table Properties</title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+// Gets the table if there is one selected.\r
+var table ;\r
+var e = oEditor.FCKSelection.GetSelectedElement() ;\r
+\r
+if ( ( !e && document.location.search.substr(1) == 'Parent' ) || ( e && e.tagName != 'TABLE' ) )\r
+       e = oEditor.FCKSelection.MoveToAncestorNode( 'TABLE' ) ;\r
+\r
+if ( e && e.tagName == "TABLE" )\r
+       table = e ;\r
+\r
+// Fired when the window loading process is finished. It sets the fields with the\r
+// actual values if a table is selected in the editor.\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if (table)\r
+       {\r
+               document.getElementById('txtRows').value    = table.rows.length ;\r
+               document.getElementById('txtColumns').value = table.rows[0].cells.length ;\r
+\r
+               // Gets the value from the Width or the Style attribute\r
+               var iWidth  = (table.style.width  ? table.style.width  : table.width ) ;\r
+               var iHeight = (table.style.height ? table.style.height : table.height ) ;\r
+\r
+               if (iWidth.indexOf('%') >= 0)                   // Percentual = %\r
+               {\r
+                       iWidth = parseInt( iWidth.substr(0,iWidth.length - 1), 10 ) ;\r
+                       document.getElementById('selWidthType').value = "percent" ;\r
+               }\r
+               else if (iWidth.indexOf('px') >= 0)             // Style Pixel = px\r
+               {                                                                                                                                                                                                                 //\r
+                       iWidth = iWidth.substr(0,iWidth.length - 2);\r
+                       document.getElementById('selWidthType').value = "pixels" ;\r
+               }\r
+\r
+               if (iHeight && iHeight.indexOf('px') >= 0)              // Style Pixel = px\r
+                       iHeight = iHeight.substr(0,iHeight.length - 2);\r
+\r
+               document.getElementById('txtWidth').value               = iWidth || '' ;\r
+               document.getElementById('txtHeight').value              = iHeight || '' ;\r
+               document.getElementById('txtBorder').value              = GetAttribute( table, 'border', '' ) ;\r
+               document.getElementById('selAlignment').value   = GetAttribute( table, 'align', '' ) ;\r
+               document.getElementById('txtCellPadding').value = GetAttribute( table, 'cellPadding', '' ) ;\r
+               document.getElementById('txtCellSpacing').value = GetAttribute( table, 'cellSpacing', '' ) ;\r
+               document.getElementById('txtSummary').value     = GetAttribute( table, 'summary', '' ) ;\r
+//             document.getElementById('cmbFontStyle').value   = table.className ;\r
+\r
+               if (table.caption) document.getElementById('txtCaption').value = table.caption.innerHTML ;\r
+\r
+               document.getElementById('txtRows').disabled    = true ;\r
+               document.getElementById('txtColumns').disabled = true ;\r
+       }\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+       window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+// Fired when the user press the OK button\r
+function Ok()\r
+{\r
+       var bExists = ( table != null ) ;\r
+\r
+       if ( ! bExists )\r
+               table = oEditor.FCK.EditorDocument.createElement( "TABLE" ) ;\r
+\r
+       // Removes the Width and Height styles\r
+       if ( bExists && table.style.width )             table.style.width = null ; //.removeAttribute("width") ;\r
+       if ( bExists && table.style.height )    table.style.height = null ; //.removeAttribute("height") ;\r
+\r
+       var sWidth = GetE('txtWidth').value ;\r
+       if ( sWidth.length > 0 && GetE('selWidthType').value == 'percent' )\r
+               sWidth += '%' ;\r
+\r
+       SetAttribute( table, 'width'            , sWidth ) ;\r
+       SetAttribute( table, 'height'           , GetE('txtHeight').value ) ;\r
+       SetAttribute( table, 'border'           , GetE('txtBorder').value ) ;\r
+       SetAttribute( table, 'align'            , GetE('selAlignment').value ) ;\r
+       SetAttribute( table, 'cellPadding'      , GetE('txtCellPadding').value ) ;\r
+       SetAttribute( table, 'cellSpacing'      , GetE('txtCellSpacing').value ) ;\r
+       SetAttribute( table, 'summary'          , GetE('txtSummary').value ) ;\r
+\r
+       var eCaption = oEditor.FCKDomTools.GetFirstChild( table, 'CAPTION' ) ;\r
+\r
+       if ( document.getElementById('txtCaption').value != '')\r
+       {\r
+               if ( !eCaption )\r
+               {\r
+                       eCaption = oEditor.FCK.EditorDocument.createElement( 'CAPTION' ) ;\r
+                       table.insertBefore( eCaption, table.firstChild ) ;\r
+               }\r
+\r
+               eCaption.innerHTML = document.getElementById('txtCaption').value ;\r
+       }\r
+       else if ( bExists && eCaption )\r
+       {\r
+               if ( oEditor.FCKBrowserInfo.IsIE )\r
+                       eCaption.innerHTML = '' ;       // TODO: It causes an IE internal error if using removeChild or table.deleteCaption().\r
+               else\r
+                       eCaption.parentNode.removeChild( eCaption ) ;\r
+       }\r
+\r
+       if (! bExists)\r
+       {\r
+               var iRows = document.getElementById('txtRows').value ;\r
+               var iCols = document.getElementById('txtColumns').value ;\r
+\r
+               for ( var r = 0 ; r < iRows ; r++ )\r
+               {\r
+                       var oRow = table.insertRow(-1) ;\r
+                       for ( var c = 0 ; c < iCols ; c++ )\r
+                       {\r
+                               var oCell = oRow.insertCell(-1) ;\r
+                               if ( oEditor.FCKBrowserInfo.IsGeckoLike )\r
+                                       oCell.innerHTML = GECKO_BOGUS ;\r
+                               //oCell.innerHTML = "&nbsp;" ;\r
+                       }\r
+               }\r
+\r
+               oEditor.FCKUndo.SaveUndoStep() ;\r
+\r
+               oEditor.FCK.InsertElement( table ) ;\r
+       }\r
+\r
+       return true ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden">\r
+       <table id="otable" cellspacing="0" cellpadding="0" width="100%" border="0" style="height: 100%">\r
+               <tr>\r
+                       <td>\r
+                               <table cellspacing="1" cellpadding="1" width="100%" border="0">\r
+                                       <tr>\r
+                                               <td valign="top">\r
+                                                       <table cellspacing="0" cellpadding="0" border="0">\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               <span fcklang="DlgTableRows">Rows</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtRows" type="text" maxlength="3" size="2" value="3" name="txtRows"\r
+                                                                                       onkeypress="return IsDigit(event);" /></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               <span fcklang="DlgTableColumns">Columns</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtColumns" type="text" maxlength="2" size="2" value="2" name="txtColumns"\r
+                                                                                       onkeypress="return IsDigit(event);" /></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               <span fcklang="DlgTableBorder">Border size</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtBorder" type="text" maxlength="2" size="2" value="1" name="txtBorder"\r
+                                                                                       onkeypress="return IsDigit(event);" /></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               <span fcklang="DlgTableAlign">Alignment</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<select id="selAlignment" name="selAlignment">\r
+                                                                                       <option fcklang="DlgTableAlignNotSet" value="" selected="selected">&lt;Not set&gt;</option>\r
+                                                                                       <option fcklang="DlgTableAlignLeft" value="left">Left</option>\r
+                                                                                       <option fcklang="DlgTableAlignCenter" value="center">Center</option>\r
+                                                                                       <option fcklang="DlgTableAlignRight" value="right">Right</option>\r
+                                                                               </select></td>\r
+                                                               </tr>\r
+                                                       </table>\r
+                                               </td>\r
+                                               <td>\r
+                                                       &nbsp;&nbsp;&nbsp;</td>\r
+                                               <td align="right" valign="top">\r
+                                                       <table cellspacing="0" cellpadding="0" border="0">\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               <span fcklang="DlgTableWidth">Width</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtWidth" type="text" maxlength="4" size="3" value="200" name="txtWidth"\r
+                                                                                       onkeypress="return IsDigit(event);" /></td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<select id="selWidthType" name="selWidthType">\r
+                                                                                       <option fcklang="DlgTableWidthPx" value="pixels" selected="selected">pixels</option>\r
+                                                                                       <option fcklang="DlgTableWidthPc" value="percent">percent</option>\r
+                                                                               </select></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               <span fcklang="DlgTableHeight">Height</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtHeight" type="text" maxlength="4" size="3" name="txtHeight" onkeypress="return IsDigit(event);" /></td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<span fcklang="DlgTableWidthPx">pixels</span></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgTableCellSpace">Cell spacing</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtCellSpacing" type="text" maxlength="2" size="2" value="1" name="txtCellSpacing"\r
+                                                                                       onkeypress="return IsDigit(event);" /></td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgTableCellPad">Cell padding</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtCellPadding" type="text" maxlength="2" size="2" value="1" name="txtCellPadding"\r
+                                                                                       onkeypress="return IsDigit(event);" /></td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                               </tr>\r
+                                                       </table>\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                               <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                                       <tr>\r
+                                               <td nowrap="nowrap">\r
+                                                       <span fcklang="DlgTableCaption">Caption</span>:&nbsp;</td>\r
+                                               <td>\r
+                                                       &nbsp;</td>\r
+                                               <td width="100%" nowrap="nowrap">\r
+                                                       <input id="txtCaption" type="text" style="width: 100%" /></td>\r
+                                       </tr>\r
+                                       <tr>\r
+                                               <td nowrap="nowrap">\r
+                                                       <span fcklang="DlgTableSummary">Summary</span>:&nbsp;</td>\r
+                                               <td>\r
+                                                       &nbsp;</td>\r
+                                               <td width="100%" nowrap="nowrap">\r
+                                                       <input id="txtSummary" type="text" style="width: 100%" /></td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_tablecell.html b/httemplate/elements/fckeditor/editor/dialog/fck_tablecell.html
new file mode 100644 (file)
index 0000000..b7c536b
--- /dev/null
@@ -0,0 +1,255 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Cell properties dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>Table Cell Properties</title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+// Array of selected Cells\r
+var aCells = oEditor.FCKTableHandler.GetSelectedCells() ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage( document ) ;\r
+\r
+       SetStartupValue() ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+       window.parent.SetAutoSize( true ) ;\r
+}\r
+\r
+function SetStartupValue()\r
+{\r
+       if ( aCells.length > 0 )\r
+       {\r
+               var oCell = aCells[0] ;\r
+               var iWidth = GetAttribute( oCell, 'width' ) ;\r
+\r
+               if ( iWidth.indexOf && iWidth.indexOf( '%' ) >= 0 )\r
+               {\r
+                       iWidth = iWidth.substr( 0, iWidth.length - 1 ) ;\r
+                       GetE('selWidthType').value = 'percent' ;\r
+               }\r
+\r
+               if ( oCell.attributes['noWrap'] != null && oCell.attributes['noWrap'].specified )\r
+                       GetE('selWordWrap').value = !oCell.noWrap ;\r
+\r
+               GetE('txtWidth').value                  = iWidth ;\r
+               GetE('txtHeight').value                 = GetAttribute( oCell, 'height' ) ;\r
+               GetE('selHAlign').value                 = GetAttribute( oCell, 'align' ) ;\r
+               GetE('selVAlign').value                 = GetAttribute( oCell, 'vAlign' ) ;\r
+               GetE('txtRowSpan').value                = GetAttribute( oCell, 'rowSpan' ) ;\r
+               GetE('txtCollSpan').value               = GetAttribute( oCell, 'colSpan' ) ;\r
+               GetE('txtBackColor').value              = GetAttribute( oCell, 'bgColor' ) ;\r
+               GetE('txtBorderColor').value    = GetAttribute( oCell, 'borderColor' ) ;\r
+//             GetE('cmbFontStyle').value              = oCell.className ;\r
+       }\r
+}\r
+\r
+// Fired when the user press the OK button\r
+function Ok()\r
+{\r
+       for( i = 0 ; i < aCells.length ; i++ )\r
+       {\r
+               if ( GetE('txtWidth').value.length > 0 )\r
+                       aCells[i].width = GetE('txtWidth').value + ( GetE('selWidthType').value == 'percent' ? '%' : '') ;\r
+               else\r
+                       aCells[i].removeAttribute( 'width', 0 ) ;\r
+\r
+               if ( GetE('selWordWrap').value == 'false' )\r
+                       aCells[i].noWrap = true ;\r
+               else\r
+                       aCells[i].removeAttribute( 'noWrap' ) ;\r
+\r
+               SetAttribute( aCells[i], 'height'               , GetE('txtHeight').value ) ;\r
+               SetAttribute( aCells[i], 'align'                , GetE('selHAlign').value ) ;\r
+               SetAttribute( aCells[i], 'vAlign'               , GetE('selVAlign').value ) ;\r
+               SetAttribute( aCells[i], 'rowSpan'              , GetE('txtRowSpan').value ) ;\r
+               SetAttribute( aCells[i], 'colSpan'              , GetE('txtCollSpan').value ) ;\r
+               SetAttribute( aCells[i], 'bgColor'              , GetE('txtBackColor').value ) ;\r
+               SetAttribute( aCells[i], 'borderColor'  , GetE('txtBorderColor').value ) ;\r
+//             SetAttribute( aCells[i], 'className'    , GetE('cmbFontStyle').value ) ;\r
+       }\r
+\r
+       return true ;\r
+}\r
+\r
+function SelectBackColor( color )\r
+{\r
+       if ( color && color.length > 0 )\r
+               GetE('txtBackColor').value = color ;\r
+}\r
+\r
+function SelectBorderColor( color )\r
+{\r
+       if ( color && color.length > 0 )\r
+               GetE('txtBorderColor').value = color ;\r
+}\r
+\r
+function SelectColor( wich )\r
+{\r
+       oEditor.FCKDialog.OpenDialog( 'FCKDialog_Color', oEditor.FCKLang.DlgColorTitle, 'dialog/fck_colorselector.html', 400, 330, wich == 'Back' ? SelectBackColor : SelectBorderColor, window ) ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body scroll="no" style="overflow: hidden">\r
+       <table cellspacing="0" cellpadding="0" width="100%" border="0" height="100%">\r
+               <tr>\r
+                       <td>\r
+                               <table cellspacing="1" cellpadding="1" width="100%" border="0">\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <table cellspacing="0" cellpadding="0" border="0">\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellWidth">Width</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input onkeypress="return IsDigit(event);" id="txtWidth" type="text" maxlength="4"\r
+                                                                                       size="3" name="txtWidth" />&nbsp;<select id="selWidthType" name="selWidthType">\r
+                                                                                               <option fcklang="DlgCellWidthPx" value="pixels" selected="selected">pixels</option>\r
+                                                                                               <option fcklang="DlgCellWidthPc" value="percent">percent</option>\r
+                                                                                       </select></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellHeight">Height</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtHeight" type="text" maxlength="4" size="3" name="txtHeight" onkeypress="return IsDigit(event);" />&nbsp;<span\r
+                                                                                       fcklang="DlgCellWidthPx">pixels</span></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellWordWrap">Word Wrap</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<select id="selWordWrap" name="selAlignment">\r
+                                                                                       <option fcklang="DlgCellWordWrapYes" value="true" selected="selected">Yes</option>\r
+                                                                                       <option fcklang="DlgCellWordWrapNo" value="false">No</option>\r
+                                                                               </select></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellHorAlign">Horizontal Alignment</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<select id="selHAlign" name="selAlignment">\r
+                                                                                       <option fcklang="DlgCellHorAlignNotSet" value="" selected>&lt;Not set&gt;</option>\r
+                                                                                       <option fcklang="DlgCellHorAlignLeft" value="left">Left</option>\r
+                                                                                       <option fcklang="DlgCellHorAlignCenter" value="center">Center</option>\r
+                                                                                       <option fcklang="DlgCellHorAlignRight" value="right">Right</option>\r
+                                                                               </select></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellVerAlign">Vertical Alignment</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<select id="selVAlign" name="selAlignment">\r
+                                                                                       <option fcklang="DlgCellVerAlignNotSet" value="" selected>&lt;Not set&gt;</option>\r
+                                                                                       <option fcklang="DlgCellVerAlignTop" value="top">Top</option>\r
+                                                                                       <option fcklang="DlgCellVerAlignMiddle" value="middle">Middle</option>\r
+                                                                                       <option fcklang="DlgCellVerAlignBottom" value="bottom">Bottom</option>\r
+                                                                                       <option fcklang="DlgCellVerAlignBaseline" value="baseline">Baseline</option>\r
+                                                                               </select></td>\r
+                                                               </tr>\r
+                                                       </table>\r
+                                               </td>\r
+                                               <td>\r
+                                                       &nbsp;&nbsp;&nbsp;</td>\r
+                                               <td align="right">\r
+                                                       <table cellspacing="0" cellpadding="0" border="0">\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellRowSpan">Rows Span</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;\r
+                                                                               <input onkeypress="return IsDigit(event);" id="txtRowSpan" type="text" maxlength="3" size="2"\r
+                                                                                       name="txtRows"></td>\r
+                                                                       <td>\r
+                                                                       </td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellCollSpan">Columns Span</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;\r
+                                                                               <input onkeypress="return IsDigit(event);" id="txtCollSpan" type="text" maxlength="2"\r
+                                                                                       size="2" name="txtColumns"></td>\r
+                                                                       <td>\r
+                                                                       </td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;</td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellBackColor">Background Color</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtBackColor" type="text" size="8" name="txtCellSpacing"></td>\r
+                                                                       <td>\r
+                                                                               &nbsp;\r
+                                                                               <input type="button" fcklang="DlgCellBtnSelect" value="Select..." onclick="SelectColor( 'Back' )"></td>\r
+                                                               </tr>\r
+                                                               <tr>\r
+                                                                       <td nowrap="nowrap">\r
+                                                                               <span fcklang="DlgCellBorderColor">Border Color</span>:</td>\r
+                                                                       <td>\r
+                                                                               &nbsp;<input id="txtBorderColor" type="text" size="8" name="txtCellPadding" /></td>\r
+                                                                       <td>\r
+                                                                               &nbsp;\r
+                                                                               <input type="button" fcklang="DlgCellBtnSelect" value="Select..." onclick="SelectColor( 'Border' )" /></td>\r
+                                                               </tr>\r
+                                                       </table>\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_template.html b/httemplate/elements/fckeditor/editor/dialog/fck_template.html
new file mode 100644 (file)
index 0000000..418e9df
--- /dev/null
@@ -0,0 +1,242 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Template selection dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <style type="text/css">\r
+                       .TplList\r
+                       {\r
+                               border: #dcdcdc 2px solid;\r
+                               background-color: #ffffff;\r
+                               overflow: auto;\r
+                               width: 90%;\r
+                       }\r
+\r
+                       .TplItem\r
+                       {\r
+                               margin: 5px;\r
+                               padding: 7px;\r
+                               border: #eeeeee 1px solid;\r
+                       }\r
+\r
+                       .TplItem TABLE\r
+                       {\r
+                               display: inline;\r
+                       }\r
+\r
+                       .TplTitle\r
+                       {\r
+                               font-weight: bold;\r
+                       }\r
+               </style>\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor            = window.parent.InnerDialogLoaded() ;\r
+var FCK                        = oEditor.FCK ;\r
+var FCKLang            = oEditor.FCKLang ;\r
+var FCKConfig  = oEditor.FCKConfig ;\r
+\r
+window.onload = function()\r
+{\r
+       // Set the right box height (browser dependent).\r
+       GetE('eList').style.height = document.all ? '100%' : '295px' ;\r
+\r
+       // Translate the dialog box texts.\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       GetE('xChkReplaceAll').checked = ( FCKConfig.TemplateReplaceAll !== false ) ;\r
+\r
+       if ( FCKConfig.TemplateReplaceCheckbox !== false )\r
+               GetE('xReplaceBlock').style.display = '' ;\r
+\r
+       window.parent.SetAutoSize( true ) ;\r
+\r
+       LoadTemplatesXml() ;\r
+}\r
+\r
+function LoadTemplatesXml()\r
+{\r
+       var oTemplate ;\r
+\r
+       if ( !FCK._Templates )\r
+       {\r
+               GetE('eLoading').style.display = '' ;\r
+\r
+               // Create the Templates array.\r
+               FCK._Templates = new Array() ;\r
+\r
+               // Load the XML file.\r
+               var oXml = new oEditor.FCKXml() ;\r
+               oXml.LoadUrl( FCKConfig.TemplatesXmlPath ) ;\r
+\r
+               // Get the Images Base Path.\r
+               var oAtt = oXml.SelectSingleNode( 'Templates/@imagesBasePath' ) ;\r
+               var sImagesBasePath = oAtt ? oAtt.value : '' ;\r
+\r
+               // Get the "Template" nodes defined in the XML file.\r
+               var aTplNodes = oXml.SelectNodes( 'Templates/Template' ) ;\r
+\r
+               for ( var i = 0 ; i < aTplNodes.length ; i++ )\r
+               {\r
+                       var oNode = aTplNodes[i] ;\r
+\r
+                       oTemplate = new Object() ;\r
+\r
+                       var oPart ;\r
+\r
+                       // Get the Template Title.\r
+                       if ( (oPart = oNode.attributes.getNamedItem('title')) )\r
+                               oTemplate.Title = oPart.value ;\r
+                       else\r
+                               oTemplate.Title = 'Template ' + ( i + 1 ) ;\r
+\r
+                       // Get the Template Description.\r
+                       if ( (oPart = oXml.SelectSingleNode( 'Description', oNode )) )\r
+                               oTemplate.Description = oPart.text ? oPart.text : oPart.textContent ;\r
+\r
+                       // Get the Template Image.\r
+                       if ( (oPart = oNode.attributes.getNamedItem('image')) )\r
+                               oTemplate.Image = sImagesBasePath + oPart.value ;\r
+\r
+                       // Get the Template HTML.\r
+                       if ( (oPart = oXml.SelectSingleNode( 'Html', oNode )) )\r
+                               oTemplate.Html = oPart.text ? oPart.text : oPart.textContent ;\r
+                       else\r
+                       {\r
+                               alert( 'No HTML defined for template index ' + i + '. Please review the "' + FCKConfig.TemplatesXmlPath + '" file.' ) ;\r
+                               continue ;\r
+                       }\r
+\r
+                       FCK._Templates[ FCK._Templates.length ] = oTemplate ;\r
+               }\r
+\r
+               GetE('eLoading').style.display = 'none' ;\r
+       }\r
+\r
+       if ( FCK._Templates.length == 0 )\r
+               GetE('eEmpty').style.display = '' ;\r
+       else\r
+       {\r
+               for ( var j = 0 ; j < FCK._Templates.length ; j++ )\r
+               {\r
+                       oTemplate = FCK._Templates[j] ;\r
+\r
+                       var oItemDiv = GetE('eList').appendChild( document.createElement( 'DIV' ) ) ;\r
+                       oItemDiv.TplIndex = j ;\r
+                       oItemDiv.className = 'TplItem' ;\r
+\r
+                       // Build the inner HTML of our new item DIV.\r
+                       var sInner = '<table><tr>' ;\r
+\r
+                       if ( oTemplate.Image )\r
+                               sInner += '<td valign="top"><img src="' + oTemplate.Image + '"><\/td>' ;\r
+\r
+                       sInner += '<td valign="top"><div class="TplTitle">' + oTemplate.Title + '<\/div>' ;\r
+\r
+                       if ( oTemplate.Description )\r
+                               sInner += '<div>' + oTemplate.Description + '<\/div>' ;\r
+\r
+                       sInner += '<\/td><\/tr><\/table>' ;\r
+\r
+                       oItemDiv.innerHTML = sInner ;\r
+\r
+                       oItemDiv.onmouseover = ItemDiv_OnMouseOver ;\r
+                       oItemDiv.onmouseout = ItemDiv_OnMouseOut ;\r
+                       oItemDiv.onclick = ItemDiv_OnClick ;\r
+               }\r
+       }\r
+}\r
+\r
+function ItemDiv_OnMouseOver()\r
+{\r
+       this.className += ' PopupSelectionBox' ;\r
+}\r
+\r
+function ItemDiv_OnMouseOut()\r
+{\r
+       this.className = this.className.replace( /\s*PopupSelectionBox\s*/, '' ) ;\r
+}\r
+\r
+function ItemDiv_OnClick()\r
+{\r
+       SelectTemplate( this.TplIndex ) ;\r
+}\r
+\r
+function SelectTemplate( index )\r
+{\r
+       oEditor.FCKUndo.SaveUndoStep() ;\r
+\r
+       if ( GetE('xChkReplaceAll').checked )\r
+               FCK.SetHTML( FCK._Templates[index].Html ) ;\r
+       else\r
+               FCK.InsertHtml( FCK._Templates[index].Html ) ;\r
+\r
+       window.parent.Cancel( true ) ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden">\r
+       <table width="100%" style="height: 100%">\r
+               <tr>\r
+                       <td align="center">\r
+                               <span fcklang="DlgTemplatesSelMsg">Please select the template to open in the editor<br />\r
+                                       (the actual contents will be lost):</span>\r
+                       </td>\r
+               </tr>\r
+               <tr>\r
+                       <td height="100%" align="center">\r
+                               <div id="eList" align="left" class="TplList">\r
+                                       <div id="eLoading" align="center" style="display: none">\r
+                                               <br />\r
+                                               <span fcklang="DlgTemplatesLoading">Loading templates list. Please wait...</span>\r
+                                       </div>\r
+                                       <div id="eEmpty" align="center" style="display: none">\r
+                                               <br />\r
+                                               <span fcklang="DlgTemplatesNoTpl">(No templates defined)</span>\r
+                                       </div>\r
+                               </div>\r
+                       </td>\r
+               </tr>\r
+               <tr id="xReplaceBlock" style="display: none">\r
+                       <td>\r
+                               <table cellpadding="0" cellspacing="0">\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <input id="xChkReplaceAll" type="checkbox" /></td>\r
+                                               <td>\r
+                                                       &nbsp;</td>\r
+                                               <td>\r
+                                                       <label for="xChkReplaceAll" fcklang="DlgTemplatesReplace">\r
+                                                               Replace actual contents</label></td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
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 (file)
index 0000000..efdabbe
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template1.gif 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 (file)
index 0000000..d1cebb3
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template2.gif 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 (file)
index 0000000..db41cb4
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/dialog/fck_template/images/template3.gif 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 (file)
index 0000000..b7de33a
--- /dev/null
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Text Area dialog window.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Text Area Properties</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta content="noindex, nofollow" name="robots">\r
+               <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+               <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+var oActiveEl = oEditor.FCKSelection.GetSelectedElement() ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( oActiveEl && oActiveEl.tagName == 'TEXTAREA' )\r
+       {\r
+               GetE('txtName').value           = oActiveEl.name ;\r
+               GetE('txtCols').value           = GetAttribute( oActiveEl, 'cols' ) ;\r
+               GetE('txtRows').value           = GetAttribute( oActiveEl, 'rows' ) ;\r
+       }\r
+       else\r
+               oActiveEl = null ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( !oActiveEl )\r
+       {\r
+               oActiveEl = oEditor.FCK.EditorDocument.createElement( 'TEXTAREA' ) ;\r
+               oActiveEl = oEditor.FCK.InsertElementAndGetIt( oActiveEl ) ;\r
+       }\r
+\r
+       oActiveEl.name = GetE('txtName').value ;\r
+       SetAttribute( oActiveEl, 'cols', GetE('txtCols').value ) ;\r
+       SetAttribute( oActiveEl, 'rows', GetE('txtRows').value ) ;\r
+\r
+       return true ;\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body style='OVERFLOW: hidden' scroll='no'>\r
+               <table height="100%" width="100%">\r
+                       <tr>\r
+                               <td align="center">\r
+                                       <table border="0" cellpadding="0" cellspacing="0" width="80%">\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fckLang="DlgTextareaName">Name</span><br>\r
+                                                               <input type="text" id="txtName" style="WIDTH: 100%">\r
+                                                               <span fckLang="DlgTextareaCols">Collumns</span><br>\r
+                                                               <input id="txtCols" type="text" size="5">\r
+                                                               <br>\r
+                                                               <span fckLang="DlgTextareaRows">Rows</span><br>\r
+                                                               <input id="txtRows" type="text" size="5">\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/dialog/fck_textfield.html b/httemplate/elements/fckeditor/editor/dialog/fck_textfield.html
new file mode 100644 (file)
index 0000000..7b4c8ef
--- /dev/null
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Text field dialog window.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title></title>\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta content="noindex, nofollow" name="robots" />\r
+       <script src="common/fck_dialog_common.js" type="text/javascript"></script>\r
+       <script type="text/javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+\r
+// Gets the document DOM\r
+var oDOM = oEditor.FCK.EditorDocument ;\r
+\r
+var oActiveEl = oEditor.FCKSelection.GetSelectedElement() ;\r
+\r
+window.onload = function()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage(document) ;\r
+\r
+       if ( oActiveEl && oActiveEl.tagName == 'INPUT' && ( oActiveEl.type == 'text' || oActiveEl.type == 'password' ) )\r
+       {\r
+               GetE('txtName').value   = oActiveEl.name ;\r
+               GetE('txtValue').value  = oActiveEl.value ;\r
+               GetE('txtSize').value   = GetAttribute( oActiveEl, 'size' ) ;\r
+               GetE('txtMax').value    = GetAttribute( oActiveEl, 'maxLength' ) ;\r
+               GetE('txtType').value   = oActiveEl.type ;\r
+\r
+               GetE('txtType').disabled = true ;\r
+       }\r
+       else\r
+               oActiveEl = null ;\r
+\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( isNaN( GetE('txtMax').value ) || GetE('txtMax').value < 0 )\r
+       {\r
+               alert( "Maximum characters must be a positive number." ) ;\r
+               GetE('txtMax').focus() ;\r
+               return false ;\r
+       }\r
+       else if( isNaN( GetE('txtSize').value ) || GetE('txtSize').value < 0 )\r
+       {\r
+               alert( "Width must be a positive number." ) ;\r
+               GetE('txtSize').focus() ;\r
+               return false ;\r
+       }\r
+\r
+       if ( !oActiveEl )\r
+       {\r
+               oActiveEl = oEditor.FCK.EditorDocument.createElement( 'INPUT' ) ;\r
+               oActiveEl.type = GetE('txtType').value ;\r
+               oActiveEl = oEditor.FCK.InsertElementAndGetIt( oActiveEl ) ;\r
+       }\r
+\r
+       oActiveEl.name = GetE('txtName').value ;\r
+       SetAttribute( oActiveEl, 'value'        , GetE('txtValue').value ) ;\r
+       SetAttribute( oActiveEl, 'size'         , GetE('txtSize').value ) ;\r
+       SetAttribute( oActiveEl, 'maxlength', GetE('txtMax').value ) ;\r
+\r
+       return true ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body style="overflow: hidden">\r
+       <table width="100%" style="height: 100%">\r
+               <tr>\r
+                       <td align="center">\r
+                               <table cellspacing="0" cellpadding="0" border="0">\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgTextName">Name</span><br />\r
+                                                       <input id="txtName" type="text" size="20" />\r
+                                               </td>\r
+                                               <td>\r
+                                               </td>\r
+                                               <td>\r
+                                                       <span fcklang="DlgTextValue">Value</span><br />\r
+                                                       <input id="txtValue" type="text" size="25" />\r
+                                               </td>\r
+                                       </tr>\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgTextCharWidth">Character Width</span><br />\r
+                                                       <input id="txtSize" type="text" size="5" />\r
+                                               </td>\r
+                                               <td>\r
+                                               </td>\r
+                                               <td>\r
+                                                       <span fcklang="DlgTextMaxChars">Maximum Characters</span><br />\r
+                                                       <input id="txtMax" type="text" size="5" />\r
+                                               </td>\r
+                                       </tr>\r
+                                       <tr>\r
+                                               <td>\r
+                                                       <span fcklang="DlgTextType">Type</span><br />\r
+                                                       <select id="txtType">\r
+                                                               <option value="text" selected="selected" fcklang="DlgTextTypeText">Text</option>\r
+                                                               <option value="password" fcklang="DlgTextTypePass">Password</option>\r
+                                                       </select>\r
+                                               </td>\r
+                                               <td>\r
+                                                       &nbsp;</td>\r
+                                               <td>\r
+                                               </td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/fckdebug.html b/httemplate/elements/fckeditor/editor/fckdebug.html
new file mode 100644 (file)
index 0000000..db99d60
--- /dev/null
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This is the Debug window.\r
+ * It automatically popups if the Debug = true in the configuration file.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>FCKeditor Debug Window</title>\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <script type="text/javascript">\r
+\r
+var oWindow ;\r
+var oDiv ;\r
+\r
+if ( !window.FCKMessages )\r
+       window.FCKMessages = new Array() ;\r
+\r
+window.onload = function()\r
+{\r
+       oWindow = document.getElementById('xOutput').contentWindow ;\r
+       oWindow.document.open() ;\r
+       oWindow.document.write( '<div id="divMsg"><\/div>' ) ;\r
+       oWindow.document.close() ;\r
+       oDiv    = oWindow.document.getElementById('divMsg') ;\r
+}\r
+\r
+function Output( message, color, noParse )\r
+{\r
+       if ( !noParse && message != null && isNaN( message ) )\r
+               message = message.replace(/</g, "&lt;") ;\r
+\r
+       if ( color )\r
+               message = '<font color="' + color + '">' + message + '<\/font>' ;\r
+\r
+       window.FCKMessages[ window.FCKMessages.length ] = message ;\r
+       StartTimer() ;\r
+}\r
+\r
+function OutputObject( anyObject, color )\r
+{\r
+       var message ;\r
+\r
+       if ( anyObject != null )\r
+       {\r
+               message = 'Properties of: ' + anyObject + '</b><blockquote>' ;\r
+\r
+               for (var prop in anyObject)\r
+               {\r
+                       try\r
+                       {\r
+                               var sVal = anyObject[ prop ] != null ? anyObject[ prop ] + '' : '[null]' ;\r
+                               message += '<b>' + prop + '</b> : ' + sVal.replace(/</g, '&lt;') + '<br>' ;\r
+                       }\r
+                       catch (e)\r
+                       {\r
+                               try\r
+                               {\r
+                                       message += '<b>' + prop + '</b> : [' + typeof( anyObject[ prop ] ) + ']<br>' ;\r
+                               }\r
+                               catch (e)\r
+                               {\r
+                                       message += '<b>' + prop + '</b> : [-error-]<br>' ;\r
+                               }\r
+                       }\r
+               }\r
+\r
+               message += '</blockquote><b>' ;\r
+       } else\r
+               message = 'OutputObject : Object is "null".' ;\r
+\r
+       Output( message, color, true ) ;\r
+}\r
+\r
+function StartTimer()\r
+{\r
+       window.setTimeout( 'CheckMessages()', 100 ) ;\r
+}\r
+\r
+function CheckMessages()\r
+{\r
+       if ( window.FCKMessages.length > 0 )\r
+       {\r
+               // Get the first item in the queue\r
+               var sMessage = window.FCKMessages[0] ;\r
+\r
+               // Removes the first item from the queue\r
+               var oTempArray = new Array() ;\r
+               for ( i = 1 ; i < window.FCKMessages.length ; i++ )\r
+                       oTempArray[ i - 1 ] = window.FCKMessages[ i ] ;\r
+               window.FCKMessages = oTempArray ;\r
+\r
+               var d = new Date() ;\r
+               var sTime =\r
+                       ( d.getHours() + 100 + '' ).substr( 1,2 ) + ':' +\r
+                       ( d.getMinutes() + 100 + '' ).substr( 1,2 ) + ':' +\r
+                       ( d.getSeconds() + 100 + '' ).substr( 1,2 ) + ':' +\r
+                       ( d.getMilliseconds() + 1000 + '' ).substr( 1,3 ) ;\r
+\r
+               var oMsgDiv = oWindow.document.createElement( 'div' ) ;\r
+               oMsgDiv.innerHTML = sTime + ': <b>' + sMessage + '<\/b>' ;\r
+               oDiv.appendChild( oMsgDiv ) ;\r
+               oMsgDiv.scrollIntoView() ;\r
+       }\r
+}\r
+\r
+function Clear()\r
+{\r
+       oDiv.innerHTML = '' ;\r
+}\r
+       </script>\r
+</head>\r
+<body style="margin: 10px">\r
+       <table style="height: 100%" cellspacing="5" cellpadding="0" width="100%" border="0">\r
+               <tr>\r
+                       <td>\r
+                               <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                                       <tr>\r
+                                               <td style="font-weight: bold; font-size: 1.2em;">\r
+                                                       FCKeditor Debug Window</td>\r
+                                               <td align="right">\r
+                                                       <input type="button" value="Clear" onclick="Clear();" /></td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+               <tr style="height: 100%">\r
+                       <td style="border: #696969 1px solid">\r
+                               <iframe id="xOutput" width="100%" height="100%" scrolling="auto" src="javascript:void(0)"\r
+                                       frameborder="0"></iframe>\r
+                       </td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/fckdialog.html b/httemplate/elements/fckeditor/editor/fckdialog.html
new file mode 100644 (file)
index 0000000..7f26822
--- /dev/null
@@ -0,0 +1,324 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This page is used by all dialog box as the container.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+       <head>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+               <meta name="robots" content="noindex, nofollow" />\r
+               <script type="text/javascript">\r
+\r
+// On some Gecko browsers (probably over slow connections) the\r
+// "dialogArguments" are not set so we must get it from the opener window.\r
+if ( !window.dialogArguments )\r
+       window.dialogArguments = window.opener.FCKLastDialogInfo ;\r
+\r
+// Sets the Skin CSS\r
+document.write( '<link href="' + window.dialogArguments.Editor.FCKConfig.SkinPath + 'fck_dialog.css" type="text/css" rel="stylesheet">' ) ;\r
+\r
+// Sets the language direction.\r
+window.document.dir = window.dialogArguments.Editor.FCKLang.Dir ;\r
+\r
+var sTitle = window.dialogArguments.Title ;\r
+document.write( '<title>' + sTitle + '<\/title>' ) ;\r
+\r
+function LoadInnerDialog()\r
+{\r
+       if ( window.onresize )\r
+               window.onresize() ;\r
+\r
+       // First of all, translate the dialog box contents.\r
+       window.dialogArguments.Editor.FCKLanguageManager.TranslatePage( document ) ;\r
+\r
+       window.frames["frmMain"].document.location.href = window.dialogArguments.Page ;\r
+}\r
+\r
+function InnerDialogLoaded()\r
+{\r
+       var oInnerDoc = document.getElementById('frmMain').contentWindow.document ;\r
+\r
+       // Set the language direction.\r
+       oInnerDoc.dir = window.dialogArguments.Editor.FCKLang.Dir ;\r
+\r
+       // Sets the Skin CSS.\r
+       oInnerDoc.write( '<link href="' + window.dialogArguments.Editor.FCKConfig.SkinPath + 'fck_dialog.css" type="text/css" rel="stylesheet">' ) ;\r
+\r
+       SetOnKeyDown( oInnerDoc ) ;\r
+       DisableContextMenu( oInnerDoc ) ;\r
+\r
+       return window.dialogArguments.Editor ;\r
+}\r
+\r
+function SetOkButton( showIt )\r
+{\r
+       document.getElementById('btnOk').style.visibility = ( showIt ? '' : 'hidden' ) ;\r
+}\r
+\r
+var bAutoSize = false ;\r
+\r
+function SetAutoSize( autoSize )\r
+{\r
+       bAutoSize = autoSize ;\r
+       RefreshSize() ;\r
+}\r
+\r
+function RefreshSize()\r
+{\r
+       if ( bAutoSize )\r
+       {\r
+               var oInnerDoc = document.getElementById('frmMain').contentWindow.document ;\r
+\r
+               var iFrameHeight ;\r
+               if ( document.all )\r
+                       iFrameHeight = oInnerDoc.body.offsetHeight ;\r
+               else\r
+                       iFrameHeight = document.getElementById('frmMain').contentWindow.innerHeight ;\r
+\r
+               var iInnerHeight = oInnerDoc.body.scrollHeight ;\r
+\r
+               var iDiff = iInnerHeight - iFrameHeight ;\r
+\r
+               if ( iDiff > 0 )\r
+               {\r
+                       if ( document.all )\r
+                               window.dialogHeight = ( parseInt( window.dialogHeight, 10 ) + iDiff ) + 'px' ;\r
+                       else\r
+                               window.resizeBy( 0, iDiff ) ;\r
+               }\r
+       }\r
+}\r
+\r
+function Ok()\r
+{\r
+       if ( window.frames["frmMain"].Ok && window.frames["frmMain"].Ok() )\r
+               Cancel() ;\r
+}\r
+\r
+function Cancel( dontFireChange )\r
+{\r
+       if ( !dontFireChange )\r
+       {\r
+               // All dialog windows, by default, will fire the "OnSelectionChange"\r
+               // event, no matter the Ok or Cancel button has been pressed.\r
+               window.dialogArguments.Editor.FCK.Events.FireEvent( 'OnSelectionChange' ) ;\r
+       }\r
+       window.close() ;\r
+}\r
+\r
+// Object that holds all available tabs.\r
+var oTabs = new Object() ;\r
+\r
+function TabDiv_OnClick()\r
+{\r
+       SetSelectedTab( this.TabCode ) ;\r
+}\r
+\r
+function AddTab( tabCode, tabText, startHidden )\r
+{\r
+       if ( typeof( oTabs[ tabCode ] ) != 'undefined' )\r
+               return ;\r
+\r
+       var eTabsRow = document.getElementById( 'Tabs' ) ;\r
+\r
+       var oCell = eTabsRow.insertCell(  eTabsRow.cells.length - 1 ) ;\r
+       oCell.noWrap = true ;\r
+\r
+       var oDiv = document.createElement( 'DIV' ) ;\r
+       oDiv.className = 'PopupTab' ;\r
+       oDiv.innerHTML = tabText ;\r
+       oDiv.TabCode = tabCode ;\r
+       oDiv.onclick = TabDiv_OnClick ;\r
+\r
+       if ( startHidden )\r
+               oDiv.style.display = 'none' ;\r
+\r
+       eTabsRow = document.getElementById( 'TabsRow' ) ;\r
+\r
+       oCell.appendChild( oDiv ) ;\r
+\r
+       if ( eTabsRow.style.display == 'none' )\r
+       {\r
+               var eTitleArea = document.getElementById( 'TitleArea' ) ;\r
+               eTitleArea.className = 'PopupTitle' ;\r
+\r
+               oDiv.className = 'PopupTabSelected' ;\r
+               eTabsRow.style.display = '' ;\r
+\r
+               if ( ! window.dialogArguments.Editor.FCKBrowserInfo.IsIE )\r
+                       window.onresize() ;\r
+       }\r
+\r
+       oTabs[ tabCode ] = oDiv ;\r
+}\r
+\r
+function SetSelectedTab( tabCode )\r
+{\r
+       for ( var sCode in oTabs )\r
+       {\r
+               if ( sCode == tabCode )\r
+                       oTabs[sCode].className = 'PopupTabSelected' ;\r
+               else\r
+                       oTabs[sCode].className = 'PopupTab' ;\r
+       }\r
+\r
+       if ( typeof( window.frames["frmMain"].OnDialogTabChange ) == 'function' )\r
+               window.frames["frmMain"].OnDialogTabChange( tabCode ) ;\r
+}\r
+\r
+function SetTabVisibility( tabCode, isVisible )\r
+{\r
+       var oTab = oTabs[ tabCode ] ;\r
+       oTab.style.display = isVisible ? '' : 'none' ;\r
+\r
+       if ( ! isVisible && oTab.className == 'PopupTabSelected' )\r
+       {\r
+               for ( var sCode in oTabs )\r
+               {\r
+                       if ( oTabs[sCode].style.display != 'none' )\r
+                       {\r
+                               SetSelectedTab( sCode ) ;\r
+                               break ;\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+function SetOnKeyDown( targetDocument )\r
+{\r
+       targetDocument.onkeydown = function ( e )\r
+       {\r
+               e = e || event || this.parentWindow.event ;\r
+               switch ( e.keyCode )\r
+               {\r
+                       case 13 :               // ENTER\r
+                               var oTarget = e.srcElement || e.target ;\r
+                               if ( oTarget.tagName == 'TEXTAREA' )\r
+                                       return true ;\r
+                               Ok() ;\r
+                               return false ;\r
+                       case 27 :               // ESC\r
+                               Cancel() ;\r
+                               return false ;\r
+                               break ;\r
+               }\r
+               return true ;\r
+       }\r
+}\r
+SetOnKeyDown( document ) ;\r
+\r
+function DisableContextMenu( targetDocument )\r
+{\r
+       if ( window.dialogArguments.Editor.FCKBrowserInfo.IsIE ) return ;\r
+\r
+       // Disable Right-Click\r
+       var oOnContextMenu = function( e )\r
+       {\r
+               var sTagName = e.target.tagName ;\r
+               if ( ! ( ( sTagName == "INPUT" && e.target.type == "text" ) || sTagName == "TEXTAREA" ) )\r
+                       e.preventDefault() ;\r
+       }\r
+       targetDocument.addEventListener( 'contextmenu', oOnContextMenu, true ) ;\r
+}\r
+DisableContextMenu( document ) ;\r
+\r
+if ( ! window.dialogArguments.Editor.FCKBrowserInfo.IsIE )\r
+{\r
+       window.onresize = function()\r
+       {\r
+               var oFrame = document.getElementById("frmMain") ;\r
+\r
+               if ( ! oFrame )\r
+               return ;\r
+\r
+               oFrame.height = 0 ;\r
+\r
+               var oCell = document.getElementById("FrameCell") ;\r
+               var iHeight = oCell.offsetHeight ;\r
+\r
+               oFrame.height = iHeight - 2 ;\r
+       }\r
+}\r
+\r
+if ( window.dialogArguments.Editor.FCKBrowserInfo.IsIE )\r
+{\r
+       function Window_OnBeforeUnload()\r
+       {\r
+               for ( var t in oTabs )\r
+                       oTabs[t] = null ;\r
+\r
+               window.dialogArguments.Editor = null ;\r
+       }\r
+       window.attachEvent( "onbeforeunload", Window_OnBeforeUnload ) ;\r
+}\r
+\r
+function Window_OnClose()\r
+{\r
+       window.dialogArguments.Editor.FCKFocusManager.Unlock() ;\r
+}\r
+\r
+if ( window.addEventListener )\r
+       window.addEventListener( 'unload', Window_OnClose, false ) ;\r
+\r
+               </script>\r
+       </head>\r
+       <body onload="LoadInnerDialog();" class="PopupBody">\r
+               <table height="100%" cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                       <tr>\r
+                               <td id="TitleArea" class="PopupTitle PopupTitleBorder">\r
+                                       <script type="text/javascript">\r
+document.write( sTitle ) ;\r
+                                       </script>\r
+                               </td>\r
+                       </tr>\r
+                       <tr id="TabsRow" style="DISPLAY: none">\r
+                               <td class="PopupTabArea">\r
+                                       <table border="0" cellpadding="0" cellspacing="0" width="100%">\r
+                                               <tr id="Tabs" onselectstart="return false;">\r
+                                                       <td class="PopupTabEmptyArea">&nbsp;</td>\r
+                                                       <td class="PopupTabEmptyArea" width="100%">&nbsp;</td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td id="FrameCell" height="100%" valign="top">\r
+                                       <iframe id="frmMain" src="javascript:void(0)" name="frmMain" frameborder="0" height="100%" width="100%" scrolling="auto">\r
+                                       </iframe>\r
+                               </td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td class="PopupButtons">\r
+                                       <table border="0" cellpadding="0" cellspacing="0">\r
+                                               <tr>\r
+                                                       <td width="100%">&nbsp;</td>\r
+                                                       <td nowrap="nowrap">\r
+                                                               <input id="btnOk" style="VISIBILITY: hidden;" type="button" value="Ok" class="Button" onclick="Ok();" fckLang="DlgBtnOK" />\r
+                                                               &nbsp; \r
+                                                               <input id="btnCancel" type="button" value="Cancel" class="Button" onclick="Cancel();" fckLang="DlgBtnCancel" />\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/fckeditor.html b/httemplate/elements/fckeditor/editor/fckeditor.html
new file mode 100644 (file)
index 0000000..25ad37e
--- /dev/null
@@ -0,0 +1,227 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Main page that holds the editor.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>FCKeditor</title>\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <meta http-equiv="Cache-Control" content="public" />\r
+       <script type="text/javascript">\r
+\r
+// Instead of loading scripts and CSSs using inline tags, all scripts are\r
+// loaded by code. In this way we can guarantee the correct processing order,\r
+// otherwise external scripts and inline scripts could be executed in an\r
+// unwanted order (IE).\r
+\r
+function LoadScript( url )\r
+{\r
+       document.write( '<scr' + 'ipt type="text/javascript" src="' + url + '" onerror="alert(\'Error loading \' + this.src);"><\/scr' + 'ipt>' ) ;\r
+}\r
+\r
+function LoadCss( url )\r
+{\r
+       document.write( '<link href="' + url + '" type="text/css" rel="stylesheet" onerror="alert(\'Error loading \' + this.src);" />' ) ;\r
+}\r
+\r
+// Main editor scripts.\r
+var sSuffix = /msie/.test( navigator.userAgent.toLowerCase() ) ? 'ie' : 'gecko' ;\r
+\r
+LoadScript( 'js/fckeditorcode_' + sSuffix + '.js' ) ;\r
+\r
+// Base configuration file.\r
+LoadScript( '../fckconfig.js' ) ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+if ( FCKBrowserInfo.IsIE )\r
+{\r
+       // Remove IE mouse flickering.\r
+       try\r
+       {\r
+               document.execCommand( 'BackgroundImageCache', false, true ) ;\r
+       }\r
+       catch (e)\r
+       {\r
+               // We have been reported about loading problems caused by the above\r
+               // line. For safety, let's just ignore errors.\r
+       }\r
+\r
+       // Create the default cleanup object used by the editor.\r
+       FCK.IECleanup = new FCKIECleanup( window ) ;\r
+       FCK.IECleanup.AddItem( FCKTempBin, FCKTempBin.Reset ) ;\r
+       FCK.IECleanup.AddItem( FCK, FCK_Cleanup ) ;\r
+}\r
+\r
+// The config hidden field is processed immediately, because\r
+// CustomConfigurationsPath may be set in the page.\r
+FCKConfig.ProcessHiddenField() ;\r
+\r
+// Load the custom configurations file (if defined).\r
+if ( FCKConfig.CustomConfigurationsPath.length > 0 )\r
+       LoadScript( FCKConfig.CustomConfigurationsPath ) ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+// Load configurations defined at page level.\r
+FCKConfig_LoadPageConfig() ;\r
+\r
+FCKConfig_PreProcess() ;\r
+\r
+// Load the active skin CSS.\r
+LoadCss( FCKConfig.SkinPath + 'fck_editor.css' ) ;\r
+\r
+// Load the language file.\r
+FCKLanguageManager.Initialize() ;\r
+LoadScript( 'lang/' + FCKLanguageManager.ActiveLanguage.Code + '.js' ) ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+// Initialize the editing area context menu.\r
+FCK_ContextMenu_Init() ;\r
+\r
+FCKPlugins.Load() ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+// Set the editor interface direction.\r
+window.document.dir = FCKLang.Dir ;\r
+\r
+// Activate pasting operations.\r
+if ( FCKConfig.ForcePasteAsPlainText || FCKConfig.AutoDetectPasteFromWord )\r
+       FCK.Events.AttachEvent( 'OnPaste', FCK.Paste ) ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+window.onload = function()\r
+{\r
+       InitializeAPI() ;\r
+\r
+       if ( FCKBrowserInfo.IsIE )\r
+               FCK_PreloadImages() ;\r
+       else\r
+               LoadToolbarSetup() ;\r
+}\r
+\r
+function LoadToolbarSetup()\r
+{\r
+       FCKeditorAPI._FunctionQueue.Add( LoadToolbar ) ;\r
+}\r
+\r
+function LoadToolbar()\r
+{\r
+       var oToolbarSet = FCK.ToolbarSet = FCKToolbarSet_Create() ;\r
+\r
+       if ( oToolbarSet.IsLoaded )\r
+               StartEditor() ;\r
+       else\r
+       {\r
+               oToolbarSet.OnLoad = StartEditor ;\r
+               oToolbarSet.Load( FCKURLParams['Toolbar'] || 'Default' ) ;\r
+       }\r
+}\r
+\r
+function StartEditor()\r
+{\r
+       // Remove the onload listener.\r
+       FCK.ToolbarSet.OnLoad = null ;\r
+\r
+       FCKeditorAPI._FunctionQueue.Remove( LoadToolbar ) ;\r
+\r
+       FCK.Events.AttachEvent( 'OnStatusChange', WaitForActive ) ;\r
+\r
+       // Start the editor.\r
+       FCK.StartEditor() ;\r
+}\r
+\r
+function WaitForActive( editorInstance, newStatus )\r
+{\r
+       if ( newStatus == FCK_STATUS_ACTIVE )\r
+       {\r
+               if ( FCKBrowserInfo.IsGecko )\r
+                       FCKTools.RunFunction( window.onresize ) ;\r
+\r
+               _AttachFormSubmitToAPI() ;\r
+\r
+               FCK.SetStatus( FCK_STATUS_COMPLETE ) ;\r
+\r
+               // Call the special "FCKeditor_OnComplete" function that should be present in\r
+               // the HTML page where the editor is located.\r
+               if ( typeof( window.parent.FCKeditor_OnComplete ) == 'function' )\r
+                       window.parent.FCKeditor_OnComplete( FCK ) ;\r
+       }\r
+}\r
+\r
+// Gecko browsers doens't calculate well that IFRAME size so we must\r
+// recalculate it every time the window size changes.\r
+if ( FCKBrowserInfo.IsGecko )\r
+{\r
+       function Window_OnResize()\r
+       {\r
+               if ( FCKBrowserInfo.IsOpera )\r
+                       return ;\r
+\r
+               var oCell = document.getElementById( 'xEditingArea' ) ;\r
+\r
+               var eInnerElement = oCell.firstChild ;\r
+               if ( eInnerElement )\r
+               {\r
+                       eInnerElement.style.height = 0 ;\r
+                       eInnerElement.style.height = oCell.scrollHeight - 2 ;\r
+               }\r
+       }\r
+       window.onresize = Window_OnResize ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body>\r
+       <table width="100%" cellpadding="0" cellspacing="0" style="height: 100%; table-layout: fixed">\r
+               <tr id="xToolbarRow" style="display: none">\r
+                       <td id="xToolbarSpace" style="overflow: hidden">\r
+                               <table width="100%" cellpadding="0" cellspacing="0">\r
+                                       <tr id="xCollapsed" style="display: none">\r
+                                               <td id="xExpandHandle" class="TB_Expand" colspan="3">\r
+                                                       <img class="TB_ExpandImg" alt="" src="images/spacer.gif" width="8" height="4" /></td>\r
+                                       </tr>\r
+                                       <tr id="xExpanded" style="display: none">\r
+                                               <td id="xTBLeftBorder" class="TB_SideBorder" style="width: 1px; display: none;"></td>\r
+                                               <td id="xCollapseHandle" style="display: none" class="TB_Collapse" valign="bottom">\r
+                                                       <img class="TB_CollapseImg" alt="" src="images/spacer.gif" width="8" height="4" /></td>\r
+                                               <td id="xToolbar" class="TB_ToolbarSet"></td>\r
+                                               <td class="TB_SideBorder" style="width: 1px"></td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+               <tr>\r
+                       <td id="xEditingArea" valign="top" style="height: 100%"></td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/fckeditor.original.html b/httemplate/elements/fckeditor/editor/fckeditor.original.html
new file mode 100644 (file)
index 0000000..846eed9
--- /dev/null
@@ -0,0 +1,319 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Main page that holds the editor.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>FCKeditor</title>\r
+       <meta name="robots" content="noindex, nofollow" />\r
+       <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+       <!-- @Packager.RemoveLine\r
+       <meta http-equiv="Cache-Control" content="public" />\r
+       @Packager.RemoveLine -->\r
+       <script type="text/javascript">\r
+\r
+// Instead of loading scripts and CSSs using inline tags, all scripts are\r
+// loaded by code. In this way we can guarantee the correct processing order,\r
+// otherwise external scripts and inline scripts could be executed in an\r
+// unwanted order (IE).\r
+\r
+function LoadScript( url )\r
+{\r
+       document.write( '<scr' + 'ipt type="text/javascript" src="' + url + '" onerror="alert(\'Error loading \' + this.src);"><\/scr' + 'ipt>' ) ;\r
+}\r
+\r
+function LoadCss( url )\r
+{\r
+       document.write( '<link href="' + url + '" type="text/css" rel="stylesheet" onerror="alert(\'Error loading \' + this.src);" />' ) ;\r
+}\r
+\r
+// Main editor scripts.\r
+var sSuffix = /msie/.test( navigator.userAgent.toLowerCase() ) ? 'ie' : 'gecko' ;\r
+\r
+/* @Packager.RemoveLine\r
+LoadScript( 'js/fckeditorcode_' + sSuffix + '.js' ) ;\r
+@Packager.RemoveLine */\r
+// @Packager.Remove.Start\r
+\r
+LoadScript( '_source/fckconstants.js' ) ;\r
+LoadScript( '_source/fckjscoreextensions.js' ) ;\r
+\r
+if ( sSuffix == 'ie' )\r
+       LoadScript( '_source/classes/fckiecleanup.js' ) ;\r
+\r
+LoadScript( '_source/internals/fckbrowserinfo.js' ) ;\r
+LoadScript( '_source/internals/fckurlparams.js' ) ;\r
+LoadScript( '_source/classes/fckevents.js' ) ;\r
+LoadScript( '_source/internals/fck.js' ) ;\r
+LoadScript( '_source/internals/fck_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/internals/fckconfig.js' ) ;\r
+\r
+LoadScript( '_source/internals/fckdebug.js' ) ;\r
+LoadScript( '_source/internals/fckdomtools.js' ) ;\r
+LoadScript( '_source/internals/fcktools.js' ) ;\r
+LoadScript( '_source/internals/fcktools_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/fckeditorapi.js' ) ;\r
+LoadScript( '_source/classes/fckimagepreloader.js' ) ;\r
+LoadScript( '_source/internals/fckregexlib.js' ) ;\r
+LoadScript( '_source/internals/fcklistslib.js' ) ;\r
+LoadScript( '_source/internals/fcklanguagemanager.js' ) ;\r
+LoadScript( '_source/internals/fckxhtmlentities.js' ) ;\r
+LoadScript( '_source/internals/fckxhtml.js' ) ;\r
+LoadScript( '_source/internals/fckxhtml_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/internals/fckcodeformatter.js' ) ;\r
+LoadScript( '_source/internals/fckundo_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/classes/fckeditingarea.js' ) ;\r
+LoadScript( '_source/classes/fckkeystrokehandler.js' ) ;\r
+\r
+LoadScript( '_source/internals/fcklisthandler.js' ) ;\r
+LoadScript( '_source/classes/fckelementpath.js' ) ;\r
+LoadScript( '_source/classes/fckdomrange.js' ) ;\r
+LoadScript( '_source/classes/fckdocumentfragment_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/classes/fckw3crange.js' ) ;\r
+LoadScript( '_source/classes/fckdomrange_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/classes/fckenterkey.js' ) ;\r
+\r
+LoadScript( '_source/internals/fckdocumentprocessor.js' ) ;\r
+LoadScript( '_source/internals/fckselection.js' ) ;\r
+LoadScript( '_source/internals/fckselection_' + sSuffix + '.js' ) ;\r
+\r
+LoadScript( '_source/internals/fcktablehandler.js' ) ;\r
+LoadScript( '_source/internals/fcktablehandler_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/classes/fckxml_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/classes/fckstyledef.js' ) ;\r
+LoadScript( '_source/classes/fckstyledef_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/classes/fckstylesloader.js' ) ;\r
+\r
+LoadScript( '_source/commandclasses/fcknamedcommand.js' ) ;\r
+LoadScript( '_source/commandclasses/fck_othercommands.js' ) ;\r
+LoadScript( '_source/commandclasses/fckspellcheckcommand_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/commandclasses/fcktextcolorcommand.js' ) ;\r
+LoadScript( '_source/commandclasses/fckpasteplaintextcommand.js' ) ;\r
+LoadScript( '_source/commandclasses/fckpastewordcommand.js' ) ;\r
+LoadScript( '_source/commandclasses/fcktablecommand.js' ) ;\r
+LoadScript( '_source/commandclasses/fckstylecommand.js' ) ;\r
+LoadScript( '_source/commandclasses/fckfitwindow.js' ) ;\r
+LoadScript( '_source/internals/fckcommands.js' ) ;\r
+\r
+LoadScript( '_source/classes/fckpanel.js' ) ;\r
+LoadScript( '_source/classes/fckicon.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarbuttonui.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarbutton.js' ) ;\r
+LoadScript( '_source/classes/fckspecialcombo.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarspecialcombo.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarfontscombo.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarfontsizecombo.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarfontformatcombo.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarstylecombo.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarpanelbutton.js' ) ;\r
+LoadScript( '_source/internals/fcktoolbaritems.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbar.js' ) ;\r
+LoadScript( '_source/classes/fcktoolbarbreak_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/internals/fcktoolbarset.js' ) ;\r
+LoadScript( '_source/internals/fckdialog.js' ) ;\r
+LoadScript( '_source/internals/fckdialog_' + sSuffix + '.js' ) ;\r
+LoadScript( '_source/classes/fckmenuitem.js' ) ;\r
+LoadScript( '_source/classes/fckmenublock.js' ) ;\r
+LoadScript( '_source/classes/fckmenublockpanel.js' ) ;\r
+LoadScript( '_source/classes/fckcontextmenu.js' ) ;\r
+LoadScript( '_source/internals/fck_contextmenu.js' ) ;\r
+LoadScript( '_source/classes/fckplugin.js' ) ;\r
+LoadScript( '_source/internals/fckplugins.js' ) ;\r
+\r
+// @Packager.Remove.End\r
+\r
+// Base configuration file.\r
+LoadScript( '../fckconfig.js' ) ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+if ( FCKBrowserInfo.IsIE )\r
+{\r
+       // Remove IE mouse flickering.\r
+       try\r
+       {\r
+               document.execCommand( 'BackgroundImageCache', false, true ) ;\r
+       }\r
+       catch (e)\r
+       {\r
+               // We have been reported about loading problems caused by the above\r
+               // line. For safety, let's just ignore errors.\r
+       }\r
+\r
+       // Create the default cleanup object used by the editor.\r
+       FCK.IECleanup = new FCKIECleanup( window ) ;\r
+       FCK.IECleanup.AddItem( FCKTempBin, FCKTempBin.Reset ) ;\r
+       FCK.IECleanup.AddItem( FCK, FCK_Cleanup ) ;\r
+}\r
+\r
+// The config hidden field is processed immediately, because\r
+// CustomConfigurationsPath may be set in the page.\r
+FCKConfig.ProcessHiddenField() ;\r
+\r
+// Load the custom configurations file (if defined).\r
+if ( FCKConfig.CustomConfigurationsPath.length > 0 )\r
+       LoadScript( FCKConfig.CustomConfigurationsPath ) ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+// Load configurations defined at page level.\r
+FCKConfig_LoadPageConfig() ;\r
+\r
+FCKConfig_PreProcess() ;\r
+\r
+// Load the active skin CSS.\r
+LoadCss( FCKConfig.SkinPath + 'fck_editor.css' ) ;\r
+\r
+// Load the language file.\r
+FCKLanguageManager.Initialize() ;\r
+LoadScript( 'lang/' + FCKLanguageManager.ActiveLanguage.Code + '.js' ) ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+// Initialize the editing area context menu.\r
+FCK_ContextMenu_Init() ;\r
+\r
+FCKPlugins.Load() ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+// Set the editor interface direction.\r
+window.document.dir = FCKLang.Dir ;\r
+\r
+// Activate pasting operations.\r
+if ( FCKConfig.ForcePasteAsPlainText || FCKConfig.AutoDetectPasteFromWord )\r
+       FCK.Events.AttachEvent( 'OnPaste', FCK.Paste ) ;\r
+\r
+       </script>\r
+       <script type="text/javascript">\r
+\r
+window.onload = function()\r
+{\r
+       InitializeAPI() ;\r
+\r
+       if ( FCKBrowserInfo.IsIE )\r
+               FCK_PreloadImages() ;\r
+       else\r
+               LoadToolbarSetup() ;\r
+}\r
+\r
+function LoadToolbarSetup()\r
+{\r
+       FCKeditorAPI._FunctionQueue.Add( LoadToolbar ) ;\r
+}\r
+\r
+function LoadToolbar()\r
+{\r
+       var oToolbarSet = FCK.ToolbarSet = FCKToolbarSet_Create() ;\r
+\r
+       if ( oToolbarSet.IsLoaded )\r
+               StartEditor() ;\r
+       else\r
+       {\r
+               oToolbarSet.OnLoad = StartEditor ;\r
+               oToolbarSet.Load( FCKURLParams['Toolbar'] || 'Default' ) ;\r
+       }\r
+}\r
+\r
+function StartEditor()\r
+{\r
+       // Remove the onload listener.\r
+       FCK.ToolbarSet.OnLoad = null ;\r
+\r
+       FCKeditorAPI._FunctionQueue.Remove( LoadToolbar ) ;\r
+\r
+       FCK.Events.AttachEvent( 'OnStatusChange', WaitForActive ) ;\r
+\r
+       // Start the editor.\r
+       FCK.StartEditor() ;\r
+}\r
+\r
+function WaitForActive( editorInstance, newStatus )\r
+{\r
+       if ( newStatus == FCK_STATUS_ACTIVE )\r
+       {\r
+               if ( FCKBrowserInfo.IsGecko )\r
+                       FCKTools.RunFunction( window.onresize ) ;\r
+\r
+               _AttachFormSubmitToAPI() ;\r
+\r
+               FCK.SetStatus( FCK_STATUS_COMPLETE ) ;\r
+\r
+               // Call the special "FCKeditor_OnComplete" function that should be present in\r
+               // the HTML page where the editor is located.\r
+               if ( typeof( window.parent.FCKeditor_OnComplete ) == 'function' )\r
+                       window.parent.FCKeditor_OnComplete( FCK ) ;\r
+       }\r
+}\r
+\r
+// Gecko browsers doens't calculate well that IFRAME size so we must\r
+// recalculate it every time the window size changes.\r
+if ( FCKBrowserInfo.IsGecko )\r
+{\r
+       function Window_OnResize()\r
+       {\r
+               if ( FCKBrowserInfo.IsOpera )\r
+                       return ;\r
+\r
+               var oCell = document.getElementById( 'xEditingArea' ) ;\r
+\r
+               var eInnerElement = oCell.firstChild ;\r
+               if ( eInnerElement )\r
+               {\r
+                       eInnerElement.style.height = 0 ;\r
+                       eInnerElement.style.height = oCell.scrollHeight - 2 ;\r
+               }\r
+       }\r
+       window.onresize = Window_OnResize ;\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body>\r
+       <table width="100%" cellpadding="0" cellspacing="0" style="height: 100%; table-layout: fixed">\r
+               <tr id="xToolbarRow" style="display: none">\r
+                       <td id="xToolbarSpace" style="overflow: hidden">\r
+                               <table width="100%" cellpadding="0" cellspacing="0">\r
+                                       <tr id="xCollapsed" style="display: none">\r
+                                               <td id="xExpandHandle" class="TB_Expand" colspan="3">\r
+                                                       <img class="TB_ExpandImg" alt="" src="images/spacer.gif" width="8" height="4" /></td>\r
+                                       </tr>\r
+                                       <tr id="xExpanded" style="display: none">\r
+                                               <td id="xTBLeftBorder" class="TB_SideBorder" style="width: 1px; display: none;"></td>\r
+                                               <td id="xCollapseHandle" style="display: none" class="TB_Collapse" valign="bottom">\r
+                                                       <img class="TB_CollapseImg" alt="" src="images/spacer.gif" width="8" height="4" /></td>\r
+                                               <td id="xToolbar" class="TB_ToolbarSet"></td>\r
+                                               <td class="TB_SideBorder" style="width: 1px"></td>\r
+                                       </tr>\r
+                               </table>\r
+                       </td>\r
+               </tr>\r
+               <tr>\r
+                       <td id="xEditingArea" valign="top" style="height: 100%"></td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
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 (file)
index 0000000..ba464ba
--- /dev/null
@@ -0,0 +1,88 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * CSS styles used by all pages that compose the File Browser.\r
+ */\r
+\r
+body\r
+{\r
+       background-color: #f1f1e3;\r
+}\r
+\r
+form\r
+{\r
+       margin: 0px 0px 0px 0px ;\r
+       padding: 0px 0px 0px 0px ;\r
+}\r
+\r
+.Frame\r
+{\r
+       background-color: #f1f1e3;\r
+       border-color: #f1f1e3;\r
+       border-right: thin inset;\r
+       border-top: thin inset;\r
+       border-left: thin inset;\r
+       border-bottom: thin inset;\r
+}\r
+\r
+body.FileArea\r
+{\r
+\r
+       background-color: #ffffff;\r
+}\r
+\r
+body, td, input, select\r
+{\r
+       font-size: 11px;\r
+       font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;\r
+}\r
+\r
+.ActualFolder\r
+{\r
+       font-weight: bold;\r
+       font-size: 14px;\r
+}\r
+\r
+.PopupButtons\r
+{\r
+       border-top: #d5d59d 1px solid;\r
+       background-color: #e3e3c7;\r
+       padding: 7px 10px 7px 10px;\r
+}\r
+\r
+.Button, button\r
+{\r
+       border-right: #737357 1px solid;\r
+       border-top: #737357 1px solid;\r
+       border-left: #737357 1px solid;\r
+       color: #3b3b1f;\r
+       border-bottom: #737357 1px solid;\r
+       background-color: #c7c78f;\r
+}\r
+\r
+.FolderListCurrentFolder img\r
+{\r
+       background-image: url(images/FolderOpened.gif);\r
+}\r
+\r
+.FolderListFolder img\r
+{\r
+       background-image: url(images/Folder.gif);\r
+}
\ No newline at end of file
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 (file)
index 0000000..8b776a2
--- /dev/null
@@ -0,0 +1,154 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This page compose the File Browser dialog frameset.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>FCKeditor - Resources Browser</title>\r
+               <link href="browser.css" type="text/css" rel="stylesheet">\r
+               <script type="text/javascript" src="js/fckxml.js"></script>\r
+               <script language="javascript">\r
+\r
+function GetUrlParam( paramName )\r
+{\r
+       var oRegex = new RegExp( '[\?&]' + paramName + '=([^&]+)', 'i' ) ;\r
+       var oMatch = oRegex.exec( window.top.location.search ) ;\r
+\r
+       if ( oMatch && oMatch.length > 1 )\r
+               return decodeURIComponent( oMatch[1] ) ;\r
+       else\r
+               return '' ;\r
+}\r
+\r
+var oConnector = new Object() ;\r
+oConnector.CurrentFolder       = '/' ;\r
+\r
+var sConnUrl = GetUrlParam( 'Connector' ) ;\r
+\r
+// Gecko has some problems when using relative URLs (not starting with slash).\r
+if ( sConnUrl.substr(0,1) != '/' && sConnUrl.indexOf( '://' ) < 0 )\r
+       sConnUrl = window.location.href.replace( /browser.html.*$/, '' ) + sConnUrl ;\r
+\r
+oConnector.ConnectorUrl = sConnUrl + ( sConnUrl.indexOf('?') != -1 ? '&' : '?' ) ;\r
+\r
+var sServerPath = GetUrlParam( 'ServerPath' ) ;\r
+if ( sServerPath.length > 0 )\r
+       oConnector.ConnectorUrl += 'ServerPath=' + encodeURIComponent( sServerPath ) + '&' ;\r
+\r
+oConnector.ResourceType                = GetUrlParam( 'Type' ) ;\r
+oConnector.ShowAllTypes                = ( oConnector.ResourceType.length == 0 ) ;\r
+\r
+if ( oConnector.ShowAllTypes )\r
+       oConnector.ResourceType = 'File' ;\r
+\r
+oConnector.SendCommand = function( command, params, callBackFunction )\r
+{\r
+       var sUrl = this.ConnectorUrl + 'Command=' + command ;\r
+       sUrl += '&Type=' + this.ResourceType ;\r
+       sUrl += '&CurrentFolder=' + encodeURIComponent( this.CurrentFolder ) ;\r
+\r
+       if ( params ) sUrl += '&' + params ;\r
+\r
+       var oXML = new FCKXml() ;\r
+\r
+       if ( callBackFunction )\r
+               oXML.LoadUrl( sUrl, callBackFunction ) ;        // Asynchronous load.\r
+       else\r
+               return oXML.LoadUrl( sUrl ) ;\r
+\r
+       return null ;\r
+}\r
+\r
+oConnector.CheckError = function( responseXml )\r
+{\r
+       var iErrorNumber = 0 ;\r
+       var oErrorNode = responseXml.SelectSingleNode( 'Connector/Error' ) ;\r
+\r
+       if ( oErrorNode )\r
+       {\r
+               iErrorNumber = parseInt( oErrorNode.attributes.getNamedItem('number').value, 10 ) ;\r
+\r
+               switch ( iErrorNumber )\r
+               {\r
+                       case 0 :\r
+                               break ;\r
+                       case 1 :        // Custom error. Message placed in the "text" attribute.\r
+                               alert( oErrorNode.attributes.getNamedItem('text').value ) ;\r
+                               break ;\r
+                       case 101 :\r
+                               alert( 'Folder already exists' ) ;\r
+                               break ;\r
+                       case 102 :\r
+                               alert( 'Invalid folder name' ) ;\r
+                               break ;\r
+                       case 103 :\r
+                               alert( 'You have no permissions to create the folder' ) ;\r
+                               break ;\r
+                       case 110 :\r
+                               alert( 'Unknown error creating folder' ) ;\r
+                               break ;\r
+                       default :\r
+                               alert( 'Error on your request. Error number: ' + iErrorNumber ) ;\r
+                               break ;\r
+               }\r
+       }\r
+       return iErrorNumber ;\r
+}\r
+\r
+var oIcons = new Object() ;\r
+\r
+oIcons.AvailableIconsArray = [\r
+       'ai','avi','bmp','cs','dll','doc','exe','fla','gif','htm','html','jpg','js',\r
+       'mdb','mp3','pdf','png','ppt','rdp','swf','swt','txt','vsd','xls','xml','zip' ] ;\r
+\r
+oIcons.AvailableIcons = new Object() ;\r
+\r
+for ( var i = 0 ; i < oIcons.AvailableIconsArray.length ; i++ )\r
+       oIcons.AvailableIcons[ oIcons.AvailableIconsArray[i] ] = true ;\r
+\r
+oIcons.GetIcon = function( fileName )\r
+{\r
+       var sExtension = fileName.substr( fileName.lastIndexOf('.') + 1 ).toLowerCase() ;\r
+\r
+       if ( this.AvailableIcons[ sExtension ] == true )\r
+               return sExtension ;\r
+       else\r
+               return 'default.icon' ;\r
+}\r
+               </script>\r
+       </head>\r
+       <frameset cols="150,*" class="Frame" framespacing="3" bordercolor="#f1f1e3" frameborder="1">\r
+               <frameset rows="50,*" framespacing="0">\r
+                       <frame src="frmresourcetype.html" scrolling="no" frameborder="0">\r
+                       <frame name="frmFolders" src="frmfolders.html" scrolling="auto" frameborder="1">\r
+               </frameset>\r
+               <frameset rows="50,*,50" framespacing="0">\r
+                       <frame name="frmActualFolder" src="frmactualfolder.html" scrolling="no" frameborder="0">\r
+                       <frame name="frmResourcesList" src="frmresourceslist.html" scrolling="auto" frameborder="1">\r
+                       <frameset cols="150,*,0" framespacing="0" frameborder="0">\r
+                               <frame name="frmCreateFolder" src="frmcreatefolder.html" scrolling="no" frameborder="0">\r
+                               <frame name="frmUpload" src="frmupload.html" scrolling="no" frameborder="0">\r
+                               <frame name="frmUploadWorker" src="javascript:void(0)" scrolling="no" frameborder="0">\r
+                       </frameset>\r
+               </frameset>\r
+       </frameset>\r
+</html>\r
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 (file)
index 0000000..f64b7c7
--- /dev/null
@@ -0,0 +1,63 @@
+#####\r
+#  FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+#  Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+#\r
+#  == BEGIN LICENSE ==\r
+#\r
+#  Licensed under the terms of any of the following licenses at your\r
+#  choice:\r
+#\r
+#   - GNU General Public License Version 2 or later (the "GPL")\r
+#     http://www.gnu.org/licenses/gpl.html\r
+#\r
+#   - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+#     http://www.gnu.org/licenses/lgpl.html\r
+#\r
+#   - Mozilla Public License Version 1.1 or later (the "MPL")\r
+#     http://www.mozilla.org/MPL/MPL-1.1.html\r
+#\r
+#  == END LICENSE ==\r
+#\r
+#  This is the File Manager Connector for Perl.\r
+#####\r
+\r
+sub CreateXmlHeader\r
+{\r
+       local($command,$resourceType,$currentFolder) = @_;\r
+\r
+       # Create the XML document header.\r
+       print '<?xml version="1.0" encoding="utf-8" ?>';\r
+\r
+       # Create the main "Connector" node.\r
+       print '<Connector command="' . $command . '" resourceType="' . $resourceType . '">';\r
+\r
+       # Add the current folder node.\r
+       print '<CurrentFolder path="' . ConvertToXmlAttribute($currentFolder) . '" url="' . ConvertToXmlAttribute(GetUrlFromPath($resourceType,$currentFolder)) . '" />';\r
+}\r
+\r
+sub CreateXmlFooter\r
+{\r
+       print '</Connector>';\r
+}\r
+\r
+sub SendError\r
+{\r
+       local( $number, $text ) = @_;\r
+\r
+       print << "_HTML_HEAD_";\r
+Content-Type:text/xml; charset=utf-8\r
+Pragma: no-cache\r
+Cache-Control: no-cache\r
+Expires: Thu, 01 Dec 1994 16:00:00 GMT\r
+\r
+_HTML_HEAD_\r
+\r
+       # Create the XML document header\r
+       print '<?xml version="1.0" encoding="utf-8" ?>' ;\r
+\r
+       print '<Connector><Error number="' . $number . '" text="' . &specialchar_cnv( $text ) . '" /></Connector>' ;\r
+\r
+       exit ;\r
+}\r
+\r
+1;\r
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 (file)
index 0000000..2ed2e62
--- /dev/null
@@ -0,0 +1,158 @@
+#####\r
+#  FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+#  Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+#\r
+#  == BEGIN LICENSE ==\r
+#\r
+#  Licensed under the terms of any of the following licenses at your\r
+#  choice:\r
+#\r
+#   - GNU General Public License Version 2 or later (the "GPL")\r
+#     http://www.gnu.org/licenses/gpl.html\r
+#\r
+#   - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+#     http://www.gnu.org/licenses/lgpl.html\r
+#\r
+#   - Mozilla Public License Version 1.1 or later (the "MPL")\r
+#     http://www.mozilla.org/MPL/MPL-1.1.html\r
+#\r
+#  == END LICENSE ==\r
+#\r
+#  This is the File Manager Connector for Perl.\r
+#####\r
+\r
+sub GetFolders\r
+{\r
+\r
+       local($resourceType, $currentFolder) = @_;\r
+\r
+       # Map the virtual path to the local server path.\r
+       $sServerDir = &ServerMapFolder($resourceType, $currentFolder);\r
+       print "<Folders>";                      # Open the "Folders" node.\r
+\r
+       opendir(DIR,"$sServerDir");\r
+       @files = grep(!/^\.\.?$/,readdir(DIR));\r
+       closedir(DIR);\r
+\r
+       foreach $sFile (@files) {\r
+               if($sFile != '.' && $sFile != '..' && (-d "$sServerDir$sFile")) {\r
+                       $cnv_filename = &ConvertToXmlAttribute($sFile);\r
+                       print '<Folder name="' . $cnv_filename . '" />';\r
+               }\r
+       }\r
+       print "</Folders>";                     # Close the "Folders" node.\r
+}\r
+\r
+sub GetFoldersAndFiles\r
+{\r
+\r
+       local($resourceType, $currentFolder) = @_;\r
+       # Map the virtual path to the local server path.\r
+       $sServerDir = &ServerMapFolder($resourceType,$currentFolder);\r
+\r
+       # Initialize the output buffers for "Folders" and "Files".\r
+       $sFolders       = '<Folders>';\r
+       $sFiles         = '<Files>';\r
+\r
+       opendir(DIR,"$sServerDir");\r
+       @files = grep(!/^\.\.?$/,readdir(DIR));\r
+       closedir(DIR);\r
+\r
+       foreach $sFile (@files) {\r
+               if($sFile ne '.' && $sFile ne '..') {\r
+                       if(-d "$sServerDir$sFile") {\r
+                               $cnv_filename = &ConvertToXmlAttribute($sFile);\r
+                               $sFolders .= '<Folder name="' . $cnv_filename . '" />' ;\r
+                       } else {\r
+                               ($iFileSize,$refdate,$filedate,$fileperm) = (stat("$sServerDir$sFile"))[7,8,9,2];\r
+                               if($iFileSize > 0) {\r
+                                       $iFileSize = int($iFileSize / 1024);\r
+                                       if($iFileSize < 1) {\r
+                                               $iFileSize = 1;\r
+                                       }\r
+                               }\r
+                               $cnv_filename = &ConvertToXmlAttribute($sFile);\r
+                               $sFiles .= '<File name="' . $cnv_filename . '" size="' . $iFileSize . '" />' ;\r
+                       }\r
+               }\r
+       }\r
+       print $sFolders ;\r
+       print '</Folders>';                     # Close the "Folders" node.\r
+       print $sFiles ;\r
+       print '</Files>';                       # Close the "Files" node.\r
+}\r
+\r
+sub CreateFolder\r
+{\r
+\r
+       local($resourceType, $currentFolder) = @_;\r
+       $sErrorNumber   = '0' ;\r
+       $sErrorMsg              = '' ;\r
+\r
+       if($FORM{'NewFolderName'} ne "") {\r
+               $sNewFolderName = $FORM{'NewFolderName'};\r
+               # Map the virtual path to the local server path of the current folder.\r
+               $sServerDir = &ServerMapFolder($resourceType, $currentFolder);\r
+               if(-w $sServerDir) {\r
+                       $sServerDir .= $sNewFolderName;\r
+                       $sErrorMsg = &CreateServerFolder($sServerDir);\r
+                       if($sErrorMsg == 0) {\r
+                               $sErrorNumber = '0';\r
+                       } elsif($sErrorMsg eq 'Invalid argument' || $sErrorMsg eq 'No such file or directory') {\r
+                               $sErrorNumber = '102';          #// Path too long.\r
+                       } else {\r
+                               $sErrorNumber = '110';\r
+                       }\r
+               } else {\r
+                       $sErrorNumber = '103';\r
+               }\r
+       } else {\r
+               $sErrorNumber = '102' ;\r
+       }\r
+       # Create the "Error" node.\r
+       $cnv_errmsg = &ConvertToXmlAttribute($sErrorMsg);\r
+       print '<Error number="' . $sErrorNumber . '" originalDescription="' . $cnv_errmsg . '" />';\r
+}\r
+\r
+sub FileUpload\r
+{\r
+eval("use File::Copy;");\r
+\r
+       local($resourceType, $currentFolder) = @_;\r
+\r
+       $sErrorNumber = '0' ;\r
+       $sFileName = '' ;\r
+       if($new_fname) {\r
+               # Map the virtual path to the local server path.\r
+               $sServerDir = &ServerMapFolder($resourceType,$currentFolder);\r
+\r
+               # Get the uploaded file name.\r
+               $sFileName = $new_fname;\r
+               $sOriginalFileName = $sFileName;\r
+\r
+               $iCounter = 0;\r
+               while(1) {\r
+                       $sFilePath = $sServerDir . $sFileName;\r
+                       if(-e $sFilePath) {\r
+                               $iCounter++ ;\r
+                               ($path,$BaseName,$ext) = &RemoveExtension($sOriginalFileName);\r
+                               $sFileName = $BaseName . '(' . $iCounter . ').' . $ext;\r
+                               $sErrorNumber = '201';\r
+                       } else {\r
+                               copy("$img_dir/$new_fname","$sFilePath");\r
+                               chmod(0777,$sFilePath);\r
+                               unlink("$img_dir/$new_fname");\r
+                               last;\r
+                       }\r
+               }\r
+       } else {\r
+               $sErrorNumber = '202' ;\r
+       }\r
+       $sFileName      =~ s/"/\\"/g;\r
+       print "Content-type: text/html\n\n";\r
+       print '<script type="text/javascript">';\r
+       print 'window.parent.frames["frmUpload"].OnUploadCompleted(' . $sErrorNumber . ',"' . $sFileName . '") ;';\r
+       print '</script>';\r
+       exit ;\r
+}\r
+1;\r
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 (file)
index 0000000..a741215
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/env perl\r
+\r
+#####\r
+#  FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+#  Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+#\r
+#  == BEGIN LICENSE ==\r
+#\r
+#  Licensed under the terms of any of the following licenses at your\r
+#  choice:\r
+#\r
+#   - GNU General Public License Version 2 or later (the "GPL")\r
+#     http://www.gnu.org/licenses/gpl.html\r
+#\r
+#   - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+#     http://www.gnu.org/licenses/lgpl.html\r
+#\r
+#   - Mozilla Public License Version 1.1 or later (the "MPL")\r
+#     http://www.mozilla.org/MPL/MPL-1.1.html\r
+#\r
+#  == END LICENSE ==\r
+#\r
+#  This is the File Manager Connector for Perl.\r
+#####\r
+\r
+##\r
+# ATTENTION: To enable this connector, look for the "SECURITY" comment in this file.\r
+##\r
+\r
+## START: Hack for Windows (Not important to understand the editor code... Perl specific).\r
+if(Windows_check()) {\r
+       chdir(GetScriptPath($0));\r
+}\r
+\r
+sub Windows_check\r
+{\r
+       # IIS,PWS(NT/95)\r
+       $www_server_os = $^O;\r
+       # Win98 & NT(SP4)\r
+       if($www_server_os eq "") { $www_server_os= $ENV{'OS'}; }\r
+       # AnHTTPd/Omni/IIS\r
+       if($ENV{'SERVER_SOFTWARE'} =~ /AnWeb|Omni|IIS\//i) { $www_server_os= 'win'; }\r
+       # Win Apache\r
+       if($ENV{'WINDIR'} ne "") { $www_server_os= 'win'; }\r
+       if($www_server_os=~ /win/i) { return(1); }\r
+       return(0);\r
+}\r
+\r
+sub GetScriptPath {\r
+       local($path) = @_;\r
+       if($path =~ /[\:\/\\]/) { $path =~ s/(.*?)[\/\\][^\/\\]+$/$1/; } else { $path = '.'; }\r
+       $path;\r
+}\r
+## END: Hack for IIS\r
+\r
+require 'util.pl';\r
+require 'io.pl';\r
+require 'basexml.pl';\r
+require 'commands.pl';\r
+require 'upload_fck.pl';\r
+\r
+##\r
+# SECURITY: REMOVE/COMMENT THE FOLLOWING LINE TO ENABLE THIS CONNECTOR.\r
+##\r
+&SendError( 1, 'This connector is disabled. Please check the "editor/filemanager/browser/default/connectors/perl/connector.cgi" file' ) ;\r
+\r
+       &read_input();\r
+\r
+       if($FORM{'ServerPath'} ne "") {\r
+               $GLOBALS{'UserFilesPath'} = $FORM{'ServerPath'};\r
+               if(!($GLOBALS{'UserFilesPath'} =~ /\/$/)) {\r
+                       $GLOBALS{'UserFilesPath'} .= '/' ;\r
+               }\r
+       } else {\r
+               $GLOBALS{'UserFilesPath'} = '/userfiles/';\r
+       }\r
+\r
+       # Map the "UserFiles" path to a local directory.\r
+       $rootpath = &GetRootPath();\r
+       $GLOBALS{'UserFilesDirectory'} = $rootpath . $GLOBALS{'UserFilesPath'};\r
+\r
+       &DoResponse();\r
+\r
+sub DoResponse\r
+{\r
+\r
+       if($FORM{'Command'} eq "" || $FORM{'Type'} eq "" || $FORM{'CurrentFolder'} eq "") {\r
+               return ;\r
+       }\r
+       # Get the main request informaiton.\r
+       $sCommand               = $FORM{'Command'};\r
+       $sResourceType  = $FORM{'Type'};\r
+       $sCurrentFolder = $FORM{'CurrentFolder'};\r
+\r
+       # Check the current folder syntax (must begin and start with a slash).\r
+       if(!($sCurrentFolder =~ /\/$/)) {\r
+               $sCurrentFolder .= '/';\r
+       }\r
+       if(!($sCurrentFolder =~ /^\//)) {\r
+               $sCurrentFolder = '/' . $sCurrentFolder;\r
+       }\r
+\r
+       # Check for invalid folder paths (..)\r
+       if ( $sCurrentFolder =~ /\.\./ ) {\r
+               SendError( 102, "" ) ;\r
+       }\r
+\r
+       # File Upload doesn't have to Return XML, so it must be intercepted before anything.\r
+       if($sCommand eq 'FileUpload') {\r
+               FileUpload($sResourceType,$sCurrentFolder);\r
+               return ;\r
+       }\r
+\r
+       print << "_HTML_HEAD_";\r
+Content-Type:text/xml; charset=utf-8\r
+Pragma: no-cache\r
+Cache-Control: no-cache\r
+Expires: Thu, 01 Dec 1994 16:00:00 GMT\r
+\r
+_HTML_HEAD_\r
+\r
+       &CreateXmlHeader($sCommand,$sResourceType,$sCurrentFolder);\r
+\r
+       # Execute the required command.\r
+       if($sCommand eq 'GetFolders') {\r
+               &GetFolders($sResourceType,$sCurrentFolder);\r
+       } elsif($sCommand eq 'GetFoldersAndFiles') {\r
+               &GetFoldersAndFiles($sResourceType,$sCurrentFolder);\r
+       } elsif($sCommand eq 'CreateFolder') {\r
+               &CreateFolder($sResourceType,$sCurrentFolder);\r
+       }\r
+\r
+       &CreateXmlFooter();\r
+\r
+       exit ;\r
+}\r
+\r
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 (file)
index 0000000..c1dbccf
--- /dev/null
@@ -0,0 +1,131 @@
+#####\r
+#  FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+#  Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+#\r
+#  == BEGIN LICENSE ==\r
+#\r
+#  Licensed under the terms of any of the following licenses at your\r
+#  choice:\r
+#\r
+#   - GNU General Public License Version 2 or later (the "GPL")\r
+#     http://www.gnu.org/licenses/gpl.html\r
+#\r
+#   - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+#     http://www.gnu.org/licenses/lgpl.html\r
+#\r
+#   - Mozilla Public License Version 1.1 or later (the "MPL")\r
+#     http://www.mozilla.org/MPL/MPL-1.1.html\r
+#\r
+#  == END LICENSE ==\r
+#\r
+#  This is the File Manager Connector for Perl.\r
+#####\r
+\r
+sub GetUrlFromPath\r
+{\r
+       local($resourceType, $folderPath) = @_;\r
+\r
+       if($resourceType eq '') {\r
+               $rmpath = &RemoveFromEnd($GLOBALS{'UserFilesPath'},'/');\r
+               return("$rmpath$folderPath");\r
+       } else {\r
+               return("$GLOBALS{'UserFilesPath'}$resourceType$folderPath");\r
+       }\r
+}\r
+\r
+sub RemoveExtension\r
+{\r
+       local($fileName) = @_;\r
+       local($path, $base, $ext);\r
+       if($fileName !~ /\./) {\r
+               $fileName .= '.';\r
+       }\r
+       if($fileName =~ /([^\\\/]*)\.(.*)$/) {\r
+               $base = $1;\r
+               $ext  = $2;\r
+               if($fileName =~ /(.*)$base\.$ext$/) {\r
+                       $path = $1;\r
+               }\r
+       }\r
+       return($path,$base,$ext);\r
+\r
+}\r
+\r
+sub ServerMapFolder\r
+{\r
+       local($resourceType,$folderPath) = @_;\r
+\r
+       # Get the resource type directory.\r
+       $sResourceTypePath = $GLOBALS{'UserFilesDirectory'} . $resourceType . '/';\r
+\r
+       # Ensure that the directory exists.\r
+       &CreateServerFolder($sResourceTypePath);\r
+\r
+       # Return the resource type directory combined with the required path.\r
+       $rmpath = &RemoveFromStart($folderPath,'/');\r
+       return("$sResourceTypePath$rmpath");\r
+}\r
+\r
+sub GetParentFolder\r
+{\r
+       local($folderPath) = @_;\r
+\r
+       $folderPath =~ s/[\/][^\/]+[\/]?$//g;\r
+       return $folderPath;\r
+}\r
+\r
+sub CreateServerFolder\r
+{\r
+       local($folderPath) = @_;\r
+\r
+       $sParent = &GetParentFolder($folderPath);\r
+       # Check if the parent exists, or create it.\r
+       if(!(-e $sParent)) {\r
+               $sErrorMsg = &CreateServerFolder($sParent);\r
+               if($sErrorMsg == 1) {\r
+                       return(1);\r
+               }\r
+       }\r
+       if(!(-e $folderPath)) {\r
+               umask(000);\r
+               mkdir("$folderPath",0777);\r
+               chmod(0777,"$folderPath");\r
+               return(0);\r
+       } else {\r
+               return(1);\r
+       }\r
+}\r
+\r
+sub GetRootPath\r
+{\r
+#use Cwd;\r
+\r
+#      my $dir = getcwd;\r
+#      print $dir;\r
+#      $dir  =~ s/$ENV{'DOCUMENT_ROOT'}//g;\r
+#      print $dir;\r
+#      return($dir);\r
+\r
+#      $wk = $0;\r
+#      $wk =~ s/\/connector\.cgi//g;\r
+#      if($wk) {\r
+#              $current_dir = $wk;\r
+#      } else {\r
+#              $current_dir = `pwd`;\r
+#      }\r
+#      return($current_dir);\r
+use Cwd;\r
+\r
+       if($ENV{'DOCUMENT_ROOT'}) {\r
+               $dir = $ENV{'DOCUMENT_ROOT'};\r
+       } else {\r
+               my $dir = getcwd;\r
+               $workdir =~ s/\/connector\.cgi//g;\r
+               $dir  =~ s/$workdir//g;\r
+       }\r
+       return($dir);\r
+\r
+\r
+\r
+}\r
+1;\r
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 (file)
index 0000000..1c3f4e2
--- /dev/null
@@ -0,0 +1,667 @@
+#####\r
+#  FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+#  Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+#\r
+#  == BEGIN LICENSE ==\r
+#\r
+#  Licensed under the terms of any of the following licenses at your\r
+#  choice:\r
+#\r
+#   - GNU General Public License Version 2 or later (the "GPL")\r
+#     http://www.gnu.org/licenses/gpl.html\r
+#\r
+#   - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+#     http://www.gnu.org/licenses/lgpl.html\r
+#\r
+#   - Mozilla Public License Version 1.1 or later (the "MPL")\r
+#     http://www.mozilla.org/MPL/MPL-1.1.html\r
+#\r
+#  == END LICENSE ==\r
+#\r
+#  This is the File Manager Connector for Perl.\r
+#####\r
+\r
+# image data save dir\r
+$img_dir       = './temp/';\r
+\r
+\r
+# File size max(unit KB)\r
+$MAX_CONTENT_SIZE =  30000;\r
+\r
+# Filelock (1=use,0=not use)\r
+$PM{'flock'}           = '1';\r
+\r
+\r
+# upload Content-Type list\r
+my %UPLOAD_CONTENT_TYPE_LIST = (\r
+       'image/(x-)?png'                                                =>      'png',  # PNG image\r
+       'image/p?jpe?g'                                                 =>      'jpg',  # JPEG image\r
+       'image/gif'                                                             =>      'gif',  # GIF image\r
+       'image/x-xbitmap'                                               =>      'xbm',  # XBM image\r
+\r
+       'image/(x-(MS-)?)?bmp'                                  =>      'bmp',  # Windows BMP image\r
+       'image/pict'                                                    =>      'pict', # Macintosh PICT image\r
+       'image/tiff'                                                    =>      'tif',  # TIFF image\r
+       'application/pdf'                                               =>      'pdf',  # PDF image\r
+       'application/x-shockwave-flash'                 =>      'swf',  # Shockwave Flash\r
+\r
+       'video/(x-)?msvideo'                                    =>      'avi',  # Microsoft Video\r
+       'video/quicktime'                                               =>      'mov',  # QuickTime Video\r
+       'video/mpeg'                                                    =>      'mpeg', # MPEG Video\r
+       'video/x-mpeg2'                                                 =>      'mpv2', # MPEG2 Video\r
+\r
+       'audio/(x-)?midi?'                                              =>      'mid',  # MIDI Audio\r
+       'audio/(x-)?wav'                                                =>      'wav',  # WAV Audio\r
+       'audio/basic'                                                   =>      'au',   # ULAW Audio\r
+       'audio/mpeg'                                                    =>      'mpga', # MPEG Audio\r
+\r
+       'application/(x-)?zip(-compressed)?'    =>      'zip',  # ZIP Compress\r
+\r
+       'text/html'                                                             =>      'html', # HTML\r
+       'text/plain'                                                    =>      'txt',  # TEXT\r
+       '(?:application|text)/(?:rtf|richtext)' =>      'rtf',  # RichText\r
+\r
+       'application/msword'                                    =>      'doc',  # Microsoft Word\r
+       'application/vnd.ms-excel'                              =>      'xls',  # Microsoft Excel\r
+\r
+       ''\r
+);\r
+\r
+# Upload is permitted.\r
+# A regular expression is possible.\r
+my %UPLOAD_EXT_LIST = (\r
+       'png'                                   =>      'PNG image',\r
+       'p?jpe?g|jpe|jfif|pjp'  =>      'JPEG image',\r
+       'gif'                                   =>      'GIF image',\r
+       'xbm'                                   =>      'XBM image',\r
+\r
+       'bmp|dib|rle'                   =>      'Windows BMP image',\r
+       'pi?ct'                                 =>      'Macintosh PICT image',\r
+       'tiff?'                                 =>      'TIFF image',\r
+       'pdf'                                   =>      'PDF image',\r
+       'swf'                                   =>      'Shockwave Flash',\r
+\r
+       'avi'                                   =>      'Microsoft Video',\r
+       'moo?v|qt'                              =>      'QuickTime Video',\r
+       'm(p(e?gv?|e|v)|1v)'    =>      'MPEG Video',\r
+       'mp(v2|2v)'                             =>      'MPEG2 Video',\r
+\r
+       'midi?|kar|smf|rmi|mff' =>      'MIDI Audio',\r
+       'wav'                                   =>      'WAVE Audio',\r
+       'au|snd'                                =>      'ULAW Audio',\r
+       'mp(e?ga|2|a|3)|abs'    =>      'MPEG Audio',\r
+\r
+       'zip'                                   =>      'ZIP Compress',\r
+       'lzh'                                   =>      'LZH Compress',\r
+       'cab'                                   =>      'CAB Compress',\r
+\r
+       'd?html?'                               =>      'HTML',\r
+       'rtf|rtx'                               =>      'RichText',\r
+       'txt|text'                              =>      'Text',\r
+\r
+       ''\r
+);\r
+\r
+\r
+# sjis or euc\r
+my $CHARCODE = 'sjis';\r
+\r
+$TRANS_2BYTE_CODE = 0;\r
+\r
+##############################################################################\r
+# Summary\r
+#\r
+# Form Read input\r
+#\r
+# Parameters\r
+# Returns\r
+# Memo\r
+##############################################################################\r
+sub read_input\r
+{\r
+eval("use File::Copy;");\r
+eval("use File::Path;");\r
+\r
+       my ($FORM) = @_;\r
+\r
+\r
+       mkdir($img_dir,0777);\r
+       chmod(0777,$img_dir);\r
+\r
+       undef $img_data_exists;\r
+       undef @NEWFNAMES;\r
+       undef @NEWFNAME_DATA;\r
+\r
+       if($ENV{'CONTENT_LENGTH'} > 10000000 || $ENV{'CONTENT_LENGTH'} > $MAX_CONTENT_SIZE * 1024) {\r
+               &upload_error(\r
+                       'Size Error',\r
+                       sprintf(\r
+                               "Transmitting size is too large.MAX <strong>%d KB</strong> Now Size <strong>%d KB</strong>(<strong>%d bytes</strong> Over)",\r
+                               $MAX_CONTENT_SIZE,\r
+                               int($ENV{'CONTENT_LENGTH'} / 1024),\r
+                               $ENV{'CONTENT_LENGTH'} - $MAX_CONTENT_SIZE * 1024\r
+                       )\r
+               );\r
+       }\r
+\r
+       my $Buffer;\r
+       if($ENV{'CONTENT_TYPE'} =~ /multipart\/form-data/) {\r
+               # METHOD POST only\r
+               return  unless($ENV{'CONTENT_LENGTH'});\r
+\r
+               binmode(STDIN);\r
+               # STDIN A pause character is detected.'(MacIE3.0 boundary of $ENV{'CONTENT_TYPE'} cannot be trusted.)\r
+               my $Boundary = <STDIN>;\r
+               $Boundary =~ s/\x0D\x0A//;\r
+               $Boundary = quotemeta($Boundary);\r
+               while(<STDIN>) {\r
+                       if(/^\s*Content-Disposition:/i) {\r
+                               my($name,$ContentType,$FileName);\r
+                               # form data get\r
+                               if(/\bname="([^"]+)"/i || /\bname=([^\s:;]+)/i) {\r
+                                       $name = $1;\r
+                                       $name   =~ tr/+/ /;\r
+                                       $name   =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;\r
+                                       &Encode(\$name);\r
+                               }\r
+                               if(/\bfilename="([^"]*)"/i || /\bfilename=([^\s:;]*)/i) {\r
+                                       $FileName = $1 || 'unknown';\r
+                               }\r
+                               # head read\r
+                               while(<STDIN>) {\r
+                                       last    if(! /\w/);\r
+                                       if(/^\s*Content-Type:\s*"([^"]+)"/i || /^\s*Content-Type:\s*([^\s:;]+)/i) {\r
+                                               $ContentType = $1;\r
+                                       }\r
+                               }\r
+                               # body read\r
+                               $value = "";\r
+                               while(<STDIN>) {\r
+                                       last    if(/^$Boundary/o);\r
+                                       $value .= $_;\r
+                               };\r
+                               $lastline = $_;\r
+                               $value =~s /\x0D\x0A$//;\r
+                               if($value ne '') {\r
+                                       if($FileName || $ContentType) {\r
+                                               $img_data_exists = 1;\r
+                                               (\r
+                                                       $FileName,              #\r
+                                                       $Ext,                   #\r
+                                                       $Length,                #\r
+                                                       $ImageWidth,    #\r
+                                                       $ImageHeight,   #\r
+                                                       $ContentName    #\r
+                                               ) = &CheckContentType(\$value,$FileName,$ContentType);\r
+\r
+                                               $FORM{$name}    = $FileName;\r
+                                               $new_fname              = $FileName;\r
+                                               push(@NEWFNAME_DATA,"$FileName\t$Ext\t$Length\t$ImageWidth\t$ImageHeight\t$ContentName");\r
+\r
+                                               # Multi-upload correspondence\r
+                                               push(@NEWFNAMES,$new_fname);\r
+                                               open(OUT,">$img_dir/$new_fname");\r
+                                               binmode(OUT);\r
+                                               eval "flock(OUT,2);" if($PM{'flock'} == 1);\r
+                                               print OUT $value;\r
+                                               eval "flock(OUT,8);" if($PM{'flock'} == 1);\r
+                                               close(OUT);\r
+\r
+                                       } elsif($name) {\r
+                                               $value  =~ tr/+/ /;\r
+                                               $value  =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;\r
+                                               &Encode(\$value,'trans');\r
+                                               $FORM{$name} .= "\0"                    if(defined($FORM{$name}));\r
+                                               $FORM{$name} .= $value;\r
+                                       }\r
+                               }\r
+                       };\r
+                       last if($lastline =~ /^$Boundary\-\-/o);\r
+               }\r
+       } elsif($ENV{'CONTENT_LENGTH'}) {\r
+               read(STDIN,$Buffer,$ENV{'CONTENT_LENGTH'});\r
+       }\r
+       foreach(split(/&/,$Buffer),split(/&/,$ENV{'QUERY_STRING'})) {\r
+               my($name, $value) = split(/=/);\r
+               $name   =~ tr/+/ /;\r
+               $name   =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;\r
+               $value  =~ tr/+/ /;\r
+               $value  =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;\r
+\r
+               &Encode(\$name);\r
+               &Encode(\$value,'trans');\r
+               $FORM{$name} .= "\0"                    if(defined($FORM{$name}));\r
+               $FORM{$name} .= $value;\r
+\r
+       }\r
+\r
+}\r
+\r
+##############################################################################\r
+# Summary\r
+#\r
+#      CheckContentType\r
+#\r
+# Parameters\r
+# Returns\r
+# Memo\r
+##############################################################################\r
+sub CheckContentType\r
+{\r
+\r
+       my($DATA,$FileName,$ContentType) = @_;\r
+       my($Ext,$ImageWidth,$ImageHeight,$ContentName,$Infomation);\r
+       my $DataLength = length($$DATA);\r
+\r
+       # An unknown file type\r
+\r
+       $_ = $ContentType;\r
+       my $UnknownType = (\r
+               !$_\r
+               || /^application\/(x-)?macbinary$/i\r
+               || /^application\/applefile$/i\r
+               || /^application\/octet-stream$/i\r
+               || /^text\/plane$/i\r
+               || /^x-unknown-content-type/i\r
+       );\r
+\r
+       # MacBinary(Mac Unnecessary data are deleted.)\r
+       if($UnknownType || $ENV{'HTTP_USER_AGENT'} =~ /Macintosh|Mac_/) {\r
+               if($DataLength > 128 && !unpack("C",substr($$DATA,0,1)) && !unpack("C",substr($$DATA,74,1)) && !unpack("C",substr($$DATA,82,1)) ) {\r
+                       my $MacBinary_ForkLength = unpack("N", substr($$DATA, 83, 4));          # ForkLength Get\r
+                       my $MacBinary_FileName = quotemeta(substr($$DATA, 2, unpack("C",substr($$DATA, 1, 1))));\r
+                       if($MacBinary_FileName && $MacBinary_ForkLength && $DataLength >= $MacBinary_ForkLength + 128\r
+                                       && ($FileName =~ /$MacBinary_FileName/i || substr($$DATA,102,4) eq 'mBIN')) {   # DATA TOP 128byte MacBinary!!\r
+                               $$DATA                          = substr($$DATA,128,$MacBinary_ForkLength);\r
+                               my $ResourceLength      = $DataLength - $MacBinary_ForkLength - 128;\r
+                               $DataLength                     = $MacBinary_ForkLength;\r
+                       }\r
+               }\r
+       }\r
+\r
+       # A file name is changed into EUC.\r
+#      &jcode::convert(\$FileName,'euc',$FormCodeDefault);\r
+#      &jcode::h2z_euc(\$FileName);\r
+       $FileName =~ s/^.*\\//;                                 # Windows, Mac\r
+       $FileName =~ s/^.*\///;                                 # UNIX\r
+       $FileName =~ s/&/&amp;/g;\r
+       $FileName =~ s/"/&quot;/g;\r
+       $FileName =~ s/</&lt;/g;\r
+       $FileName =~ s/>/&gt;/g;\r
+#\r
+#      if($CHARCODE ne 'euc') {\r
+#              &jcode::convert(\$FileName,$CHARCODE,'euc');\r
+#      }\r
+\r
+       # An extension is extracted and it changes into a small letter.\r
+       my $FileExt;\r
+       if($FileName =~ /\.(\w+)$/) {\r
+               $FileExt = $1;\r
+               $FileExt =~ tr/A-Z/a-z/;\r
+       }\r
+\r
+       # Executable file detection (ban on upload)\r
+       if($$DATA =~ /^MZ/) {\r
+               $Ext = 'exe';\r
+       }\r
+       # text\r
+       if(!$Ext && ($UnknownType || $ContentType =~ /^text\//i || $ContentType =~ /^application\/(?:rtf|richtext)$/i || $ContentType =~ /^image\/x-xbitmap$/i)\r
+                               && ! $$DATA =~ /[\000-\006\177\377]/) {\r
+#              $$DATA =~ s/\x0D\x0A/\n/g;\r
+#              $$DATA =~ tr/\x0D\x0A/\n\n/;\r
+#\r
+#              if(\r
+#                      $$DATA =~ /<\s*SCRIPT(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*(?:.|\n)*?\bONLOAD\s*=(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*(?:.|\n)*?\bONCLICK\s*=(?:.|\n)*?>/i\r
+#                              ) {\r
+#                      $Infomation = '(JavaScript contains)';\r
+#              }\r
+#              if($$DATA =~ /<\s*TABLE(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*BLINK(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*MARQUEE(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*OBJECT(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*EMBED(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*FRAME(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*APPLET(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*FORM(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*(?:.|\n)*?\bSRC\s*=(?:.|\n)*?>/i\r
+#                              || $$DATA =~ /<\s*(?:.|\n)*?\bDYNSRC\s*=(?:.|\n)*?>/i\r
+#                              ) {\r
+#                      $Infomation = '(the HTML tag which is not safe is included)';\r
+#              }\r
+\r
+               if($FileExt =~ /^txt$/i || $FileExt =~ /^cgi$/i || $FileExt =~ /^pl$/i) {                                                               # Text File\r
+                       $Ext = 'txt';\r
+               } elsif($ContentType =~ /^text\/html$/i || $FileExt =~ /html?/i || $$DATA =~ /<\s*HTML(?:.|\n)*?>/i) {  # HTML File\r
+                       $Ext = 'html';\r
+               } elsif($ContentType =~ /^image\/x-xbitmap$/i || $FileExt =~ /^xbm$/i) {                                                                # XBM(x-BitMap) Image\r
+                       my $XbmName = $1;\r
+                       my ($XbmWidth, $XbmHeight);\r
+                       if($$DATA =~ /\#define\s*$XbmName\_width\s*(\d+)/i) {\r
+                               $XbmWidth = $1;\r
+                       }\r
+                       if($$DATA =~ /\#define\s*$XbmName\_height\s*(\d+)/i) {\r
+                               $XbmHeight = $1;\r
+                       }\r
+                       if($XbmWidth && $XbmHeight) {\r
+                               $Ext = 'xbm';\r
+                               $ImageWidth             = $XbmWidth;\r
+                               $ImageHeight    = $XbmHeight;\r
+                       }\r
+               } else {                #\r
+                       $Ext = 'txt';\r
+               }\r
+       }\r
+\r
+       # image\r
+       if(!$Ext && ($UnknownType || $ContentType =~ /^image\//i)) {\r
+               # PNG\r
+               if($$DATA =~ /^\x89PNG\x0D\x0A\x1A\x0A/) {\r
+                       if(substr($$DATA, 12, 4) eq 'IHDR') {\r
+                               $Ext = 'png';\r
+                               ($ImageWidth, $ImageHeight) = unpack("N2", substr($$DATA, 16, 8));\r
+                       }\r
+               } elsif($$DATA =~ /^GIF8(?:9|7)a/) {                                                                                                                    # GIF89a(modified), GIF89a, GIF87a\r
+                       $Ext = 'gif';\r
+                       ($ImageWidth, $ImageHeight) = unpack("v2", substr($$DATA, 6, 4));\r
+               } elsif($$DATA =~ /^II\x2a\x00\x08\x00\x00\x00/ || $$DATA =~ /^MM\x00\x2a\x00\x00\x00\x08/) {   # TIFF\r
+                       $Ext = 'tif';\r
+               } elsif($$DATA =~ /^BM/) {                                                                                                                                              # BMP\r
+                       $Ext = 'bmp';\r
+               } elsif($$DATA =~ /^\xFF\xD8\xFF/ || $$DATA =~ /JFIF/) {                                                                                # JPEG\r
+                       my $HeaderPoint = index($$DATA, "\xFF\xD8\xFF", 0);\r
+                       my $Point = $HeaderPoint + 2;\r
+                       while($Point < $DataLength) {\r
+                               my($Maker, $MakerType, $MakerLength) = unpack("C2n",substr($$DATA,$Point,4));\r
+                               if($Maker != 0xFF || $MakerType == 0xd9 || $MakerType == 0xda) {\r
+                                       last;\r
+                               } elsif($MakerType >= 0xC0 && $MakerType <= 0xC3) {\r
+                                       $Ext = 'jpg';\r
+                                       ($ImageHeight, $ImageWidth) = unpack("n2", substr($$DATA, $Point + 5, 4));\r
+                                       if($HeaderPoint > 0) {\r
+                                               $$DATA = substr($$DATA, $HeaderPoint);\r
+                                               $DataLength = length($$DATA);\r
+                                       }\r
+                                       last;\r
+                               } else {\r
+                                       $Point += $MakerLength + 2;\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       # audio\r
+       if(!$Ext && ($UnknownType || $ContentType =~ /^audio\//i)) {\r
+               # MIDI Audio\r
+               if($$DATA =~ /^MThd/) {\r
+                       $Ext = 'mid';\r
+               } elsif($$DATA =~ /^\x2esnd/) {         # ULAW Audio\r
+                       $Ext = 'au';\r
+               } elsif($$DATA =~ /^RIFF/ || $$DATA =~ /^ID3/ && $$DATA =~ /RIFF/) {\r
+                       my $HeaderPoint = index($$DATA, "RIFF", 0);\r
+                       $_ = substr($$DATA, $HeaderPoint + 8, 8);\r
+                       if(/^WAVEfmt $/) {\r
+                               # WAVE\r
+                               if(unpack("V",substr($$DATA, $HeaderPoint + 16, 4)) == 16) {\r
+                                       $Ext = 'wav';\r
+                               } else {                                        # RIFF WAVE MP3\r
+                                       $Ext = 'mp3';\r
+                               }\r
+                       } elsif(/^RMIDdata$/) {                 # RIFF MIDI\r
+                               $Ext = 'rmi';\r
+                       } elsif(/^RMP3data$/) {                 # RIFF MP3\r
+                               $Ext = 'rmp';\r
+                       }\r
+                       if($ContentType =~ /^audio\//i) {\r
+                               $Infomation .= '(RIFF '. substr($$DATA, $HeaderPoint + 8, 4). ')';\r
+                       }\r
+               }\r
+       }\r
+\r
+       # a binary file\r
+       unless ($Ext) {\r
+               # PDF image\r
+               if($$DATA =~ /^\%PDF/) {\r
+                       # Picture size is not measured.\r
+                       $Ext = 'pdf';\r
+               } elsif($$DATA =~ /^FWS/) {             # Shockwave Flash\r
+                       $Ext = 'swf';\r
+               } elsif($$DATA =~ /^RIFF/ || $$DATA =~ /^ID3/ && $$DATA =~ /RIFF/) {\r
+                       my $HeaderPoint = index($$DATA, "RIFF", 0);\r
+                       $_ = substr($$DATA,$HeaderPoint + 8, 8);\r
+                       # AVI\r
+                       if(/^AVI LIST$/) {\r
+                               $Ext = 'avi';\r
+                       }\r
+                       if($ContentType =~ /^video\//i) {\r
+                               $Infomation .= '(RIFF '. substr($$DATA, $HeaderPoint + 8, 4). ')';\r
+                       }\r
+               } elsif($$DATA =~ /^PK/) {                      # ZIP Compress File\r
+                       $Ext = 'zip';\r
+               } elsif($$DATA =~ /^MSCF/) {            # CAB Compress File\r
+                       $Ext = 'cab';\r
+               } elsif($$DATA =~ /^Rar\!/) {           # RAR Compress File\r
+                       $Ext = 'rar';\r
+               } elsif(substr($$DATA, 2, 5) =~ /^\-lh(\d+|d)\-$/) {            # LHA Compress File\r
+                       $Infomation .= "(lh$1)";\r
+                       $Ext = 'lzh';\r
+               } 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") {\r
+                       # QuickTime\r
+                       $Ext = 'mov';\r
+               }\r
+       }\r
+\r
+       # Header analysis failure\r
+       unless ($Ext) {\r
+               # It will be followed if it applies for the MIME type from the browser.\r
+               foreach (keys %UPLOAD_CONTENT_TYPE_LIST) {\r
+                       next unless ($_);\r
+                       if($ContentType =~ /^$_$/i) {\r
+                               $Ext = $UPLOAD_CONTENT_TYPE_LIST{$_};\r
+                               $ContentName = &CheckContentExt($Ext);\r
+                               if(\r
+                                       grep {$_ eq $Ext;} (\r
+                                               'png',\r
+                                               'gif',\r
+                                               'jpg',\r
+                                               'xbm',\r
+                                               'tif',\r
+                                               'bmp',\r
+                                               'pdf',\r
+                                               'swf',\r
+                                               'mov',\r
+                                               'zip',\r
+                                               'cab',\r
+                                               'lzh',\r
+                                               'rar',\r
+                                               'mid',\r
+                                               'rmi',\r
+                                               'au',\r
+                                               'wav',\r
+                                               'avi',\r
+                                               'exe'\r
+                                       )\r
+                               ) {\r
+                                       $Infomation .= ' / Header analysis failure';\r
+                               }\r
+                               if($Ext ne $FileExt && &CheckContentExt($FileExt) eq $ContentName) {\r
+                                       $Ext = $FileExt;\r
+                               }\r
+                               last;\r
+                       }\r
+               }\r
+               # a MIME type is unknown--It judges from an extension.\r
+               unless ($Ext) {\r
+                       $ContentName = &CheckContentExt($FileExt);\r
+                       if($ContentName) {\r
+                               $Ext = $FileExt;\r
+                               $Infomation .= ' /      MIME type is unknown('. $ContentType. ')';\r
+                               last;\r
+                       }\r
+               }\r
+       }\r
+\r
+#      $ContentName = &CheckContentExt($Ext)   unless($ContentName);\r
+#      if($Ext && $ContentName) {\r
+#              $ContentName .=  $Infomation;\r
+#      } else {\r
+#              &upload_error(\r
+#                      'Extension Error',\r
+#                      "$FileName A not corresponding extension ($Ext)<BR>The extension which can be responded ". join(',', sort values(%UPLOAD_EXT_LIST))\r
+#              );\r
+#      }\r
+\r
+#      # SSI Tag Deletion\r
+#      if($Ext =~ /.?html?/ && $$DATA =~ /<\!/) {\r
+#              foreach (\r
+#                      'config',\r
+#                      'echo',\r
+#                      'exec',\r
+#                      'flastmod',\r
+#                      'fsize',\r
+#                      'include'\r
+#              ) {\r
+#                      $$DATA =~ s/\#\s*$_/\&\#35\;$_/ig\r
+#              }\r
+#      }\r
+\r
+       return (\r
+               $FileName,\r
+               $Ext,\r
+               int($DataLength / 1024 + 1),\r
+               $ImageWidth,\r
+               $ImageHeight,\r
+               $ContentName\r
+       );\r
+}\r
+\r
+##############################################################################\r
+# Summary\r
+#\r
+# Extension discernment\r
+#\r
+# Parameters\r
+# Returns\r
+# Memo\r
+##############################################################################\r
+\r
+sub CheckContentExt\r
+{\r
+\r
+       my($Ext) = @_;\r
+       my $ContentName;\r
+       foreach (keys %UPLOAD_EXT_LIST) {\r
+               next    unless ($_);\r
+               if($_ && $Ext =~ /^$_$/) {\r
+                       $ContentName = $UPLOAD_EXT_LIST{$_};\r
+                       last;\r
+               }\r
+       }\r
+       return $ContentName;\r
+\r
+}\r
+\r
+##############################################################################\r
+# Summary\r
+#\r
+# Form decode\r
+#\r
+# Parameters\r
+# Returns\r
+# Memo\r
+##############################################################################\r
+sub Encode\r
+{\r
+\r
+       my($value,$Trans) = @_;\r
+\r
+#      my $FormCode = &jcode::getcode($value) || $FormCodeDefault;\r
+#      $FormCodeDefault ||= $FormCode;\r
+#\r
+#      if($Trans && $TRANS_2BYTE_CODE) {\r
+#              if($FormCode ne 'euc') {\r
+#                      &jcode::convert($value, 'euc', $FormCode);\r
+#              }\r
+#              &jcode::tr(\r
+#                      $value,\r
+#                      "\xA3\xB0-\xA3\xB9\xA3\xC1-\xA3\xDA\xA3\xE1-\xA3\xFA",\r
+#                      '0-9A-Za-z'\r
+#              );\r
+#              if($CHARCODE ne 'euc') {\r
+#                      &jcode::convert($value,$CHARCODE,'euc');\r
+#              }\r
+#      } else {\r
+#              if($CHARCODE ne $FormCode) {\r
+#                      &jcode::convert($value,$CHARCODE,$FormCode);\r
+#              }\r
+#      }\r
+#      if($CHARCODE eq 'euc') {\r
+#              &jcode::h2z_euc($value);\r
+#      } elsif($CHARCODE eq 'sjis') {\r
+#              &jcode::h2z_sjis($value);\r
+#      }\r
+\r
+}\r
+\r
+##############################################################################\r
+# Summary\r
+#\r
+# Error Msg\r
+#\r
+# Parameters\r
+# Returns\r
+# Memo\r
+##############################################################################\r
+\r
+sub upload_error\r
+{\r
+\r
+       local($error_message)   = $_[0];\r
+       local($error_message2)  = $_[1];\r
+\r
+       print "Content-type: text/html\n\n";\r
+       print<<EOF;\r
+<HTML>\r
+<HEAD>\r
+<TITLE>Error Message</TITLE></HEAD>\r
+<BODY>\r
+<table border="1" cellspacing="10" cellpadding="10">\r
+       <TR bgcolor="#0000B0">\r
+       <TD bgcolor="#0000B0" NOWRAP><font size="-1" color="white"><B>Error Message</B></font></TD>\r
+       </TR>\r
+</table>\r
+<UL>\r
+<H4> $error_message </H4>\r
+$error_message2 <BR>\r
+</UL>\r
+</BODY>\r
+</HTML>\r
+EOF\r
+       &rm_tmp_uploaded_files;                 # Image Temporary deletion\r
+       exit;\r
+}\r
+\r
+##############################################################################\r
+# Summary\r
+#\r
+# Image Temporary deletion\r
+#\r
+# Parameters\r
+# Returns\r
+# Memo\r
+##############################################################################\r
+\r
+sub rm_tmp_uploaded_files\r
+{\r
+       if($img_data_exists == 1){\r
+               sleep 1;\r
+               foreach $fname_list(@NEWFNAMES) {\r
+                       if(-e "$img_dir/$fname_list") {\r
+                               unlink("$img_dir/$fname_list");\r
+                       }\r
+               }\r
+       }\r
+\r
+}\r
+1;\r
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 (file)
index 0000000..e860292
--- /dev/null
@@ -0,0 +1,60 @@
+#####\r
+#  FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+#  Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+#\r
+#  == BEGIN LICENSE ==\r
+#\r
+#  Licensed under the terms of any of the following licenses at your\r
+#  choice:\r
+#\r
+#   - GNU General Public License Version 2 or later (the "GPL")\r
+#     http://www.gnu.org/licenses/gpl.html\r
+#\r
+#   - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+#     http://www.gnu.org/licenses/lgpl.html\r
+#\r
+#   - Mozilla Public License Version 1.1 or later (the "MPL")\r
+#     http://www.mozilla.org/MPL/MPL-1.1.html\r
+#\r
+#  == END LICENSE ==\r
+#\r
+#  This is the File Manager Connector for Perl.\r
+#####\r
+\r
+sub RemoveFromStart\r
+{\r
+       local($sourceString, $charToRemove) = @_;\r
+       $sPattern = '^' . $charToRemove . '+' ;\r
+       $sourceString =~ s/^$charToRemove+//g;\r
+       return $sourceString;\r
+}\r
+\r
+sub RemoveFromEnd\r
+{\r
+       local($sourceString, $charToRemove) = @_;\r
+       $sPattern = $charToRemove . '+$' ;\r
+       $sourceString =~ s/$charToRemove+$//g;\r
+       return $sourceString;\r
+}\r
+\r
+sub ConvertToXmlAttribute\r
+{\r
+       local($value) = @_;\r
+       return $value;\r
+#      return utf8_encode(htmlspecialchars($value));\r
+\r
+}\r
+\r
+sub specialchar_cnv\r
+{\r
+       local($ch) = @_;\r
+\r
+       $ch =~ s/&/&amp;/g;             # &\r
+       $ch =~ s/\"/&quot;/g;   #"\r
+       $ch =~ s/\'/&#39;/g;    # '\r
+       $ch =~ s/</&lt;/g;              # <\r
+       $ch =~ s/>/&gt;/g;              # >\r
+       return($ch);\r
+}\r
+\r
+1;\r
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 (file)
index 0000000..90653d6
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This page shows the actual folder path.\r
+-->\r
+<html>\r
+       <head>\r
+               <link href="browser.css" type="text/css" rel="stylesheet">\r
+               <script type="text/javascript">\r
+\r
+function OnResize()\r
+{\r
+       divName.style.width = "1px" ;\r
+       divName.style.width = tdName.offsetWidth + "px" ;\r
+}\r
+\r
+function SetCurrentFolder( resourceType, folderPath )\r
+{\r
+       document.getElementById('tdName').innerHTML = folderPath ;\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       window.top.IsLoadedActualFolder = true ;\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body bottomMargin="0" topMargin="0">\r
+               <table height="100%" cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                       <tr>\r
+                               <td>\r
+                                       <button style="WIDTH: 100%" type="button">\r
+                                               <table cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                                                       <tr>\r
+                                                               <td><img height="32" alt="" src="images/FolderOpened32.gif" width="32"></td>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td id="tdName" width="100%" nowrap class="ActualFolder">/</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td><img height="8" src="images/ButtonArrow.gif" width="12"></td>\r
+                                                               <td>&nbsp;</td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </button>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..8f72ff5
--- /dev/null
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Page used to create new folders in the current folder.\r
+-->\r
+<html>\r
+       <head>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <link href="browser.css" type="text/css" rel="stylesheet">\r
+               <script type="text/javascript" src="js/common.js"></script>\r
+               <script language="javascript">\r
+\r
+function SetCurrentFolder( resourceType, folderPath )\r
+{\r
+       oConnector.ResourceType = resourceType ;\r
+       oConnector.CurrentFolder = folderPath ;\r
+}\r
+\r
+function CreateFolder()\r
+{\r
+       var sFolderName ;\r
+\r
+       while ( true )\r
+       {\r
+               sFolderName = prompt( 'Type the name of the new folder:', '' ) ;\r
+\r
+               if ( sFolderName == null )\r
+                       return ;\r
+               else if ( sFolderName.length == 0 )\r
+                       alert( 'Please type the folder name' ) ;\r
+               else\r
+                       break ;\r
+       }\r
+\r
+       oConnector.SendCommand( 'CreateFolder', 'NewFolderName=' + encodeURIComponent( sFolderName) , CreateFolderCallBack ) ;\r
+}\r
+\r
+function CreateFolderCallBack( fckXml )\r
+{\r
+       if ( oConnector.CheckError( fckXml ) == 0 )\r
+               window.parent.frames['frmResourcesList'].Refresh() ;\r
+\r
+       /*\r
+       // Get the current folder path.\r
+       var oNode = fckXml.SelectSingleNode( 'Connector/Error' ) ;\r
+       var iErrorNumber = parseInt( oNode.attributes.getNamedItem('number').value ) ;\r
+\r
+       switch ( iErrorNumber )\r
+       {\r
+               case 0 :\r
+                       window.parent.frames['frmResourcesList'].Refresh() ;\r
+                       break ;\r
+               case 101 :\r
+                       alert( 'Folder already exists' ) ;\r
+                       break ;\r
+               case 102 :\r
+                       alert( 'Invalid folder name' ) ;\r
+                       break ;\r
+               case 103 :\r
+                       alert( 'You have no permissions to create the folder' ) ;\r
+                       break ;\r
+               case 110 :\r
+                       alert( 'Unknown error creating folder' ) ;\r
+                       break ;\r
+               default :\r
+                       alert( 'Error creating folder. Error number: ' + iErrorNumber ) ;\r
+                       break ;\r
+       }\r
+       */\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       window.top.IsLoadedCreateFolder = true ;\r
+}\r
+               </script>\r
+       </head>\r
+       <body bottomMargin="0" topMargin="0">\r
+               <table height="100%" cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                       <tr>\r
+                               <td>\r
+                                       <button type="button" style="WIDTH: 100%" onclick="CreateFolder();">\r
+                                               <table cellSpacing="0" cellPadding="0" border="0">\r
+                                                       <tr>\r
+                                                               <td><img height="16" alt="" src="images/Folder.gif" width="16"></td>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td nowrap>Create New Folder</td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </button>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..2dc0eb0
--- /dev/null
@@ -0,0 +1,196 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This page shows the list of folders available in the parent folder\r
+ * of the current folder.\r
+-->\r
+<html>\r
+       <head>\r
+               <link href="browser.css" type="text/css" rel="stylesheet">\r
+               <script type="text/javascript" src="js/common.js"></script>\r
+               <script language="javascript">\r
+\r
+var sActiveFolder ;\r
+\r
+var bIsLoaded = false ;\r
+var iIntervalId ;\r
+\r
+var oListManager = new Object() ;\r
+\r
+oListManager.Init = function()\r
+{\r
+       this.Table = document.getElementById('tableFiles') ;\r
+       this.UpRow = document.getElementById('trUp') ;\r
+\r
+       this.TableRows = new Object() ;\r
+}\r
+\r
+oListManager.Clear = function()\r
+{\r
+       // Remove all other rows available.\r
+       while ( this.Table.rows.length > 1 )\r
+               this.Table.deleteRow(1) ;\r
+\r
+       // Reset the TableRows collection.\r
+       this.TableRows = new Object() ;\r
+}\r
+\r
+oListManager.AddItem = function( folderName, folderPath )\r
+{\r
+       // Create the new row.\r
+       var oRow = this.Table.insertRow(-1) ;\r
+       oRow.className = 'FolderListFolder' ;\r
+\r
+       // Build the link to view the folder.\r
+       var sLink = '<a href="#" onclick="OpenFolder(\'' + folderPath + '\');return false;">' ;\r
+\r
+       // Add the folder icon cell.\r
+       var oCell = oRow.insertCell(-1) ;\r
+       oCell.width = 16 ;\r
+       oCell.innerHTML = sLink + '<img alt="" src="images/spacer.gif" width="16" height="16" border="0"></a>' ;\r
+\r
+       // Add the folder name cell.\r
+       oCell = oRow.insertCell(-1) ;\r
+       oCell.noWrap = true ;\r
+       oCell.innerHTML = '&nbsp;' + sLink + folderName + '</a>' ;\r
+\r
+       this.TableRows[ folderPath ] = oRow ;\r
+}\r
+\r
+oListManager.ShowUpFolder = function( upFolderPath )\r
+{\r
+       this.UpRow.style.display = ( upFolderPath != null ? '' : 'none' ) ;\r
+\r
+       if ( upFolderPath != null )\r
+       {\r
+               document.getElementById('linkUpIcon').onclick = document.getElementById('linkUp').onclick = function()\r
+               {\r
+                       LoadFolders( upFolderPath ) ;\r
+                       return false ;\r
+               }\r
+       }\r
+}\r
+\r
+function CheckLoaded()\r
+{\r
+       if ( window.top.IsLoadedActualFolder\r
+               && window.top.IsLoadedCreateFolder\r
+               && window.top.IsLoadedUpload\r
+               && window.top.IsLoadedResourcesList )\r
+       {\r
+               window.clearInterval( iIntervalId ) ;\r
+               bIsLoaded = true ;\r
+               OpenFolder( sActiveFolder ) ;\r
+       }\r
+}\r
+\r
+function OpenFolder( folderPath )\r
+{\r
+       sActiveFolder = folderPath ;\r
+\r
+       if ( ! bIsLoaded )\r
+       {\r
+               if ( ! iIntervalId )\r
+                       iIntervalId = window.setInterval( CheckLoaded, 100 ) ;\r
+               return ;\r
+       }\r
+\r
+       // Change the style for the select row (to show the opened folder).\r
+       for ( var sFolderPath in oListManager.TableRows )\r
+       {\r
+               oListManager.TableRows[ sFolderPath ].className =\r
+                       ( sFolderPath == folderPath ? 'FolderListCurrentFolder' : 'FolderListFolder' ) ;\r
+       }\r
+\r
+       // Set the current folder in all frames.\r
+       window.parent.frames['frmActualFolder'].SetCurrentFolder( oConnector.ResourceType, folderPath ) ;\r
+       window.parent.frames['frmCreateFolder'].SetCurrentFolder( oConnector.ResourceType, folderPath ) ;\r
+       window.parent.frames['frmUpload'].SetCurrentFolder( oConnector.ResourceType, folderPath ) ;\r
+\r
+       // Load the resources list for this folder.\r
+       window.parent.frames['frmResourcesList'].LoadResources( oConnector.ResourceType, folderPath ) ;\r
+}\r
+\r
+function LoadFolders( folderPath )\r
+{\r
+       // Clear the folders list.\r
+       oListManager.Clear() ;\r
+\r
+       // Get the parent folder path.\r
+       var sParentFolderPath ;\r
+       if ( folderPath != '/' )\r
+               sParentFolderPath = folderPath.substring( 0, folderPath.lastIndexOf( '/', folderPath.length - 2 ) + 1 ) ;\r
+\r
+       // Show/Hide the Up Folder.\r
+       oListManager.ShowUpFolder( sParentFolderPath ) ;\r
+\r
+       if ( folderPath != '/' )\r
+       {\r
+               sActiveFolder = folderPath ;\r
+               oConnector.CurrentFolder = sParentFolderPath ;\r
+               oConnector.SendCommand( 'GetFolders', null, GetFoldersCallBack ) ;\r
+       }\r
+       else\r
+               OpenFolder( '/' ) ;\r
+}\r
+\r
+function GetFoldersCallBack( fckXml )\r
+{\r
+       if ( oConnector.CheckError( fckXml ) != 0 )\r
+               return ;\r
+\r
+       // Get the current folder path.\r
+       var oNode = fckXml.SelectSingleNode( 'Connector/CurrentFolder' ) ;\r
+       var sCurrentFolderPath = oNode.attributes.getNamedItem('path').value ;\r
+\r
+       var oNodes = fckXml.SelectNodes( 'Connector/Folders/Folder' ) ;\r
+\r
+       for ( var i = 0 ; i < oNodes.length ; i++ )\r
+       {\r
+               var sFolderName = oNodes[i].attributes.getNamedItem('name').value ;\r
+               oListManager.AddItem( sFolderName, sCurrentFolderPath + sFolderName + "/" ) ;\r
+       }\r
+\r
+       OpenFolder( sActiveFolder ) ;\r
+}\r
+\r
+function SetResourceType( type )\r
+{\r
+       oConnector.ResourceType = type ;\r
+       LoadFolders( '/' ) ;\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       oListManager.Init() ;\r
+       LoadFolders( '/' ) ;\r
+}\r
+               </script>\r
+       </head>\r
+       <body class="FileArea" bottomMargin="10" leftMargin="10" topMargin="10" rightMargin="10">\r
+               <table id="tableFiles" cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                       <tr id="trUp" style="DISPLAY: none">\r
+                               <td width="16"><a id="linkUpIcon" href="#"><img alt="" src="images/FolderUp.gif" width="16" height="16" border="0"></a></td>\r
+                               <td nowrap width="100%">&nbsp;<a id="linkUp" href="#">..</a></td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..3f041f7
--- /dev/null
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This page shows all resources available in a folder in the File Browser.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <link href="browser.css" type="text/css" rel="stylesheet" />\r
+       <script type="text/javascript" src="js/common.js"></script>\r
+       <script type="text/javascript">\r
+\r
+var oListManager = new Object() ;\r
+\r
+oListManager.Clear = function()\r
+{\r
+       document.body.innerHTML = '' ;\r
+}\r
+\r
+oListManager.GetFolderRowHtml = function( folderName, folderPath )\r
+{\r
+       // Build the link to view the folder.\r
+       var sLink = '<a href="#" onclick="OpenFolder(\'' + folderPath.replace( /'/g, '\\\'') + '\');return false;">' ;\r
+\r
+       return '<tr>' +\r
+                       '<td width="16">' +\r
+                               sLink +\r
+                               '<img alt="" src="images/Folder.gif" width="16" height="16" border="0"></a>' +\r
+                       '</td><td nowrap colspan="2">&nbsp;' +\r
+                               sLink +\r
+                               folderName +\r
+                               '</a>' +\r
+               '</td></tr>' ;\r
+}\r
+\r
+oListManager.GetFileRowHtml = function( fileName, fileUrl, fileSize )\r
+{\r
+       // Build the link to view the folder.\r
+       var sLink = '<a href="#" onclick="OpenFile(\'' + fileUrl.replace( /'/g, '\\\'') + '\');return false;">' ;\r
+\r
+       // Get the file icon.\r
+       var sIcon = oIcons.GetIcon( fileName ) ;\r
+\r
+       return '<tr>' +\r
+                       '<td width="16">' +\r
+                               sLink +\r
+                               '<img alt="" src="images/icons/' + sIcon + '.gif" width="16" height="16" border="0"></a>' +\r
+                       '</td><td>&nbsp;' +\r
+                               sLink +\r
+                               fileName +\r
+                               '</a>' +\r
+                       '</td><td align="right" nowrap>&nbsp;' +\r
+                               fileSize +\r
+                               ' KB' +\r
+               '</td></tr>' ;\r
+}\r
+\r
+function OpenFolder( folderPath )\r
+{\r
+       // Load the resources list for this folder.\r
+       window.parent.frames['frmFolders'].LoadFolders( folderPath ) ;\r
+}\r
+\r
+function OpenFile( fileUrl )\r
+{\r
+       window.top.opener.SetUrl( encodeURI( fileUrl ) ) ;\r
+       window.top.close() ;\r
+       window.top.opener.focus() ;\r
+}\r
+\r
+function LoadResources( resourceType, folderPath )\r
+{\r
+       oListManager.Clear() ;\r
+       oConnector.ResourceType = resourceType ;\r
+       oConnector.CurrentFolder = folderPath ;\r
+       oConnector.SendCommand( 'GetFoldersAndFiles', null, GetFoldersAndFilesCallBack ) ;\r
+}\r
+\r
+function Refresh()\r
+{\r
+       LoadResources( oConnector.ResourceType, oConnector.CurrentFolder ) ;\r
+}\r
+\r
+function GetFoldersAndFilesCallBack( fckXml )\r
+{\r
+       if ( oConnector.CheckError( fckXml ) != 0 )\r
+               return ;\r
+\r
+       // Get the current folder path.\r
+       var oFolderNode = fckXml.SelectSingleNode( 'Connector/CurrentFolder' ) ;\r
+       if ( oFolderNode == null )\r
+       {\r
+               alert( 'The server didn\'t reply with a proper XML data. Please check your configuration.' ) ;\r
+               return ;\r
+       }\r
+       var sCurrentFolderPath  = oFolderNode.attributes.getNamedItem('path').value ;\r
+       var sCurrentFolderUrl   = oFolderNode.attributes.getNamedItem('url').value ;\r
+\r
+//     var dTimer = new Date() ;\r
+\r
+       var oHtml = new StringBuilder( '<table id="tableFiles" cellspacing="1" cellpadding="0" width="100%" border="0">' ) ;\r
+\r
+       // Add the Folders.\r
+       var oNodes ;\r
+       oNodes = fckXml.SelectNodes( 'Connector/Folders/Folder' ) ;\r
+       for ( var i = 0 ; i < oNodes.length ; i++ )\r
+       {\r
+               var sFolderName = oNodes[i].attributes.getNamedItem('name').value ;\r
+               oHtml.Append( oListManager.GetFolderRowHtml( sFolderName, sCurrentFolderPath + sFolderName + "/" ) ) ;\r
+       }\r
+\r
+       // Add the Files.\r
+       oNodes = fckXml.SelectNodes( 'Connector/Files/File' ) ;\r
+       for ( var j = 0 ; j < oNodes.length ; j++ )\r
+       {\r
+               var oNode = oNodes[j] ;\r
+               var sFileName = oNode.attributes.getNamedItem('name').value ;\r
+               var sFileSize = oNode.attributes.getNamedItem('size').value ;\r
+\r
+               // Get the optional "url" attribute. If not available, build the url.\r
+               var oFileUrlAtt = oNodes[j].attributes.getNamedItem('url') ;\r
+               var sFileUrl = oFileUrlAtt != null ? oFileUrlAtt.value : sCurrentFolderUrl + sFileName ;\r
+\r
+               oHtml.Append( oListManager.GetFileRowHtml( sFileName, sFileUrl, sFileSize ) ) ;\r
+       }\r
+\r
+       oHtml.Append( '<\/table>' ) ;\r
+\r
+       document.body.innerHTML = oHtml.ToString() ;\r
+\r
+//     window.top.document.title = 'Finished processing in ' + ( ( ( new Date() ) - dTimer ) / 1000 ) + ' seconds' ;\r
+\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       window.top.IsLoadedResourcesList = true ;\r
+}\r
+       </script>\r
+</head>\r
+<body class="FileArea" bottommargin="10" leftmargin="10" topmargin="10" rightmargin="10">\r
+</body>\r
+</html>\r
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 (file)
index 0000000..933e855
--- /dev/null
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This page shows the list of available resource types.\r
+-->\r
+<html>\r
+       <head>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <link href="browser.css" type="text/css" rel="stylesheet">\r
+               <script type="text/javascript" src="js/common.js"></script>\r
+               <script language="javascript">\r
+\r
+function SetResourceType( type )\r
+{\r
+       window.parent.frames["frmFolders"].SetResourceType( type ) ;\r
+}\r
+\r
+var aTypes = [\r
+       ['File','File'],\r
+       ['Image','Image'],\r
+       ['Flash','Flash'],\r
+       ['Media','Media']\r
+] ;\r
+\r
+window.onload = function()\r
+{\r
+       for ( var i = 0 ; i < aTypes.length ; i++ )\r
+       {\r
+               if ( oConnector.ShowAllTypes || aTypes[i][0] == oConnector.ResourceType )\r
+                       AddSelectOption( document.getElementById('cmbType'), aTypes[i][1], aTypes[i][0] ) ;\r
+       }\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body bottomMargin="0" topMargin="0">\r
+               <table height="100%" cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                       <tr>\r
+                               <td nowrap>\r
+                                       Resource Type<BR>\r
+                                       <select id="cmbType" style="WIDTH: 100%" onchange="SetResourceType(this.value);">\r
+                                       </select>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..b84882d
--- /dev/null
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Page used to upload new files in the current folder.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+       <head>\r
+               <link href="browser.css" type="text/css" rel="stylesheet" />\r
+               <script type="text/javascript" src="js/common.js"></script>\r
+               <script type="text/javascript">\r
+\r
+function SetCurrentFolder( resourceType, folderPath )\r
+{\r
+       var sUrl = oConnector.ConnectorUrl + 'Command=FileUpload' ;\r
+       sUrl += '&Type=' + resourceType ;\r
+       sUrl += '&CurrentFolder=' + encodeURIComponent( folderPath ) ;\r
+\r
+       document.getElementById('frmUpload').action = sUrl ;\r
+}\r
+\r
+function OnSubmit()\r
+{\r
+       if ( document.getElementById('NewFile').value.length == 0 )\r
+       {\r
+               alert( 'Please select a file from your computer' ) ;\r
+               return false ;\r
+       }\r
+\r
+       // Set the interface elements.\r
+       document.getElementById('eUploadMessage').innerHTML = 'Upload a new file in this folder (Upload in progress, please wait...)' ;\r
+       document.getElementById('btnUpload').disabled = true ;\r
+\r
+       return true ;\r
+}\r
+\r
+function OnUploadCompleted( errorNumber, data )\r
+{\r
+       // Reset the Upload Worker Frame.\r
+       window.parent.frames['frmUploadWorker'].location = 'javascript:void(0)' ;\r
+\r
+       // Reset the upload form (On IE we must do a little trick to avout problems).\r
+       if ( document.all )\r
+               document.getElementById('NewFile').outerHTML = '<input id="NewFile" name="NewFile" style="WIDTH: 100%" type="file">' ;\r
+       else\r
+               document.getElementById('frmUpload').reset() ;\r
+\r
+       // Reset the interface elements.\r
+       document.getElementById('eUploadMessage').innerHTML = 'Upload a new file in this folder' ;\r
+       document.getElementById('btnUpload').disabled = false ;\r
+\r
+       switch ( errorNumber )\r
+       {\r
+               case 0 :\r
+                       window.parent.frames['frmResourcesList'].Refresh() ;\r
+                       break ;\r
+               case 1 :        // Custom error.\r
+                       alert( data ) ;\r
+                       break ;\r
+               case 201 :\r
+                       window.parent.frames['frmResourcesList'].Refresh() ;\r
+                       alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + data + '"' ) ;\r
+                       break ;\r
+               case 202 :\r
+                       alert( 'Invalid file' ) ;\r
+                       break ;\r
+               default :\r
+                       alert( 'Error on file upload. Error number: ' + errorNumber ) ;\r
+                       break ;\r
+       }\r
+}\r
+\r
+window.onload = function()\r
+{\r
+       window.top.IsLoadedUpload = true ;\r
+}\r
+               </script>\r
+       </head>\r
+       <body bottommargin="0" topmargin="0">\r
+               <form id="frmUpload" action="" target="frmUploadWorker" method="post" enctype="multipart/form-data" onsubmit="return OnSubmit();">\r
+                       <table height="100%" cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                               <tr>\r
+                                       <td nowrap="nowrap">\r
+                                               <span id="eUploadMessage">Upload a new file in this folder</span><br>\r
+                                               <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                                                       <tr>\r
+                                                               <td width="100%"><input id="NewFile" name="NewFile" style="WIDTH: 100%" type="file"></td>\r
+                                                               <td nowrap="nowrap">&nbsp;<input id="btnUpload" type="submit" value="Upload"></td>\r
+                                                       </tr>\r
+                                               </table>\r
+                                       </td>\r
+                               </tr>\r
+                       </table>\r
+               </form>\r
+       </body>\r
+</html>\r
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 (file)
index 0000000..a355e5a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/ButtonArrow.gif 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 (file)
index 0000000..ab6824d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder.gif 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 (file)
index 0000000..b93b752
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/Folder32.gif 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 (file)
index 0000000..0c5dd41
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened.gif 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 (file)
index 0000000..3e3fcf5
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderOpened32.gif 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 (file)
index 0000000..ad5bc20
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/FolderUp.gif 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 (file)
index 0000000..699e6a3
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ai.gif 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 (file)
index 0000000..97025bb
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/avi.gif 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 (file)
index 0000000..f3c7f82
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/bmp.gif 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 (file)
index 0000000..b62bd02
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/cs.gif 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 (file)
index 0000000..976997b
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/default.icon.gif 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 (file)
index 0000000..9b54964
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/dll.gif 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 (file)
index 0000000..b557568
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/doc.gif 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 (file)
index 0000000..7584993
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/exe.gif 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 (file)
index 0000000..923079f
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/fla.gif 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 (file)
index 0000000..df5f579
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/gif.gif 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 (file)
index 0000000..a9bdf00
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/htm.gif 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 (file)
index 0000000..a9bdf00
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/html.gif 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 (file)
index 0000000..de78363
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/jpg.gif 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 (file)
index 0000000..fe0c98e
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/js.gif 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 (file)
index 0000000..d3af9e8
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mdb.gif 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 (file)
index 0000000..7d6360f
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/mp3.gif 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 (file)
index 0000000..4950ec8
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/pdf.gif 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 (file)
index 0000000..0a79ebf
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/png.gif 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 (file)
index 0000000..023431c
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/ppt.gif 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 (file)
index 0000000..b9eace7
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/rdp.gif 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 (file)
index 0000000..5df7de5
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swf.gif 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 (file)
index 0000000..7807c07
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/swt.gif 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 (file)
index 0000000..4e2c2e3
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/txt.gif 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 (file)
index 0000000..7624697
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/vsd.gif 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 (file)
index 0000000..afe724a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xls.gif 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 (file)
index 0000000..4fae356
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/xml.gif 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 (file)
index 0000000..7157f72
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/32/zip.gif 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 (file)
index 0000000..ba5a913
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ai.gif 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 (file)
index 0000000..6f3bac9
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/avi.gif 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 (file)
index 0000000..7708dd8
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/bmp.gif 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 (file)
index 0000000..4d92723
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/cs.gif 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 (file)
index 0000000..6ce26a4
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/default.icon.gif 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 (file)
index 0000000..48d445a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/dll.gif 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 (file)
index 0000000..6535b4c
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/doc.gif 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 (file)
index 0000000..315817f
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/exe.gif 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 (file)
index 0000000..8f91a98
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/fla.gif 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 (file)
index 0000000..a5e3e6c
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/gif.gif 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 (file)
index 0000000..0b5d6ba
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/htm.gif 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 (file)
index 0000000..0b5d6ba
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/html.gif 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 (file)
index 0000000..634b386
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/jpg.gif 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 (file)
index 0000000..4ea17d4
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/js.gif 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 (file)
index 0000000..0d7c102
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mdb.gif 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 (file)
index 0000000..6f3bac9
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/mp3.gif 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 (file)
index 0000000..ca1f94a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/pdf.gif 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 (file)
index 0000000..b6d1b32
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/png.gif 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 (file)
index 0000000..877a8c8
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/ppt.gif 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 (file)
index 0000000..916cd7e
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/rdp.gif 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 (file)
index 0000000..314469d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swf.gif 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 (file)
index 0000000..314469d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/swt.gif 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 (file)
index 0000000..1511ba3
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/txt.gif 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 (file)
index 0000000..9be3daa
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/vsd.gif 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 (file)
index 0000000..f57715d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xls.gif 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 (file)
index 0000000..4559928
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/xml.gif 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 (file)
index 0000000..b1e2492
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/icons/zip.gif 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 (file)
index 0000000..35d42e8
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/filemanager/browser/default/images/spacer.gif 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 (file)
index 0000000..2f47217
--- /dev/null
@@ -0,0 +1,55 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Common objects and functions shared by all pages that compose the\r
+ * File Browser dialog window.\r
+ */\r
+\r
+function AddSelectOption( selectElement, optionText, optionValue )\r
+{\r
+       var oOption = document.createElement("OPTION") ;\r
+\r
+       oOption.text    = optionText ;\r
+       oOption.value   = optionValue ;\r
+\r
+       selectElement.options.add(oOption) ;\r
+\r
+       return oOption ;\r
+}\r
+\r
+var oConnector = window.parent.oConnector ;\r
+var oIcons             = window.parent.oIcons ;\r
+\r
+\r
+function StringBuilder( value )\r
+{\r
+    this._Strings = new Array( value || '' ) ;\r
+}\r
+\r
+StringBuilder.prototype.Append = function( value )\r
+{\r
+    if ( value )\r
+        this._Strings.push( value ) ;\r
+}\r
+\r
+StringBuilder.prototype.ToString = function()\r
+{\r
+    return this._Strings.join( '' ) ;\r
+}
\ No newline at end of file
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 (file)
index 0000000..043ca84
--- /dev/null
@@ -0,0 +1,129 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Defines the FCKXml object that is used for XML data calls\r
+ * and XML processing.\r
+ *\r
+ * This script is shared by almost all pages that compose the\r
+ * File Browser frameset.\r
+ */\r
+\r
+var FCKXml = function()\r
+{}\r
+\r
+FCKXml.prototype.GetHttpRequest = function()\r
+{\r
+       // Gecko / IE7\r
+       if ( typeof(XMLHttpRequest) != 'undefined' )\r
+               return new XMLHttpRequest() ;\r
+\r
+       // IE6\r
+       try { return new ActiveXObject( 'Msxml2.XMLHTTP' ) ; }\r
+       catch(e) {}\r
+\r
+       // IE5\r
+       try { return new ActiveXObject( 'Microsoft.XMLHTTP' ) ; }\r
+       catch(e) {}\r
+\r
+       return null ;\r
+}\r
+\r
+FCKXml.prototype.LoadUrl = function( urlToCall, asyncFunctionPointer )\r
+{\r
+       var oFCKXml = this ;\r
+\r
+       var bAsync = ( typeof(asyncFunctionPointer) == 'function' ) ;\r
+\r
+       var oXmlHttp = this.GetHttpRequest() ;\r
+\r
+       oXmlHttp.open( "GET", urlToCall, bAsync ) ;\r
+\r
+       if ( bAsync )\r
+       {\r
+               oXmlHttp.onreadystatechange = function()\r
+               {\r
+                       if ( oXmlHttp.readyState == 4 )\r
+                       {\r
+                               if ( ( oXmlHttp.status != 200 && oXmlHttp.status != 304 ) || oXmlHttp.responseXML == null || oXmlHttp.responseXML.firstChild == null )\r
+                               {\r
+                                       alert( 'The server didn\'t send back a proper XML response. Please contact your system administrator.\n\n' +\r
+                                                       'XML request error: ' + oXmlHttp.statusText + ' (' + oXmlHttp.status + ')\n\n' +\r
+                                                       'Requested URL:\n' + urlToCall + '\n\n' +\r
+                                                       'Response text:\n' + oXmlHttp.responseText ) ;\r
+                                       return ;\r
+                               }\r
+\r
+                               oFCKXml.DOMDocument = oXmlHttp.responseXML ;\r
+                               asyncFunctionPointer( oFCKXml ) ;\r
+                       }\r
+               }\r
+       }\r
+\r
+       oXmlHttp.send( null ) ;\r
+\r
+       if ( ! bAsync )\r
+       {\r
+               if ( oXmlHttp.status == 200 || oXmlHttp.status == 304 )\r
+                       this.DOMDocument = oXmlHttp.responseXML ;\r
+               else\r
+               {\r
+                       alert( 'XML request error: ' + oXmlHttp.statusText + ' (' + oXmlHttp.status + ')' ) ;\r
+               }\r
+       }\r
+}\r
+\r
+FCKXml.prototype.SelectNodes = function( xpath )\r
+{\r
+       if ( navigator.userAgent.indexOf('MSIE') >= 0 )         // IE\r
+               return this.DOMDocument.selectNodes( xpath ) ;\r
+       else                                    // Gecko\r
+       {\r
+               var aNodeArray = new Array();\r
+\r
+               var xPathResult = this.DOMDocument.evaluate( xpath, this.DOMDocument,\r
+                               this.DOMDocument.createNSResolver(this.DOMDocument.documentElement), XPathResult.ORDERED_NODE_ITERATOR_TYPE, null) ;\r
+               if ( xPathResult )\r
+               {\r
+                       var oNode = xPathResult.iterateNext() ;\r
+                       while( oNode )\r
+                       {\r
+                               aNodeArray[aNodeArray.length] = oNode ;\r
+                               oNode = xPathResult.iterateNext();\r
+                       }\r
+               }\r
+               return aNodeArray ;\r
+       }\r
+}\r
+\r
+FCKXml.prototype.SelectSingleNode = function( xpath )\r
+{\r
+       if ( navigator.userAgent.indexOf('MSIE') >= 0 )         // IE\r
+               return this.DOMDocument.selectSingleNode( xpath ) ;\r
+       else                                    // Gecko\r
+       {\r
+               var xPathResult = this.DOMDocument.evaluate( xpath, this.DOMDocument,\r
+                               this.DOMDocument.createNSResolver(this.DOMDocument.documentElement), 9, null);\r
+\r
+               if ( xPathResult && xPathResult.singleNodeValue )\r
+                       return xPathResult.singleNodeValue ;\r
+               else\r
+                       return null ;\r
+       }\r
+}\r
diff --git a/httemplate/elements/fckeditor/editor/filemanager/upload/test.html b/httemplate/elements/fckeditor/editor/filemanager/upload/test.html
new file mode 100644 (file)
index 0000000..cf29e97
--- /dev/null
@@ -0,0 +1,133 @@
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Test page for the "File Uploaders".\r
+-->\r
+<html>\r
+       <head>\r
+               <title>FCKeditor - Uploaders Tests</title>\r
+               <script language="javascript">\r
+\r
+function SendFile()\r
+{\r
+       var sUploaderUrl = cmbUploaderUrl.value ;\r
+\r
+       if ( sUploaderUrl.length == 0 )\r
+               sUploaderUrl = txtCustomUrl.value ;\r
+\r
+       if ( sUploaderUrl.length == 0 )\r
+       {\r
+               alert( 'Please provide your custom URL or select a default one' ) ;\r
+               return ;\r
+       }\r
+\r
+       eURL.innerHTML = sUploaderUrl ;\r
+       txtUrl.value = '' ;\r
+\r
+       frmUpload.action = sUploaderUrl ;\r
+       frmUpload.submit() ;\r
+}\r
+\r
+function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )\r
+{\r
+       switch ( errorNumber )\r
+       {\r
+               case 0 :        // No errors\r
+                       txtUrl.value = fileUrl ;\r
+                       alert( 'File uploaded with no errors' ) ;\r
+                       break ;\r
+               case 1 :        // Custom error\r
+                       alert( customMsg ) ;\r
+                       break ;\r
+               case 10 :       // Custom warning\r
+                       txtUrl.value = fileUrl ;\r
+                       alert( customMsg ) ;\r
+                       break ;\r
+               case 201 :\r
+                       txtUrl.value = fileUrl ;\r
+                       alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;\r
+                       break ;\r
+               case 202 :\r
+                       alert( 'Invalid file' ) ;\r
+                       break ;\r
+               case 203 :\r
+                       alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;\r
+                       break ;\r
+               default :\r
+                       alert( 'Error on file upload. Error number: ' + errorNumber ) ;\r
+                       break ;\r
+       }\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body>\r
+               <table cellSpacing="0" cellPadding="0" width="100%" border="0" height="100%">\r
+                       <tr>\r
+                               <td>\r
+                                       <table cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                                               <tr>\r
+                                                       <td nowrap>\r
+                                                               Select the "File Uploader" to use:<br>\r
+                                                               <select id="cmbUploaderUrl">\r
+                                                                       <option selected value="asp/upload.asp">ASP</option>\r
+                                                                       <option value="aspx/upload.aspx">ASP.Net</option>\r
+                                                                       <option value="cfm/upload.cfm">ColdFusion</option>\r
+                                                                       <option value="lasso/upload.lasso">Lasso</option>\r
+                                                                       <option value="php/upload.php">PHP</option>\r
+                                                                       <option value="">(Custom)</option>\r
+                                                               </select>\r
+                                                       </td>\r
+                                                       <td nowrap>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>\r
+                                                       <td width="100%">\r
+                                                               Custom Uploader URL:<BR>\r
+                                                               <input id="txtCustomUrl" style="WIDTH: 100%; BACKGROUND-COLOR: #dcdcdc" disabled type="text">\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                                       <br>\r
+                                       <table cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                                               <tr>\r
+                                                       <td noWrap>\r
+                                                               <form id="frmUpload" target="UploadWindow" enctype="multipart/form-data" action="" method="post">\r
+                                                                       Upload a new file:<br>\r
+                                                                       <input type="file" name="NewFile"><br>\r
+                                                                       <input type="button" value="Send it to the Server" onclick="SendFile();">\r
+                                                               </form>\r
+                                                       </td>\r
+                                                       <td style="WIDTH: 16px">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>\r
+                                                       <td vAlign="top" width="100%">\r
+                                                               Uploaded File URL:<br>\r
+                                                               <INPUT id="txtUrl" style="WIDTH: 100%" readonly type="text">\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                                       <br>\r
+                                       Post URL: <span id="eURL">&nbsp;</span>\r
+                               </td>\r
+                       </tr>\r
+                       <tr>\r
+                               <td height="100%">\r
+                                       <iframe name="UploadWindow" width="100%" height="100%" src="javascript:void(0)"></iframe>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/images/anchor.gif b/httemplate/elements/fckeditor/editor/images/anchor.gif
new file mode 100644 (file)
index 0000000..5aa797b
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/anchor.gif 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 (file)
index 0000000..9c59bfe
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/arrow_ltr.gif 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 (file)
index 0000000..22e8649
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/arrow_rtl.gif 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 (file)
index 0000000..a95e053
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/angel_smile.gif 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 (file)
index 0000000..c667c5d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/angry_smile.gif 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 (file)
index 0000000..938cce1
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/broken_heart.gif 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 (file)
index 0000000..f6489d7
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/cake.gif 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 (file)
index 0000000..aeb0539
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/confused_smile.gif 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 (file)
index 0000000..0758f42
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/cry_smile.gif 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 (file)
index 0000000..15518d7
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/devil_smile.gif 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 (file)
index 0000000..c431946
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/embaressed_smile.gif 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 (file)
index 0000000..66d3656
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/envelope.gif 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 (file)
index 0000000..305714f
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/heart.gif 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 (file)
index 0000000..f840ea6
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/kiss.gif 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 (file)
index 0000000..863be6e
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/lightbulb.gif 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 (file)
index 0000000..aabc7fd
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/omg_smile.gif 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 (file)
index 0000000..33f297e
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/regular_smile.gif 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 (file)
index 0000000..dfb78ef
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/sad_smile.gif 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 (file)
index 0000000..157df77
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/shades_smile.gif 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 (file)
index 0000000..26b5a55
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/teeth_smile.gif 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 (file)
index 0000000..f53ee72
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_down.gif 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 (file)
index 0000000..7e8c746
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/thumbs_up.gif 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 (file)
index 0000000..b87ec44
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/tounge_smile.gif 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 (file)
index 0000000..c074122
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gif 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 (file)
index 0000000..eefe61d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/smiley/msn/wink_smile.gif differ
diff --git a/httemplate/elements/fckeditor/editor/images/spacer.gif b/httemplate/elements/fckeditor/editor/images/spacer.gif
new file mode 100644 (file)
index 0000000..5bfd67a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/images/spacer.gif differ
diff --git a/httemplate/elements/fckeditor/editor/js/fckeditorcode_gecko.js b/httemplate/elements/fckeditor/editor/js/fckeditorcode_gecko.js
new file mode 100644 (file)
index 0000000..8d5d31a
--- /dev/null
@@ -0,0 +1,98 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ * \r
+ * == BEGIN LICENSE ==\r
+ * \r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ * \r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ * \r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ * \r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ * \r
+ * == END LICENSE ==\r
+ * \r
+ * This file has been compressed for better performance. The original source\r
+ * can be found at "editor/_source".\r
+ */\r
+\r
+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;\r
+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;};Array.prototype.AddItem=function(A){var i=this.length;this[i]=A;return i;};Array.prototype.IndexOf=function(A){for (var i=0;i<this.length;i++){if (this[i]==A) return i;};return-1;};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);}\r
+var s=navigator.userAgent.toLowerCase();var FCKBrowserInfo={IsIE:s.Contains('msie'),IsIE7:s.Contains('msie 7'),IsGecko:s.Contains('gecko/'),IsSafari:s.Contains('safari'),IsOpera:s.Contains('opera'),IsMac:s.Contains('macintosh')};(function(A){A.IsGeckoLike=(A.IsGecko||A.IsSafari||A.IsOpera);if (A.IsGecko){var B=s.match(/gecko\/(\d+)/)[1];A.IsGecko10=((B<20051111)||(/rv:1\.7/.test(s)));}else A.IsGecko10=false;})(FCKBrowserInfo);\r
+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;}})();\r
+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 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++) C=(D[i](this.Owner,B)&&C);};return C;};\r
+var FCK={Name:FCKURLParams['InstanceName'],Status:0,EditMode:0,Toolbar:null,HasFocus:false,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 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/*V*/) in A.Keystrokes) A.SetKeystrokes([CTRL+86,true]);if ((SHIFT+45/*INS*/) in A.Keystrokes) A.SetKeystrokes([SHIFT+45,true]);};this.EditingArea=new FCKEditingArea(document.getElementById('xEditingArea'));this.EditingArea.FFSpellChecker=FCKConfig.FirefoxSpellChecker;FCKListsLib.Setup();this.SetHTML(this.GetLinkedFieldValue(),true);},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:if (!FCKListsLib.BlockElements[D.nodeName.toLowerCase()]) F=true;break;case 3:if (E||D.nodeValue.Trim().length>0) F=true;};if (F){var G=D.parentNode;if (!E) E=G.insertBefore(B.createElement(A),D);E.appendChild(G.removeChild(D));D=E.nextSibling;}else{if (E){FCKDomTools.TrimNode(E);E=null;};D=D.nextSibling;}};if (E) FCKDomTools.TrimNode(E);},GetXHTML:function(A){if (FCK.EditMode==1) return FCK.EditingArea.Textarea.value;this.FixBody();var B;var C=FCK.EditorDocument;if (!C) return null;if (FCKConfig.FullPage){B=FCKXHtml.GetXHTML(C.getElementsByTagName('html')[0],true,A);if (FCK.DocTypeDeclaration&&FCK.DocTypeDeclaration.length>0) B=FCK.DocTypeDeclaration+'\n'+B;if (FCK.XmlDeclaration&&FCK.XmlDeclaration.length>0) B=FCK.XmlDeclaration+'\n'+B;}else{B=FCKXHtml.GetXHTML(C.body,false,A);if (FCKConfig.IgnoreEmptyParagraphValue&&FCKRegexLib.EmptyOutParagraph.test(B)) B='';};B=FCK.ProtectEventsRestore(B);if (FCKBrowserInfo.IsIE) B=B.replace(FCKRegexLib.ToReplace,'$1');return FCKConfig.ProtectedSource.Revert(B);},UpdateLinkedField:function(){FCK.LinkedField.value=FCK.GetXHTML(FCKConfig.FormatOutput);FCK.Events.FireEvent('OnAfterLinkedFieldUpdate');},RegisteredDoubleClickHandlers:{},OnDoubleClick:function(A){var B=FCK.RegisteredDoubleClickHandlers[A.tagName];if (B) B(A);},RegisterDoubleClickHandler:function(A,B){FCK.RegisteredDoubleClickHandlers[B.toUpperCase()]=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');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':'ABBR|XML';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;},SetHTML:function(A,B){this.EditingArea.Mode=FCK.EditMode;if (FCK.EditMode==0){A=FCKConfig.ProtectedSource.Protect(A);A=A.replace(FCKRegexLib.InvalidSelfCloseTags,'$1></$2>');A=FCK.ProtectEvents(A);A=FCK.ProtectUrls(A);A=FCK.ProtectTags(A);if (FCKBrowserInfo.IsGecko){A=A.replace(FCKRegexLib.StrongOpener,'<b$1');A=A.replace(FCKRegexLib.StrongCloser,'<\/b>');A=A.replace(FCKRegexLib.EmOpener,'<i$1');A=A.replace(FCKRegexLib.EmCloser,'<\/i>');};this._ForceResetIsDirty=(B===true);var C='';if (FCKConfig.FullPage){if (!FCKRegexLib.HeadOpener.test(A)){if (!FCKRegexLib.HtmlOpener.test(A)) A='<html dir="'+FCKConfig.ContentLangDirection+'">'+A+'</html>';A=A.replace(FCKRegexLib.HtmlOpener,'$&<head></head>');};FCK.DocTypeDeclaration=A.match(FCKRegexLib.DocTypeTag);if (FCKBrowserInfo.IsIE) C=FCK._GetBehaviorsStyle();else if (FCKConfig.ShowBorders) C='<link href="'+FCKConfig.FullBasePath+'css/fck_showtableborders_gecko.css" rel="stylesheet" type="text/css" _fcktemp="true" />';C+='<link href="'+FCKConfig.FullBasePath+'css/fck_internal.css" rel="stylesheet" type="text/css" _fcktemp="true" />';C=A.replace(FCKRegexLib.HeadCloser,C+'$&');if (FCK.TempBaseTag.length>0&&!FCKRegexLib.HasBaseTag.test(A)) C=C.replace(FCKRegexLib.HeadOpener,'$&'+FCK.TempBaseTag);}else{C=FCKConfig.DocType+'<html dir="'+FCKConfig.ContentLangDirection+'"';if (FCKBrowserInfo.IsIE&&!FCKRegexLib.Html4DocType.test(FCKConfig.DocType)) C+=' style="overflow-y: scroll"';C+='><head><title></title>'+_FCK_GetEditorAreaStyleTags()+'<link href="'+FCKConfig.FullBasePath+'css/fck_internal.css" rel="stylesheet" type="text/css" _fcktemp="true" />';if (FCKBrowserInfo.IsIE) C+=FCK._GetBehaviorsStyle();else if (FCKConfig.ShowBorders) C+='<link href="'+FCKConfig.FullBasePath+'css/fck_showtableborders_gecko.css" rel="stylesheet" type="text/css" _fcktemp="true" />';C+=FCK.TempBaseTag;var D='<body';if (FCKConfig.BodyId&&FCKConfig.BodyId.length>0) D+=' id="'+FCKConfig.BodyId+'"';if (FCKConfig.BodyClass&&FCKConfig.BodyClass.length>0) D+=' class="'+FCKConfig.BodyClass+'"';C+='</head>'+D+'>';if (FCKBrowserInfo.IsGecko&&(A.length==0||FCKRegexLib.EmptyParagraph.test(A))) C+=GECKO_BOGUS;else C+=A;C+='</body></html>';};this.EditingArea.OnLoad=_FCK_EditingArea_OnLoad;this.EditingArea.Start(C);}else{FCK.EditorWindow=null;FCK.EditorDocument=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 (FCKBrowserInfo.IsGecko) window.onresize();},HasFocus:false,RedirectNamedCommands:{},ExecuteNamedCommand:function(A,B,C){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');};FCKUndo.SaveUndoStep();},GetNamedCommandState:function(A){try{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:'';},PasteFromWord:function(){FCKDialog.OpenDialog('FCKDialog_Paste',FCKLang.PasteFromWord,'dialog/fck_paste.html',400,330,'Word');},Preview:function(){var A=FCKConfig.ScreenWidth*0.8;var B=FCKConfig.ScreenHeight*0.7;var C=(FCKConfig.ScreenWidth-A)/2;var D=window.open('',null,'toolbar=yes,location=no,status=yes,menubar=yes,scrollbars=yes,resizable=yes,width='+A+',height='+B+',left='+C);var E;if (FCKConfig.FullPage){if (FCK.TempBaseTag.length>0) E=FCK.TempBaseTag+FCK.GetXHTML();else E=FCK.GetXHTML();}else{E=FCKConfig.DocType+'<html dir="'+FCKConfig.ContentLangDirection+'"><head>'+FCK.TempBaseTag+'<title>'+FCKLang.Preview+'</title>'+_FCK_GetEditorAreaStyleTags()+'</head><body>'+FCK.GetXHTML()+'</body></html>';};D.document.write(E);D.document.close();},SwitchEditMode:function(A){var B=(FCK.EditMode==0);var C=FCK.IsDirty();var D;if (B){if (!A&&FCKBrowserInfo.IsIE) FCKUndo.SaveUndoStep();D=FCK.GetXHTML(FCKConfig.FormatSource);if (D==null) return false;}else D=this.EditingArea.Textarea.value;FCK.EditMode=B?1:0;FCK.SetHTML(D,!C);FCK.Focus();FCKTools.RunFunction(FCK.ToolbarSet.RefreshModeState,FCK.ToolbarSet);return true;},CreateElement:function(A){var e=FCK.EditorDocument.createElement(A);return FCK.InsertElementAndGetIt(e);},InsertElementAndGetIt:function(e){e.setAttribute('FCKTempLabel','true');this.InsertElement(e);var A=FCK.EditorDocument.getElementsByTagName(e.tagName);for (var i=0;i<A.length;i++){if (A[i].getAttribute('FCKTempLabel')){A[i].removeAttribute('FCKTempLabel');return A[i];}};return null;}};FCK.Events=new FCKEvents(FCK);FCK.GetHTML=FCK.GetXHTML;function _FCK_ProtectEvents_ReplaceTags(A){return A.replace(FCKRegexLib.EventAttributes,_FCK_ProtectEvents_ReplaceEvents);};function _FCK_ProtectEvents_ReplaceEvents(A,B){return ' '+B+'_fckprotectedatt="'+A.ReplaceAll([/&/g,/'/g,/"/g,/=/g,/</g,/>/g,/\r/g,/\n/g],['&apos;','&#39;','&quot;','&#61;','&lt;','&gt;','&#10;','&#13;'])+'"';};function _FCK_ProtectEvents_RestoreEvents(A,B){return B.ReplaceAll([/&#39;/g,/&quot;/g,/&#61;/g,/&lt;/g,/&gt;/g,/&#10;/g,/&#13;/g,/&apos;/g],["'",'"','=','<','>','\r','\n','&']);};function _FCK_EditingArea_OnLoad(){FCK.EditorWindow=FCK.EditingArea.Window;FCK.EditorDocument=FCK.EditingArea.Document;FCK.InitializeBehaviors();if (!FCKConfig.DisableEnterKeyHandler) FCK.EnterKeyHandler=new FCKEnterKey(FCK.EditorWindow,FCKConfig.EnterMode,FCKConfig.ShiftEnterMode);FCK.KeystrokeHandler.AttachToElement(FCK.EditorDocument);if (FCK._ForceResetIsDirty) FCK.ResetIsDirty();if (FCKBrowserInfo.IsIE&&FCK.HasFocus) FCK.EditorDocument.body.setActive();FCK.OnAfterSetHTML();if (FCK.Status!=0) return;FCK.SetStatus(1);};function _FCK_GetEditorAreaStyleTags(){var A='';var B=FCKConfig.EditorAreaCSS;for (var i=0;i<B.length;i++) A+='<link href="'+B[i]+'" rel="stylesheet" type="text/css" />';return A;};function _FCK_KeystrokeHandler_OnKeystroke(A,B){if (FCK.Status!=2) return false;if (FCK.EditMode==0){if (B=='Paste') return!FCK.Events.FireEvent('OnPaste');}else{if (B.Equals('Paste','Undo','Redo','SelectAll')) return false;};var C=FCK.Commands.GetCommand(B);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;}};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 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(){FCK.Focus();FCKFocusManager_Win_OnFocus();};function FCKFocusManager_Win_OnFocus(){FCKFocusManager._ResetTimer();if (!FCK.HasFocus&&!FCKFocusManager.IsLocked){FCK.HasFocus=true;FCK.Events.FireEvent("OnFocus");}};\r
+FCK.Description="FCKeditor for Gecko Browsers";FCK.InitializeBehaviors=function(){if (FCKBrowserInfo.IsGecko) Window_OnResize();FCKFocusManager.AddWindow(this.EditorWindow);this.ExecOnSelectionChange=function(){FCK.Events.FireEvent("OnSelectionChange");};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);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,Cut:true,Copy:true};FCK.ExecuteRedirectedNamedCommand=function(A,B){switch (A){case 'Print':FCK.EditorWindow.print();break;case 'Paste':try                      { if (FCK.Paste()) FCK.ExecuteNamedCommand('Paste',null,true);}catch (e)        { FCKDialog.OpenDialog('FCKDialog_Paste',FCKLang.Paste,'dialog/fck_paste.html',400,330,'Security');};break;case 'Cut':try                       { FCK.ExecuteNamedCommand('Cut',null,true);}catch (e)   { alert(FCKLang.PasteErrorCut);};break;case 'Copy':try                  { FCK.ExecuteNamedCommand('Copy',null,true);}catch (e)  { alert(FCKLang.PasteErrorCopy);};break;default:FCK.ExecuteNamedCommand(A,B);}};FCK.Paste=function(){if (FCKConfig.ForcePasteAsPlainText){FCK.PasteAsPlainText();return false;};return true;};FCK.InsertHtml=function(A){A=FCKConfig.ProtectedSource.Protect(A);A=FCK.ProtectEvents(A);A=FCK.ProtectUrls(A);A=FCK.ProtectTags(A);A=A.replace(FCKRegexLib.StrongOpener,'<b$1');A=A.replace(FCKRegexLib.StrongCloser,'<\/b>');A=A.replace(FCKRegexLib.EmOpener,'<i$1');A=A.replace(FCKRegexLib.EmCloser,'<\/i>');var B=FCKSelection.Delete();var C=B.getRangeAt(0);var D=C.createContextualFragment(A);var E=D.lastChild;C.insertNode(D);FCKSelection.SelectNode(E);FCKSelection.Collapse(false);this.Focus();};FCK.InsertElement=function(A){var B=FCKSelection.Delete();var C=B.getRangeAt(0);C.insertNode(A);FCKSelection.SelectNode(A);FCKSelection.Collapse(false);this.Focus();};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){var B=[];FCK.ExecuteNamedCommand('Unlink');if (A.length>0){var C='javascript:void(0);/*'+(new Date().getTime())+'*/';FCK.ExecuteNamedCommand('CreateLink',C);var D=this.EditorDocument.evaluate("//a[@href='"+C+"']",this.EditorDocument.body,null,XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,null);for (var i=0;i<D.snapshotLength;i++){var E=D.snapshotItem(i);E.href=A;B.push(E);}};return B;};\r
+var FCKConfig=FCK.Config={};if (document.location.protocol=='file:'){FCKConfig.BasePath=decodeURIComponent(document.location.pathname.substr(1));FCKConfig.BasePath=FCKConfig.BasePath.replace(/\\/gi, '/');FCKConfig.BasePath='file://'+FCKConfig.BasePath.substring(0,FCKConfig.BasePath.lastIndexOf('/')+1);FCKConfig.FullBasePath=FCKConfig.BasePath;}else{FCKConfig.BasePath=document.location.pathname.substring(0,document.location.pathname.lastIndexOf('/')+1);FCKConfig.FullBasePath=document.location.protocol+'//'+document.location.host+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]=parseInt(E,10);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) {/*Ignore it. Much probably we are inside a FRAME where the "top" is in another domain (security error).*/}};if (!A.PluginsPath.EndsWith('/')) A.PluginsPath+='/';if (typeof(A.EditorAreaCSS)=='string') A.EditorAreaCSS=[A.EditorAreaCSS];var B=A.ToolbarComboPreviewCSS;if (!B||B.length==0) A.ToolbarComboPreviewCSS=A.EditorAreaCSS;else if (typeof(B)=='string') A.ToolbarComboPreviewCSS=[B];};FCKConfig.ToolbarSets={};FCKConfig.Plugins={};FCKConfig.Plugins.Items=[];FCKConfig.Plugins.Add=function(A,B,C){FCKConfig.Plugins.Items.AddItem([A,B,C]);};FCKConfig.ProtectedSource={};FCKConfig.ProtectedSource.RegexEntries=[/<!--[\s\S]*?-->/g,/<script[\s\S]*?<\/script>/gi,/<noscript[\s\S]*?<\/noscript>/gi,/<object[\s\S]+?<\/object>/gi];FCKConfig.ProtectedSource.Add=function(A){this.RegexEntries.AddItem(A);};FCKConfig.ProtectedSource.Protect=function(A){function _Replace(protectedSource){var B=FCKTempBin.AddElement(protectedSource);return '<!--{PS..'+B+'}-->';};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);};return A.replace(/(<|&lt;)!--\{PS..(\d+)\}--(>|&gt;)/g,_Replace);}\r
+var FCKDebug={};FCKDebug._GetWindow=function(){if (!this.DebugWindow||this.DebugWindow.closed) this.DebugWindow=window.open(FCKConfig.BasePath+'fckdebug.html','FCKeditorDebug','menubar=no,scrollbars=yes,resizable=yes,location=no,toolbar=no,width=600,height=500',true);return this.DebugWindow;};FCKDebug.Output=function(A,B,C){if (!FCKConfig.Debug) return;try{this._GetWindow().Output(A,B);}catch (e) {}};FCKDebug.OutputObject=function(A,B){if (!FCKConfig.Debug) return;try{this._GetWindow().OutputObject(A,B);}catch (e) {}}\r
+var FCKDomTools={MoveChildren:function(A,B){if (A==B) return;var C;while ((C=A.firstChild)) B.appendChild(A.removeChild(C));},TrimNode:function(A,B){this.LTrimNode(A);this.RTrimNode(A,B);},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,B){var C;while ((C=A.lastChild)){switch (C.nodeType){case 1:if (C.nodeName.toUpperCase()=='BR'&&(B||C.getAttribute('type',2)=='_moz')){C.parentNode.removeChild(C);continue;};break;case 3:var D=C.nodeValue.RTrim();var E=C.nodeValue.length;if (D.length==0){C.parentNode.removeChild(C);continue;}else if (D.length<E){C.splitText(D.length);A.lastChild.parentNode.removeChild(A.lastChild);}};break;}},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){if (!A) return null;if (A.nextSibling) A=A.nextSibling;else return this.GetNextSourceElement(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.firstChild) A=A.firstChild;else return this.GetNextSourceElement(A,B,C,D);};return null;},InsertAfterNode:function(A,B){return A.parentNode.insertBefore(B,A.nextSibling);},GetParents:function(A){var B=[];while (A){B.splice(0,0,A);A=A.parentNode;};return B;},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;}};\r
+var GECKO_BOGUS='<br type="_moz">';var FCKTools={};FCKTools.CreateBogusBR=function(A){var B=A.createElement('br');B.setAttribute('type','_moz');return B;};FCKTools.AppendStyleSheet=function(A,B){if (typeof(B)=='string') return this._AppendStyleSheet(A,B);else{var C=[];for (var i=0;i<B.length;i++) C.push(this._AppendStyleSheet(A,B[i]));return C;}};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){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.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||'CSS1Compat'));};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.GetLastItem=function(A){if (A.length>0) return A[A.length-1];return null;};\r
+FCKTools.CancelEvent=function(e){if (e) e.preventDefault();};FCKTools.DisableSelection=function(A){if (FCKBrowserInfo.IsGecko) A.style.MozUserSelect='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.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':return document.implementation.createDocument('','',null);};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={};if (A.className.length>0){B.Class=A.className;A.className='';};var C=A.getAttribute('style');if (C&&C.length>0){B.Inline=C;A.setAttribute('style','',0);};return B;};FCKTools.RestoreStyles=function(A,B){A.className=B.Class||'';if (B.Inline) A.setAttribute('style',B.Inline,0);else A.removeAttribute('style',0);};FCKTools.RegisterDollarFunction=function(A){A.$=function(id){return this.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);while (A){var E=D.getComputedStyle(A,'').position;if (E&&E!='static'&&A.style.zIndex!=FCKConfig.FloatingPanelsZIndex) break;c.X+=A.offsetLeft-A.scrollLeft;c.Y+=A.offsetTop-A.scrollTop;if (A.offsetParent) A=A.offsetParent;else{if (D!=C){A=D.frameElement;if (A) D=FCKTools.GetElementWindow(A);}else{c.X+=A.scrollLeft;c.Y+=A.scrollTop;break;}}};return c;}\r
+var FCKeditorAPI;function InitializeAPI(){var A=window.parent;if (!(FCKeditorAPI=A.FCKeditorAPI)){var B='var FCKeditorAPI = {Version : "2.4.3",VersionBuild : "15657",__Instances : new Object(),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       : {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.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[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(){delete FCKeditorAPI.__Instances[FCK.Name];};FCKTools.AddEventListener(window,'unload',FCKeditorAPI_Cleanup);\r
+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');B.onload=B.onerror=_FCKImagePreloader_OnImage;B._FCKImagePreloader=this;B.src=A[i];_FCKImagePreloader_ImageCache.push(B);}}};var _FCKImagePreloader_ImageCache=[];function _FCKImagePreloader_OnImage(){var A=this._FCKImagePreloader;if ((--A._PreloadCount)==0&&A.OnComplete) A.OnComplete();this._FCKImagePreloader=null;}\r
+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,BodyContents:/([\s\S]*\<body[^\>]*\>)([\s\S]*)(\<\/body\>[\s\S]*)/i,ToReplace:/___fcktoreplace:([\w]+)/ig,MetaHttpEquiv:/http-equiv\s*=\s*["']?([^"' ]+)/i,HasBaseTag:/<base /i,HtmlOpener:/<html\s?[^>]*>/i,HeadOpener:/<head\s?[^>]*>/i,HeadCloser:/<\/head\s*>/i,FCK_Class:/(\s*FCK__[A-Za-z]*\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;)(<\/\1>)?$/,TagBody:/></,StrongOpener:/<STRONG([ \>])/gi,StrongCloser:/<\/STRONG>/gi,EmOpener:/<EM([ \>])/gi,EmCloser:/<\/EM>/gi,GeckoEntitiesMarker:/#\?-\:/g,ProtectUrlsImg:/<img(?=\s).*?\ssrc=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,ProtectUrlsA:/<a(?=\s).*?\shref=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,Html4DocType:/HTML 4\.0 Transitional/i,DocTypeTag:/<!DOCTYPE[^>]*>/i,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};\r
+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,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,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 },EmptyElements:{ base: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,ol:1,ul:1,li:1,dt:1,de:1 },PathBlockLimitElements:{ body:1,td:1,th:1,caption:1,form:1 },Setup:function(){if (FCKConfig.EnterMode=='div') this.PathBlockElements.div=1;else this.PathBlockLimitElements.div=1;}};\r
+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',gl:'Galician',he:'Hebrew',hi:'Hindi',hr:'Croatian',hu:'Hungarian',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);eval('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);},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];}};\r
+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','◊':'loz','♠':'spades','♣':'clubs','♥':'hearts','♦':'diams','"':'quot','ˆ':'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'};for (e in B){FCKXHtmlEntities.Entities[e]=B[e];A+=e;};B=null;}}else{FCKXHtmlEntities.Entities={};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');}\r
+var FCKXHtml={};FCKXHtml.CurrentJobNum=0;FCKXHtml.GetXHTML=function(A,B,C){FCKXHtmlEntities.Initialize();this._NbspEntity=(FCKConfig.ProcessHTMLEntities?'nbsp':'#160');var D=FCK.IsDirty();this._CreateNode=FCKConfig.ForceStrongEm?FCKXHtml_CreateNode_StrongEm:FCKXHtml_CreateNode_Normal;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);var E=this._GetMainXmlString();this.XML=null;E=E.substr(7,E.length-15).Trim();if (FCKBrowserInfo.IsGecko) E=E.replace(/<br\/>$/,'');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();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) FCKDomTools.TrimNode(A,true);if (A.childNodes.length==0){if (C&&FCKConfig.FillEmptyBlocks){this._AppendEntity(A,this._NbspEntity);return A;};var E=A.nodeName;if (FCKListsLib.InlineChildReqElements[E]) return null;if (!FCKListsLib.EmptyElements[E]) A.appendChild(this.XML.createTextNode(''));};return A;};FCKXHtml._AppendNode=function(A,B){if (!B) return false;switch (B.nodeType){case 1:if (B.getAttribute('_fckfakelement')) return FCKXHtml._AppendNode(A,FCK.GetRealElement(B));if (FCKBrowserInfo.IsGecko&&B.hasAttribute('_moz_editor_bogus_node')) return false;if (B.getAttribute('_fcktemp')) return false;var C=B.tagName.toLowerCase();if (FCKBrowserInfo.IsIE){if (B.scopeName&&B.scopeName!='HTML'&&B.scopeName!='FCK') C=B.scopeName.toLowerCase()+':'+C;}else{if (C.StartsWith('fck:')) C=C.Remove(0,4);};if (!FCKRegexLib.ElementName.test(C)) return false;if (C=='br'&&B.getAttribute('type',2)=='_moz') return false;if (B._fckxhtmljob&&B._fckxhtmljob==FCKXHtml.CurrentJobNum) return false;var D=this._CreateNode(C);FCKXHtml._AppendAttributes(A,B,D,C);B._fckxhtmljob=FCKXHtml.CurrentJobNum;var E=FCKXHtml.TagProcessors[C];if (E) D=E(D,B,A);else D=this._AppendChildNodes(D,B,Boolean(FCKListsLib.NonEmptyBlockElements[C]));if (!D) return false;A.appendChild(D);break;case 3: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) {/*Do nothing... probably this is a wrong format comment.*/};break;default:A.appendChild(this.XML.createComment("Element not supported - Type: "+B.nodeType+" Name: "+B.nodeName));break;};return true;};function FCKXHtml_CreateNode_StrongEm(A){switch (A){case 'b':A='strong';break;case 'i':A='em';break;};return this.XML.createElement(A);};function FCKXHtml_CreateNode_Normal(A){return this.XML.createElement(A);};FCKXHtml._AppendSpecialItem=function(A){return '___FCKsi___'+FCKXHtml.SpecialBlocks.AddItem(A);};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._RemoveAttribute=function(A,B,C){var D=A.attributes.getNamedItem(C);if (D&&B.test(D.nodeValue)){var E=D.nodeValue.replace(B,'');if (E.length==0) A.attributes.removeNamedItem(C);else D.nodeValue=E;}};FCKXHtml.TagProcessors={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);return A;},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){FCKXHtml._RemoveAttribute(A,FCKRegexLib.FCK_Class,'class');if (B.name) FCKXHtml._AppendAttribute(A,'name',B.name);};A=FCKXHtml._AppendChildNodes(A,B,false);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;},style:function(A,B){if (!A.attributes.getNamedItem('type')) FCKXHtml._AppendAttribute(A,'type','text/css');A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem(B.innerHTML)));return A;},title:function(A,B){A.appendChild(FCKXHtml.XML.createTextNode(FCK.EditorDocument.title));return A;},table:function(A,B){if (FCKBrowserInfo.IsIE) FCKXHtml._RemoveAttribute(A,FCKRegexLib.FCK_Class,'class');A=FCKXHtml._AppendChildNodes(A,B,false);return A;},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;},span:function(A,B){if (B.innerHTML.length==0) return false;A=FCKXHtml._AppendChildNodes(A,B,false);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;}};FCKXHtml.TagProcessors.ul=FCKXHtml.TagProcessors.ol;\r
+FCKXHtml._GetMainXmlString=function(){var A=new XMLSerializer();return A.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;else if (E.nodeValue===true) G=F;else G=B.getAttribute(F,2);this._AppendAttribute(C,F,G);}}}\r
+var FCKCodeFormatter={};FCKCodeFormatter.Init=function(){var A=this.Regex={};A.BlocksOpener=/\<(P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|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|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)[ \/\>]/i;A.DecreaseIndent=/^\<\/(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR|UL|OL)[ \>]/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.AddItem(C)+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();}\r
+var FCKUndo={};FCKUndo.SaveUndoStep=function(){}\r
+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.childNodes.length>0) C.removeChild(C.childNodes[0]);if (this.Mode==0){var E=this.IFrame=D.createElement('iframe');E.src='javascript:void(0)';E.frameBorder=0;E.width=E.height='100%';C.appendChild(E);if (FCKBrowserInfo.IsIE) A=A.replace(/(<base[^>]*?)\s*\/?>(?!\s*<\/base>)/gi,'$1></base>');else if (!B){if (FCKBrowserInfo.IsGecko) A=A.replace(/(<body[^>]*>)\s*(<\/body>)/i,'$1'+GECKO_BOGUS+'$2');var F=A.match(FCKRegexLib.BodyContents);if (F){A=F[1]+'&nbsp;'+F[3];this._BodyHTML=F[2];}else this._BodyHTML=A;};this.Window=E.contentWindow;var G=this.Document=this.Window.document;G.open();G.write(A);G.close();if (FCKBrowserInfo.IsGecko10&&!B){this.Start(A,true);return;};this.Window._FCKEditingArea=this;if (FCKBrowserInfo.IsGecko10) this.Window.setTimeout(FCKEditingArea_CompleteStart,500);else FCKEditingArea_CompleteStart.call(this.Window);}else{var H=this.Textarea=D.createElement('textarea');H.className='SourceField';H.dir='ltr';H.style.width=H.style.height='100%';H.style.border='none';C.appendChild(H);H.value=A;FCKTools.RunFunction(this.OnLoad);}};function FCKEditingArea_CompleteStart(){if (!this.document.body){this.setTimeout(FCKEditingArea_CompleteStart,50);return;};var A=this._FCKEditingArea;A.MakeEditable();FCKTools.RunFunction(A.OnLoad);};FCKEditingArea.prototype.MakeEditable=function(){var A=this.Document;if (FCKBrowserInfo.IsIE){A.body.contentEditable=true;}else{try{A.body.spellcheck=(this.FFSpellChecker!==false);if (this._BodyHTML){A.body.innerHTML=this._BodyHTML;this._BodyHTML=null;};A.designMode='on';try{A.execCommand('styleWithCSS',false,FCKConfig.GeckoUseSPAN);}catch (e){A.execCommand('useCSS',false,!FCKConfig.GeckoUseSPAN);};A.execCommand('enableObjectResizing',false,!FCKConfig.DisableObjectResizing);A.execCommand('enableInlineTableEditing',false,!FCKConfig.DisableFFTableHandles);}catch (e) {}}};FCKEditingArea.prototype.Focus=function(){try{if (this.Mode==0){if (FCKBrowserInfo.IsIE&&this.Document.hasFocus()) return;if (FCKBrowserInfo.IsSafari) this.IFrame.focus();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) {}};function FCKEditingArea_Cleanup(){this.TargetElement=null;this.IFrame=null;this.Document=null;this.Textarea=null;if (this.Window){this.Window._FCKEditingArea=null;this.Window=null;}};\r
+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 (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;}\r
+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;}};\r
+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 (!C){if (!B&&FCKListsLib.PathBlockElements[E]!=null) B=e;if (FCKListsLib.PathBlockLimitElements[E]!=null) C=e;};D.push(e);if (E=='body') break;};e=e.parentNode;};this.Block=B;this.BlockLimit=C;this.Elements=D;};\r
+var FCKDomRange=function(A){this.Window=A;};FCKDomRange.prototype={_UpdateElementInfo:function(){if (!this._Range) this.Release(true);else{var A=this._Range.startContainer;var B=this._Range.endContainer;var C=new FCKElementPath(A);this.StartContainer=C.LastElement;this.StartBlock=C.Block;this.StartBlockLimit=C.BlockLimit;if (A!=B) C=new FCKElementPath(B);this.EndContainer=C.LastElement;this.EndBlock=C.Block;this.EndBlockLimit=C.BlockLimit;}},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;}},CheckIsCollapsed:function(){if (this._Range) return this._Range.collapsed;},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 ((B=A.firstChild)&&B.nodeType==1&&FCKListsLib.EmptyElements[B.nodeName.toLowerCase()]==null) A=B;this.MoveToElementStart(A);},InsertNode:function(A){if (this._Range) this._Range.insertNode(A);},CheckIsEmpty:function(A){if (this.CheckIsCollapsed()) return true;var B=this.Window.document.createElement('div');this._Range.cloneContents().AppendTo(B);FCKDomTools.TrimNode(B,A);return (B.innerHTML.length==0);},CheckStartOfBlock:function(){var A=this.Clone();A.Collapse(true);A.SetStart(A.StartBlock||A.StartBlockLimit,1);var B=A.CheckIsEmpty();A.Release();return B;},CheckEndOfBlock:function(A){var B=this.Clone();B.Collapse(false);B.SetEnd(B.EndBlock||B.EndBlockLimit,2);var C=B.CheckIsCollapsed();if (!C){var D=this.Window.document.createElement('div');B._Range.cloneContents().AppendTo(D);FCKDomTools.TrimNode(D,true);C=true;var E=D;while ((E=E.lastChild)){if (E.previousSibling||E.nodeType!=1||FCKListsLib.InlineChildReqElements[E.nodeName.toLowerCase()]==null){C=false;break;}}};B.Release();if (A) this.Select();return C;},CreateBookmark:function(){var A={StartId:'fck_dom_range_start_'+(new Date()).valueOf()+'_'+Math.floor(Math.random()*1000),EndId:'fck_dom_range_end_'+(new Date()).valueOf()+'_'+Math.floor(Math.random()*1000)};var B=this.Window.document;var C;var D;if (!this.CheckIsCollapsed()){C=B.createElement('span');C.id=A.EndId;C.innerHTML='&nbsp;';D=this.Clone();D.Collapse(false);D.InsertNode(C);};C=B.createElement('span');C.id=A.StartId;C.innerHTML='&nbsp;';D=this.Clone();D.Collapse(true);D.InsertNode(C);return A;},MoveToBookmark:function(A,B){var C=this.Window.document;var D=C.getElementById(A.StartId);var E=C.getElementById(A.EndId);this.SetStart(D,3);if (!B) FCKDomTools.RemoveNode(D);if (E){this.SetEnd(E,3);if (!B) FCKDomTools.RemoveNode(E);}else this.Collapse(true);},SetStart:function(A,B){var C=this._Range;if (!C) C=this._Range=this.CreateRange();switch(B){case 1:C.setStart(A,0);break;case 2:C.setStart(A,A.childNodes.length);break;case 3:C.setStartBefore(A);break;case 4:C.setStartAfter(A);};this._UpdateElementInfo();},SetEnd:function(A,B){var C=this._Range;if (!C) C=this._Range=this.CreateRange();switch(B){case 1:C.setEnd(A,0);break;case 2:C.setEnd(A,A.childNodes.length);break;case 3:C.setEndBefore(A);break;case 4:C.setEndAfter(A);};this._UpdateElementInfo();},Expand:function(A){var B,oSibling;switch (A){case 'block_contents':if (this.StartBlock) this.SetStart(this.StartBlock,1);else{B=this._Range.startContainer;if (B.nodeType==1){if (!(B=B.childNodes[this._Range.startOffset])) B=B.firstChild;};if (!B) return;while (true){oSibling=B.previousSibling;if (!oSibling){if (B.parentNode!=this.StartBlockLimit) B=B.parentNode;else break;}else if (oSibling.nodeType!=1||!(/^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/).test(oSibling.nodeName.toUpperCase())){B=oSibling;}else break;};this._Range.setStartBefore(B);};if (this.EndBlock) this.SetEnd(this.EndBlock,2);else{B=this._Range.endContainer;if (B.nodeType==1) B=B.childNodes[this._Range.endOffset]||B.lastChild;if (!B) return;while (true){oSibling=B.nextSibling;if (!oSibling){if (B.parentNode!=this.EndBlockLimit) B=B.parentNode;else break;}else if (oSibling.nodeType!=1||!(/^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/).test(oSibling.nodeName.toUpperCase())){B=oSibling;}else break;};this._Range.setEndAfter(B);};this._UpdateElementInfo();}},Release:function(A){if (!A) this.Window=null;this.StartContainer=null;this.StartBlock=null;this.StartBlockLimit=null;this.EndContainer=null;this.EndBlock=null;this.EndBlockLimit=null;this._Range=null;}};\r
+FCKDomRange.prototype.MoveToSelection=function(){this.Release(true);var A=this.Window.getSelection();if (A.rangeCount==1){this._Range=FCKW3CRange.CreateFromRange(this.Window.document,A.getRangeAt(0));this._UpdateElementInfo();}};FCKDomRange.prototype.Select=function(){var A=this._Range;if (A){var B=this.Window.document.createRange();B.setStart(A.startContainer,A.startOffset);try{B.setEnd(A.endContainer,A.endOffset);}catch (e){if (e.toString().Contains('NS_ERROR_ILLEGAL_VALUE')){A.collapse(true);B.setEnd(A.endContainer,A.endOffset);}else throw(e);};var C=this.Window.getSelection();C.removeAllRanges();C.addRange(B);}};\r
+var FCKDocumentFragment=function(A,B){this.RootNode=B||A.createDocumentFragment();};FCKDocumentFragment.prototype={AppendTo:function(A){A.appendChild(this.RootNode);},InsertAfterNode:function(A){FCKDomTools.InsertAfterNode(A,this.RootNode);}}\r
+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 (C.childNodes.length>0&&E<=C.childNodes.length-1){if (E==0){C=C.insertBefore(this._Document.createTextNode(''),C.firstChild);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)) this.setStart(topEnd.parentNode,FCKDomTools.GetIndexOf(topEnd));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);},toString:function(){var A=this.cloneContents();var B=this._Document.createElement('div');A.AppendTo(B);return B.textContent||B.innerText;}};\r
+var FCKEnterKey=function(A,B,C){this.Window=A;this.EnterMode=B||'p';this.ShiftEnterMode=C||'br';var D=new FCKKeystrokeHandler(false);D._EnterKey=this;D.OnKeystroke=FCKEnterKey_OnKeystroke;D.SetKeystrokes([[13,'Enter'],[SHIFT+13,'ShiftEnter'],[8,'Backspace'],[46,'Delete']]);D.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();}}catch (e){};return false;};FCKEnterKey.prototype.DoEnter=function(A,B){this._HasShift=(B===true);var C=A||this.EnterMode;if (C=='br') return this._ExecuteEnterBr();else return this._ExecuteEnterBlock(C);};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 (!B.CheckIsCollapsed()) return false;var C=B.StartBlock;var D=B.EndBlock;if (B.StartBlockLimit==B.EndBlockLimit&&C&&D){if (!B.CheckIsCollapsed()){var E=B.CheckEndOfBlock();B.DeleteContents();if (C!=D){B.SetStart(D,1);B.SetEnd(D,1);};B.Select();A=(C==D);};if (B.CheckStartOfBlock()){var F=B.StartBlock;var G=FCKDomTools.GetPreviousSourceElement(F,true,['BODY',B.StartBlockLimit.nodeName],['UL','OL']);A=this._ExecuteBackspace(B,G,F);}else if (FCKBrowserInfo.IsGecko){B.Select();}};B.Release();return A;};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.TrimNode(C);FCKDomTools.TrimNode(B);A.SetStart(B,2);A.Collapse(true);var I=A.CreateBookmark();FCKDomTools.MoveChildren(C,B);A.MoveToBookmark(I);A.Select();D=true;}};return D;};FCKEnterKey.prototype.DoDelete=function(){var A=false;var B=new FCKDomRange(this.Window);B.MoveToSelection();if (B.CheckIsCollapsed()&&B.CheckEndOfBlock(FCKBrowserInfo.IsGeckoLike)){var C=B.StartBlock;var D=FCKDomTools.GetNextSourceElement(C,true,[B.StartBlockLimit.nodeName],['UL','OL']);A=this._ExecuteBackspace(B,C,D);};B.Release();return A;};FCKEnterKey.prototype._ExecuteEnterBlock=function(A,B){var C=B||new FCKDomRange(this.Window);if (!B) C.MoveToSelection();if (C.StartBlockLimit==C.EndBlockLimit){if (!C.StartBlock) this._FixBlock(C,true,A);if (!C.EndBlock) this._FixBlock(C,false,A);var D=C.StartBlock;var E=C.EndBlock;if (!C.CheckIsEmpty()) C.DeleteContents();if (D==E){var F;var G=C.CheckStartOfBlock();var H=C.CheckEndOfBlock();if (G&&!H){F=D.cloneNode(false);if (FCKBrowserInfo.IsGeckoLike) F.innerHTML=GECKO_BOGUS;D.parentNode.insertBefore(F,D);if (FCKBrowserInfo.IsIE){C.MoveToNodeContents(F);C.Select();};C.MoveToElementEditStart(D);}else{if (H){var I=D.tagName.toUpperCase();if (G&&I=='LI'){this._OutdentWithSelection(D,C);C.Release();return true;}else{if ((/^H[1-6]$/).test(I)||this._HasShift) F=this.Window.document.createElement(A);else{F=D.cloneNode(false);this._RecreateEndingTree(D,F);};if (FCKBrowserInfo.IsGeckoLike){F.innerHTML=GECKO_BOGUS;if (G) D.innerHTML=GECKO_BOGUS;}}}else{C.SetEnd(D,2);var J=C.ExtractContents();F=D.cloneNode(false);FCKDomTools.TrimNode(J.RootNode);if (J.RootNode.firstChild.nodeType==1&&J.RootNode.firstChild.tagName.toUpperCase().Equals('UL','OL')) F.innerHTML=GECKO_BOGUS;J.AppendTo(F);if (FCKBrowserInfo.IsGecko){this._AppendBogusBr(D);this._AppendBogusBr(F);}};if (F){FCKDomTools.InsertAfterNode(D,F);C.MoveToElementEditStart(F);if (FCKBrowserInfo.IsGeckoLike) F.scrollIntoView(false);}}}else{C.MoveToElementEditStart(E);};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;if (!F&&E=='LI') return this._ExecuteEnterBlock(null,B);if (!F&&D&&(/^H[1-6]$/).test(E)){FCKDebug.Output('BR - Header');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{FCKDebug.Output('BR - No Header');var G=this.Window.document.createElement('br');B.InsertNode(G);if (FCKBrowserInfo.IsGecko) FCKDomTools.InsertAfterNode(G,this.Window.document.createTextNode(''));if (D&&FCKBrowserInfo.IsGeckoLike) this._AppendBogusBr(G.parentNode);if (FCKBrowserInfo.IsIE) B.SetStart(G,4);else B.SetStart(G.nextSibling,1);};B.Collapse(true);B.Select();};B.Release();return true;};FCKEnterKey.prototype._FixBlock=function(A,B,C){var D=A.CreateBookmark();A.Collapse(B);A.Expand('block_contents');var E=this.Window.document.createElement(C);A.ExtractContents().AppendTo(E);FCKDomTools.TrimNode(E);A.InsertNode(E);A.MoveToBookmark(D);};FCKEnterKey.prototype._AppendBogusBr=function(A){if (!A) return;var B=FCKTools.GetLastItem(A.getElementsByTagName('br'));if (!B||B.getAttribute('type',2)!='_moz') A.appendChild(FCKTools.CreateBogusBR(this.Window.document));};FCKEnterKey.prototype._RecreateEndingTree=function(A,B){while ((A=A.lastChild)&&A.nodeType==1&&FCKListsLib.InlineChildReqElements[A.nodeName.toLowerCase()]!=null) B=B.insertBefore(A.cloneNode(false),B.firstChild);};FCKEnterKey.prototype._OutdentWithSelection=function(A,B){var C=B.CreateBookmark();FCKListHandler.OutdentListItem(A);B.MoveToBookmark(C);B.Select();}\r
+var FCKDocumentProcessor={};FCKDocumentProcessor._Items=[];FCKDocumentProcessor.AppendNew=function(){var A={};this._Items.AddItem(A);return A;};FCKDocumentProcessor.Process=function(A){var B,i=0;while((B=this._Items[i++])) B.ProcessDocument(A);};var FCKDocumentProcessor_CreateFakeImage=function(A,B){var C=FCK.EditorDocument.createElement('IMG');C.className=A;C.src=FCKConfig.FullBasePath+'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 FCKFlashProcessor=FCKDocumentProcessor.AppendNew();FCKFlashProcessor.ProcessDocument=function(A){var B=A.getElementsByTagName('EMBED');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){var D=C.attributes['type'];if ((C.src&&C.src.EndsWith('.swf',true))||(D&&D.nodeValue=='application/x-shockwave-flash')){var E=C.cloneNode(true);if (FCKBrowserInfo.IsIE){var F=['scale','play','loop','menu','wmode','quality'];for (var G=0;G<F.length;G++){var H=C.getAttribute(F[G]);if (H) E.setAttribute(F[G],H);};E.setAttribute('type',D.nodeValue);};var I=FCKDocumentProcessor_CreateFakeImage('FCK__Flash',E);I.setAttribute('_fckflash','true',0);FCKFlashProcessor.RefreshView(I,C);C.parentNode.insertBefore(I,C);C.parentNode.removeChild(C);}}};FCKFlashProcessor.RefreshView=function(A,B){if (B.getAttribute('width')>0) A.style.width=FCKTools.ConvertHtmlSizeToStyle(B.getAttribute('width'));if (B.getAttribute('height')>0) A.style.height=FCKTools.ConvertHtmlSizeToStyle(B.getAttribute('height'));};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);}}}\r
+var FCKSelection=FCK.Selection={};\r
+FCKSelection.GetType=function(){this._Type='Text';var A;try { A=FCK.EditorWindow.getSelection();}catch (e) {};if (A&&A.rangeCount==1){var B=A.getRangeAt(0);if (B.startContainer==B.endContainer&&(B.endOffset-B.startOffset)==1&&B.startContainer.nodeType!=Node.TEXT_NODE) this._Type='Control';};return this._Type;};FCKSelection.GetSelectedElement=function(){if (this.GetType()=='Control'){var A=FCK.EditorWindow.getSelection();return A.anchorNode.childNodes[A.anchorOffset];};return null;};FCKSelection.GetParentElement=function(){if (this.GetType()=='Control') return FCKSelection.GetSelectedElement().parentNode;else{var A=FCK.EditorWindow.getSelection();if (A){var B=A.anchorNode;while (B&&B.nodeType!=1) B=B.parentNode;return B;}};return null;};FCKSelection.SelectNode=function(A){var B=FCK.EditorDocument.createRange();B.selectNode(A);var C=FCK.EditorWindow.getSelection();C.removeAllRanges();C.addRange(B);};FCKSelection.Collapse=function(A){var B=FCK.EditorWindow.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=FCK.EditorWindow.getSelection().getRangeAt(0).startContainer;}catch(e){}};while (B){if (B.nodeType==1&&B.tagName==A) return true;B=B.parentNode;};return false;};FCKSelection.MoveToAncestorNode=function(A){var B;var C=this.GetSelectedElement();if (!C) C=FCK.EditorWindow.getSelection().getRangeAt(0).startContainer;while (C){if (C.nodeName==A) return C;C=C.parentNode;};return null;};FCKSelection.Delete=function(){var A=FCK.EditorWindow.getSelection();for (var i=0;i<A.rangeCount;i++){A.getRangeAt(i).deleteContents();};return A;};\r
+var FCKTableHandler={};FCKTableHandler.InsertRow=function(){var A=FCKSelection.MoveToAncestorNode('TR');if (!A) return;var B=A.cloneNode(true);A.parentNode.insertBefore(B,A);FCKTableHandler.ClearRow(A);};FCKTableHandler.DeleteRows=function(A){if (!A) A=FCKSelection.MoveToAncestorNode('TR');if (!A) return;var B=FCKTools.GetElementAscensor(A,'TABLE');if (B.rows.length==1){FCKTableHandler.DeleteTable(B);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();A.parentNode.removeChild(A);};FCKTableHandler.InsertColumn=function(){var A=FCKSelection.MoveToAncestorNode('TD')||FCKSelection.MoveToAncestorNode('TH');if (!A) return;var B=FCKTools.GetElementAscensor(A,'TABLE');var C=A.cellIndex+1;for (var i=0;i<B.rows.length;i++){var D=B.rows[i];if (D.cells.length<C) continue;A=D.cells[C-1].cloneNode(false);if (FCKBrowserInfo.IsGecko) A.innerHTML=GECKO_BOGUS;var E=D.cells[C];if (E) D.insertBefore(A,E);else D.appendChild(A);}};FCKTableHandler.DeleteColumns=function(){var A=FCKSelection.MoveToAncestorNode('TD')||FCKSelection.MoveToAncestorNode('TH');if (!A) return;var B=FCKTools.GetElementAscensor(A,'TABLE');var C=A.cellIndex;for (var i=B.rows.length-1;i>=0;i--){var D=B.rows[i];if (C==0&&D.cells.length==1){FCKTableHandler.DeleteRows(D);continue;};if (D.cells[C]) D.removeChild(D.cells[C]);}};FCKTableHandler.InsertCell=function(A){var B=A?A:FCKSelection.MoveToAncestorNode('TD');if (!B) return null;var C=FCK.EditorDocument.createElement('TD');if (FCKBrowserInfo.IsGecko) C.innerHTML=GECKO_BOGUS;if (B.cellIndex==B.parentNode.cells.length-1){B.parentNode.appendChild(C);}else{B.parentNode.insertBefore(C,B.nextSibling);};return C;};FCKTableHandler.DeleteCell=function(A){if (A.parentNode.cells.length==1){FCKTableHandler.DeleteRows(FCKTools.GetElementAscensor(A,'TR'));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.MergeCells=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length<2) return;if (A[0].parentNode!=A[A.length-1].parentNode) return;var B=isNaN(A[0].colSpan)?1:A[0].colSpan;var C='';var D=FCK.EditorDocument.createDocumentFragment();for (var i=A.length-1;i>=0;i--){var E=A[i];for (var c=E.childNodes.length-1;c>=0;c--){var F=E.removeChild(E.childNodes[c]);if ((F.hasAttribute&&F.hasAttribute('_moz_editor_bogus_node'))||(F.getAttribute&&F.getAttribute('type',2)=='_moz')) continue;D.insertBefore(F,D.firstChild);};if (i>0){B+=isNaN(E.colSpan)?1:E.colSpan;FCKTableHandler.DeleteCell(E);}};A[0].colSpan=B;if (FCKBrowserInfo.IsGecko&&D.childNodes.length==0) A[0].innerHTML=GECKO_BOGUS;else A[0].appendChild(D);};FCKTableHandler.SplitCell=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length!=1) return;var B=this._CreateTableMap(A[0].parentNode.parentNode);var C=FCKTableHandler._GetCellIndexSpan(B,A[0].parentNode.rowIndex,A[0]);var D=this._GetCollumnCells(B,C);for (var i=0;i<D.length;i++){if (D[i]==A[0]){var E=this.InsertCell(A[0]);if (!isNaN(A[0].rowSpan)&&A[0].rowSpan>1) E.rowSpan=A[0].rowSpan;}else{if (isNaN(D[i].colSpan)) D[i].colSpan=2;else D[i].colSpan+=1;}}};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._GetCollumnCells=function(A,B){var C=[];for (var r=0;r<A.length;r++){var D=A[r][B];if (D&&(C.length==0||C[C.length-1]!=D)) C[C.length]=D;};return C;};FCKTableHandler._CreateTableMap=function(A){var B=A.rows;var r=-1;var C=[];for (var i=0;i<B.length;i++){r++;if (!C[r]) C[r]=[];var c=-1;for (var j=0;j<B[i].cells.length;j++){var D=B[i].cells[j];c++;while (C[r][c]) c++;var E=isNaN(D.colSpan)?1:D.colSpan;var F=isNaN(D.rowSpan)?1:D.rowSpan;for (var G=0;G<F;G++){if (!C[r+G]) C[r+G]=[];for (var H=0;H<E;H++){C[r+G][c+H]=B[i].cells[j];}};c+=E-1;}};return C;};FCKTableHandler.ClearRow=function(A){var B=A.cells;for (var i=0;i<B.length;i++){if (FCKBrowserInfo.IsGecko) B[i].innerHTML=GECKO_BOGUS;else B[i].innerHTML='';}};\r
+FCKTableHandler.GetSelectedCells=function(){var A=[];var B=FCK.EditorWindow.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.tagName.Equals('TD','TH')) A[A.length]=E;};return A;};\r
+var FCKXml=function(){};FCKXml.prototype.LoadUrl=function(A){this.Error=false;var B=this;var C=FCKTools.CreateXmlObject('XmlHttp');C.open("GET",A,false);C.send(null);if (C.status==200||C.status==304) this.DOMDocument=C.responseXML;else if (C.status==0&&C.readyState==4) this.DOMDocument=C.responseXML;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: '+C.status+'\r\nResponse text:\r\n'+C.responseText);}};FCKXml.prototype.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;};FCKXml.prototype.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;}\r
+var FCKStyleDef=function(A,B){this.Name=A;this.Element=B.toUpperCase();this.IsObjectElement=FCKRegexLib.ObjectElements.test(this.Element);this.Attributes={};};FCKStyleDef.prototype.AddAttribute=function(A,B){this.Attributes[A]=B;};FCKStyleDef.prototype.GetOpenerTag=function(){var s='<'+this.Element;for (var a in this.Attributes) s+=' '+a+'="'+this.Attributes[a]+'"';return s+'>';};FCKStyleDef.prototype.GetCloserTag=function(){return '</'+this.Element+'>';};FCKStyleDef.prototype.RemoveFromSelection=function(){if (FCKSelection.GetType()=='Control') this._RemoveMe(FCK.ToolbarSet.CurrentInstance.Selection.GetSelectedElement());else this._RemoveMe(FCK.ToolbarSet.CurrentInstance.Selection.GetParentElement());}\r
+FCKStyleDef.prototype.ApplyToSelection=function(){if (FCKSelection.GetType()=='Text'&&!this.IsObjectElement){var A=FCK.ToolbarSet.CurrentInstance.EditorWindow.getSelection();var e=FCK.ToolbarSet.CurrentInstance.EditorDocument.createElement(this.Element);for (var i=0;i<A.rangeCount;i++){e.appendChild(A.getRangeAt(i).extractContents());};this._AddAttributes(e);this._RemoveDuplicates(e);var B=A.getRangeAt(0);B.insertNode(e);}else{var C=FCK.ToolbarSet.CurrentInstance.Selection.GetSelectedElement();if (C.tagName==this.Element) this._AddAttributes(C);}};FCKStyleDef.prototype._AddAttributes=function(A){for (var a in this.Attributes){switch (a.toLowerCase()){case 'src':A.setAttribute('_fcksavedurl',this.Attributes[a],0);default:A.setAttribute(a,this.Attributes[a],0);}}};FCKStyleDef.prototype._RemoveDuplicates=function(A){for (var i=0;i<A.childNodes.length;i++){var B=A.childNodes[i];if (B.nodeType!=1) continue;this._RemoveDuplicates(B);if (this.IsEqual(B)) FCKTools.RemoveOuterTags(B);}};FCKStyleDef.prototype.IsEqual=function(e){if (e.tagName!=this.Element) return false;for (var a in this.Attributes){if (e.getAttribute(a)!=this.Attributes[a]) return false;};return true;};FCKStyleDef.prototype._RemoveMe=function(A){if (!A) return;var B=A.parentNode;if (A.nodeType==1&&this.IsEqual(A)){if (this.IsObjectElement){for (var a in this.Attributes) A.removeAttribute(a,0);return;}else FCKTools.RemoveOuterTags(A);};this._RemoveMe(B);}\r
+var FCKStylesLoader=function(){this.Styles={};this.StyleGroups={};this.Loaded=false;this.HasObjectElements=false;};FCKStylesLoader.prototype.Load=function(A){var B=new FCKXml();B.LoadUrl(A);var C=B.SelectNodes('Styles/Style');for (var i=0;i<C.length;i++){var D=C[i].attributes.getNamedItem('element').value.toUpperCase();var E=new FCKStyleDef(C[i].attributes.getNamedItem('name').value,D);if (E.IsObjectElement) this.HasObjectElements=true;var F=B.SelectNodes('Attribute',C[i]);for (var j=0;j<F.length;j++){var G=F[j].attributes.getNamedItem('name').value;var H=F[j].attributes.getNamedItem('value').value;if (G.toLowerCase()=='style'){var I=document.createElement('SPAN');I.style.cssText=H;H=I.style.cssText;};E.AddAttribute(G,H);};this.Styles[E.Name]=E;var J=this.StyleGroups[D];if (J==null){this.StyleGroups[D]=[];J=this.StyleGroups[D];};J[J.length]=E;};this.Loaded=true;}\r
+var FCKNamedCommand=function(A){this.Name=A;};FCKNamedCommand.prototype.Execute=function(){FCK.ExecuteNamedCommand(this.Name);};FCKNamedCommand.prototype.GetState=function(){return FCK.GetNamedCommandState(this.Name);};\r
+var FCKDialogCommand=function(A,B,C,D,E,F,G){this.Name=A;this.Title=B;this.Url=C;this.Width=D;this.Height=E;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,null,null,this.Resizable);};FCKDialogCommand.prototype.GetState=function(){if (this.GetStateFunction) return this.GetStateFunction(this.GetStateParam);else return 0;};var FCKUndefinedCommand=function(){this.Name='Undefined';};FCKUndefinedCommand.prototype.Execute=function(){alert(FCKLang.NotImplemented);};FCKUndefinedCommand.prototype.GetState=function(){return 0;};var FCKFontNameCommand=function(){this.Name='FontName';};FCKFontNameCommand.prototype.Execute=function(A){if (A==null||A==""){}else FCK.ExecuteNamedCommand('FontName',A);};FCKFontNameCommand.prototype.GetState=function(){return FCK.GetNamedCommandValue('FontName');};var FCKFontSizeCommand=function(){this.Name='FontSize';};FCKFontSizeCommand.prototype.Execute=function(A){if (typeof(A)=='string') A=parseInt(A,10);if (A==null||A==''){FCK.ExecuteNamedCommand('FontSize',3);}else FCK.ExecuteNamedCommand('FontSize',A);};FCKFontSizeCommand.prototype.GetState=function(){return FCK.GetNamedCommandValue('FontSize');};var FCKFormatBlockCommand=function(){this.Name='FormatBlock';};FCKFormatBlockCommand.prototype.Execute=function(A){if (A==null||A=='') FCK.ExecuteNamedCommand('FormatBlock','<P>');else if (A=='div'&&FCKBrowserInfo.IsGecko) FCK.ExecuteNamedCommand('FormatBlock','div');else FCK.ExecuteNamedCommand('FormatBlock','<'+A+'>');};FCKFormatBlockCommand.prototype.GetState=function(){return FCK.GetNamedCommandValue('FormatBlock');};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.SetHTML('');FCKUndo.Typing=true;};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,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(){if (FCKBrowserInfo.IsIE) FCKUndo.Undo();else FCK.ExecuteNamedCommand('Undo');};FCKUndoCommand.prototype.GetState=function(){if (FCKBrowserInfo.IsIE) return (FCKUndo.CheckUndoState()?0:-1);else return FCK.GetNamedCommandState('Undo');};var FCKRedoCommand=function(){this.Name='Redo';};FCKRedoCommand.prototype.Execute=function(){if (FCKBrowserInfo.IsIE) FCKUndo.Redo();else FCK.ExecuteNamedCommand('Redo');};FCKRedoCommand.prototype.GetState=function(){if (FCKBrowserInfo.IsIE) return (FCKUndo.CheckRedoState()?0:-1);else return FCK.GetNamedCommandState('Redo');};var FCKPageBreakCommand=function(){this.Name='PageBreak';};FCKPageBreakCommand.prototype.Execute=function(){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);A=FCK.InsertElement(A);};FCKPageBreakCommand.prototype.GetState=function(){return 0;};var FCKUnlinkCommand=function(){this.Name='Unlink';};FCKUnlinkCommand.prototype.Execute=function(){if (FCKBrowserInfo.IsGecko){var A=FCK.Selection.MoveToAncestorNode('A');if (A) FCKTools.RemoveOuterTags(A);return;};FCK.ExecuteNamedCommand(this.Name);};FCKUnlinkCommand.prototype.GetState=function(){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 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(){return 0;};var FCKPasteCommand=function(){this.Name='Paste';};FCKPasteCommand.prototype={Execute:function(){if (FCKBrowserInfo.IsIE) FCK.Paste();else FCK.ExecuteNamedCommand('Paste');},GetState:function(){return FCK.GetNamedCommandState('Paste');}};\r
+var FCKSpellCheckCommand=function(){this.Name='SpellCheck';this.IsEnabled=(FCKConfig.SpellChecker=='SpellerPages');};FCKSpellCheckCommand.prototype.Execute=function(){FCKDialog.OpenDialog('FCKDialog_SpellCheck','Spell Check','dialog/fck_spellerpages.html',440,480);};FCKSpellCheckCommand.prototype.GetState=function(){return this.IsEnabled?0:-1;}\r
+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.SkinPath+'fck_editor.css');this._Panel.MainNode.className='FCK_Panel';this._CreatePanelBody(this._Panel.Document,this._Panel.MainNode);FCKTools.DisableSelection(this._Panel.Document.body);};FCKTextColorCommand.prototype.Execute=function(A,B,C){FCK._ActiveColorPanelType=this.Type;this._Panel.Show(A,B,C);};FCKTextColorCommand.prototype.SetColor=function(A){if (FCK._ActiveColorPanelType=='ForeColor') FCK.ExecuteNamedCommand('ForeColor',A);else if (FCKBrowserInfo.IsGeckoLike){if (FCKBrowserInfo.IsGecko&&!FCKConfig.GeckoUseSPAN) FCK.EditorDocument.execCommand('useCSS',false,false);FCK.ExecuteNamedCommand('hilitecolor',A);if (FCKBrowserInfo.IsGecko&&!FCKConfig.GeckoUseSPAN) FCK.EditorDocument.execCommand('useCSS',false,true);}else FCK.ExecuteNamedCommand('BackColor',A);delete FCK._ActiveColorPanelType;};FCKTextColorCommand.prototype.GetState=function(){return 0;};function FCKTextColorCommand_OnMouseOver()   { this.className='ColorSelected';};function FCKTextColorCommand_OnMouseOut()    { this.className='ColorDeselected';};function FCKTextColorCommand_OnClick(){this.className='ColorDeselected';this.Command.SetColor('#'+this.Color);this.Command._Panel.Hide();};function FCKTextColorCommand_AutoOnClick(){this.className='ColorDeselected';this.Command.SetColor('');this.Command._Panel.Hide();};function FCKTextColorCommand_MoreOnClick(){this.className='ColorDeselected';this.Command._Panel.Hide();FCKDialog.OpenDialog('FCKDialog_Color',FCKLang.DlgColorTitle,'dialog/fck_colorselector.html',400,330,this.Command.SetColor);};FCKTextColorCommand.prototype._CreatePanelBody=function(A,B){function CreateSelectionDiv(){var C=A.createElement("DIV");C.className='ColorDeselected';C.onmouseover=FCKTextColorCommand_OnMouseOver;C.onmouseout=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>';C.Command=this;C.onclick=FCKTextColorCommand_AutoOnClick;var G=FCKConfig.FontColors.toString().split(',');var H=0;while (H<G.length){var I=D.insertRow(-1);for (var i=0;i<8&&H<G.length;i++,H++){C=I.insertCell(-1).appendChild(CreateSelectionDiv());C.Color=G[H];C.innerHTML='<div class="ColorBoxBorder"><div class="ColorBox" style="background-color: #'+G[H]+'"></div></div>';C.Command=this;C.onclick=FCKTextColorCommand_OnClick;}};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>';C.Command=this;C.onclick=FCKTextColorCommand_MoreOnClick;}\r
+var FCKPastePlainTextCommand=function(){this.Name='PasteText';};FCKPastePlainTextCommand.prototype.Execute=function(){FCK.PasteAsPlainText();};FCKPastePlainTextCommand.prototype.GetState=function(){return FCK.GetNamedCommandState('Paste');};\r
+var FCKPasteWordCommand=function(){this.Name='PasteWord';};FCKPasteWordCommand.prototype.Execute=function(){FCK.PasteFromWord();};FCKPasteWordCommand.prototype.GetState=function(){if (FCKConfig.ForcePasteAsPlainText) return -1;else return FCK.GetNamedCommandState('Paste');};\r
+var FCKTableCommand=function(A){this.Name=A;};FCKTableCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();switch (this.Name){case 'TableInsertRow':FCKTableHandler.InsertRow();break;case 'TableDeleteRows':FCKTableHandler.DeleteRows();break;case 'TableInsertColumn':FCKTableHandler.InsertColumn();break;case 'TableDeleteColumns':FCKTableHandler.DeleteColumns();break;case 'TableInsertCell':FCKTableHandler.InsertCell();break;case 'TableDeleteCells':FCKTableHandler.DeleteCells();break;case 'TableMergeCells':FCKTableHandler.MergeCells();break;case 'TableSplitCell':FCKTableHandler.SplitCell();break;case 'TableDelete':FCKTableHandler.DeleteTable();break;default:alert(FCKLang.UnknownCommand.replace(/%1/g,this.Name));}};FCKTableCommand.prototype.GetState=function(){return 0;}\r
+var FCKStyleCommand=function(){this.Name='Style';this.StylesLoader=new FCKStylesLoader();this.StylesLoader.Load(FCKConfig.StylesXmlPath);this.Styles=this.StylesLoader.Styles;};FCKStyleCommand.prototype.Execute=function(A,B){FCKUndo.SaveUndoStep();if (B.Selected) B.Style.RemoveFromSelection();else B.Style.ApplyToSelection();FCKUndo.SaveUndoStep();FCK.Focus();FCK.Events.FireEvent("OnSelectionChange");};FCKStyleCommand.prototype.GetState=function(){if (!FCK.EditorDocument) return -1;var A=FCK.EditorDocument.selection;if (FCKSelection.GetType()=='Control'){var e=FCKSelection.GetSelectedElement();if (e) return this.StylesLoader.StyleGroups[e.tagName]?0:-1;};return 0;};FCKStyleCommand.prototype.GetActiveStyles=function(){var A=[];if (FCKSelection.GetType()=='Control') this._CheckStyle(FCKSelection.GetSelectedElement(),A,false);else this._CheckStyle(FCKSelection.GetParentElement(),A,true);return A;};FCKStyleCommand.prototype._CheckStyle=function(A,B,C){if (!A) return;if (A.nodeType==1){var D=this.StylesLoader.StyleGroups[A.tagName];if (D){for (var i=0;i<D.length;i++){if (D[i].IsEqual(A)) B[B.length]=D[i];}}};if (C) this._CheckStyle(A.parentNode,B,C);}\r
+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;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);};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 H=FCKTools.GetViewPaneSize(C);B.position="absolute";B.zIndex=FCKConfig.FloatingPanelsZIndex-1;B.left="0px";B.top="0px";B.width=H.Width+"px";B.height=H.Height+"px";if (!FCKBrowserInfo.IsIE){B.borderRight=B.borderBottom="9999px solid white";B.backgroundColor="white";};C.scrollTo(0,0);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();FCK.EditingArea.MakeEditable();FCK.Focus();};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';};\r
+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 'DocProps':B=new FCKDialogCommand('DocProps',FCKLang.DocProps,'dialog/fck_docprops.html',400,390,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,330);break;case 'Unlink':B=new FCKUnlinkCommand();break;case 'Anchor':B=new FCKDialogCommand('Anchor',FCKLang.DlgAnchorTitle,'dialog/fck_anchor.html',370,170);break;case 'BulletedList':B=new FCKDialogCommand('BulletedList',FCKLang.BulletedListProp,'dialog/fck_listprop.html?UL',370,170);break;case 'NumberedList':B=new FCKDialogCommand('NumberedList',FCKLang.NumberedListProp,'dialog/fck_listprop.html?OL',370,170);break;case 'About':B=new FCKDialogCommand('About',FCKLang.About,'dialog/fck_about.html',400,330);break;case 'Find':B=new FCKDialogCommand('Find',FCKLang.DlgFindTitle,'dialog/fck_find.html',340,170);break;case 'Replace':B=new FCKDialogCommand('Replace',FCKLang.DlgReplaceTitle,'dialog/fck_replace.html',340,200);break;case 'Image':B=new FCKDialogCommand('Image',FCKLang.DlgImgTitle,'dialog/fck_image.html',450,400);break;case 'Flash':B=new FCKDialogCommand('Flash',FCKLang.DlgFlashTitle,'dialog/fck_flash.html',450,400);break;case 'SpecialChar':B=new FCKDialogCommand('SpecialChar',FCKLang.DlgSpecialCharTitle,'dialog/fck_specialchar.html',400,320);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',450,250);break;case 'TableProp':B=new FCKDialogCommand('Table',FCKLang.DlgTableTitle,'dialog/fck_table.html?Parent',400,250);break;case 'TableCellProp':B=new FCKDialogCommand('TableCell',FCKLang.DlgCellTitle,'dialog/fck_tablecell.html',550,250);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 '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 'TableInsertRow':B=new FCKTableCommand('TableInsertRow');break;case 'TableDeleteRows':B=new FCKTableCommand('TableDeleteRows');break;case 'TableInsertColumn':B=new FCKTableCommand('TableInsertColumn');break;case 'TableDeleteColumns':B=new FCKTableCommand('TableDeleteColumns');break;case 'TableInsertCell':B=new FCKTableCommand('TableInsertCell');break;case 'TableDeleteCells':B=new FCKTableCommand('TableDeleteCells');break;case 'TableMergeCells':B=new FCKTableCommand('TableMergeCells');break;case 'TableSplitCell':B=new FCKTableCommand('TableSplitCell');break;case 'TableDelete':B=new FCKTableCommand('TableDelete');break;case 'Form':B=new FCKDialogCommand('Form',FCKLang.Form,'dialog/fck_form.html',380,230);break;case 'Checkbox':B=new FCKDialogCommand('Checkbox',FCKLang.Checkbox,'dialog/fck_checkbox.html',380,230);break;case 'Radio':B=new FCKDialogCommand('Radio',FCKLang.RadioButton,'dialog/fck_radiobutton.html',380,230);break;case 'TextField':B=new FCKDialogCommand('TextField',FCKLang.TextField,'dialog/fck_textfield.html',380,230);break;case 'Textarea':B=new FCKDialogCommand('Textarea',FCKLang.Textarea,'dialog/fck_textarea.html',380,230);break;case 'HiddenField':B=new FCKDialogCommand('HiddenField',FCKLang.HiddenField,'dialog/fck_hiddenfield.html',380,230);break;case 'Button':B=new FCKDialogCommand('Button',FCKLang.Button,'dialog/fck_button.html',380,230);break;case 'Select':B=new FCKDialogCommand('Select',FCKLang.SelectionField,'dialog/fck_select.html',400,380);break;case 'ImageButton':B=new FCKDialogCommand('ImageButton',FCKLang.ImageButton,'dialog/fck_image.html?ImageButton',450,400);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 'SelectAll':B=new FCKSelectAllCommand();break;case 'Undefined':B=new FCKUndefinedCommand();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;};\r
+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();B=this.Document=this._Popup.document;FCK.IECleanup.AddItem(this,FCKPanel_Cleanup);}else{var C=this._IFrame=this._Window.document.createElement('iframe');C.src='javascript:void(0)';C.allowTransparency=true;C.frameBorder='0';C.scrolling='no';C.style.position='absolute';C.style.zIndex=FCKConfig.FloatingPanelsZIndex;C.width=C.height=0;if (this._Window==window.parent&&window.frameElement) window.frameElement.parentNode.insertBefore(C,window.frameElement);else this._Window.document.body.appendChild(C);var D=C.contentWindow;B=this.Document=D.document;var E='';if (FCKBrowserInfo.IsSafari) E='<base href="'+window.document.location+'">';B.open();B.write('<html><head>'+E+'<\/head><body style="margin:0px;padding:0px;"><\/body><\/html>');B.close();FCKTools.AddEventListenerEx(D,'focus',FCKPanel_Window_OnFocus,this);FCKTools.AddEventListenerEx(D,'blur',FCKPanel_Window_OnBlur,this);};B.dir=FCKLang.Dir;B.oncontextmenu=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.Show=function(x,y,A,B,C){var D;if (this._Popup){this._Popup.show(x,y,0,0,A);this.MainNode.style.width=B?B+'px':'';this.MainNode.style.height=C?C+'px':'';D=this.MainNode.offsetWidth;if (this.IsRTL){if (this.IsContextMenu) x=x-D+1;else if (A) x=(x*-1)+A.offsetWidth-D;};this._Popup.show(x,y,D,this.MainNode.offsetHeight,A);if (this.OnHide){if (this._Timer) CheckPopupOnHide.call(this,true);this._Timer=FCKTools.SetInterval(CheckPopupOnHide,100,this);}}else{if (typeof(FCKFocusManager)!='undefined') FCKFocusManager.Lock();if (this.ParentPanel) this.ParentPanel.Lock();this.MainNode.style.width=B?B+'px':'';this.MainNode.style.height=C?C+'px':'';D=this.MainNode.offsetWidth;if (!B)  this._IFrame.width=1;if (!C)    this._IFrame.height=1;D=this.MainNode.offsetWidth;var E=FCKTools.GetElementPosition(A.nodeType==9?(FCKTools.IsStrictMode(A)?A.documentElement:A.body):A,this._Window);if (this.IsRTL&&!this.IsContextMenu) x=(x*-1);x+=E.X;y+=E.Y;if (this.IsRTL){if (this.IsContextMenu) x=x-D+1;else if (A) x=x+A.offsetWidth-D;}else{var F=FCKTools.GetViewPaneSize(this._Window);var G=FCKTools.GetScrollPosition(this._Window);var H=F.Height+G.Y;var I=F.Width+G.X;if ((x+D)>I) x-=x+D-I;if ((y+this.MainNode.offsetHeight)>H) y-=y+this.MainNode.offsetHeight-H;};if (x<0) x=0;this._IFrame.style.left=x+'px';this._IFrame.style.top=y+'px';var J=D;var K=this.MainNode.offsetHeight;this._IFrame.width=J;this._IFrame.height=K;this._IFrame.contentWindow.focus();};this._IsOpened=true;FCKTools.RunFunction(this.OnShow,this);};FCKPanel.prototype.Hide=function(A){if (this._Popup) this._Popup.hide();else{if (!this._IsOpened) return;if (typeof(FCKFocusManager)!='undefined') FCKFocusManager.Unlock();this._IFrame.width=this._IFrame.height=0;this._IsOpened=false;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;FCKTools.RunFunction(this.OnHide,this);}};function FCKPanel_Cleanup(){this._Popup=null;this._Window=null;this.Document=null;this.MainNode=null;}\r
+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;}\r
+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=this.MainElement;if (B){FCKToolbarButtonUI_Cleanup.call(this);if (B.parentNode) B.parentNode.removeChild(B);B=this.MainElement=null;};var C=FCKTools.GetElementDocument(A);B=this.MainElement=C.createElement('DIV');B._FCKButton=this;B.title=this.Tooltip;if (FCKBrowserInfo.IsGecko) B.onmousedown=FCKTools.CancelEvent;this.ChangeState(this.State,true);if (this.Style==0&&!this.ShowArrow){B.appendChild(this.Icon.CreateIconElement(C));}else{var D=B.appendChild(C.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(C));else F.appendChild(this._CreatePaddingElement(C));if (this.Style==1||this.Style==2){F=E.insertCell(-1);F.className='TB_Button_Text';F.noWrap=true;F.appendChild(C.createTextNode(this.Label));};if (this.ShowArrow){if (this.Style!=0){E.insertCell(-1).appendChild(this._CreatePaddingElement(C));};F=E.insertCell(-1);var G=F.appendChild(C.createElement('IMG'));G.src=FCKConfig.SkinPath+'images/toolbar.buttonarrow.gif';G.width=5;G.height=3;};F=E.insertCell(-1);F.appendChild(this._CreatePaddingElement(C));};A.appendChild(B);};FCKToolbarButtonUI.prototype.ChangeState=function(A,B){if (!B&&this.State==A) return;var e=this.MainElement;switch (parseInt(A,10)){case 0:e.className='TB_Button_Off';e.onmouseover=FCKToolbarButton_OnMouseOverOff;e.onmouseout=FCKToolbarButton_OnMouseOutOff;e.onclick=FCKToolbarButton_OnClick;break;case 1:e.className='TB_Button_On';e.onmouseover=FCKToolbarButton_OnMouseOverOn;e.onmouseout=FCKToolbarButton_OnMouseOutOn;e.onclick=FCKToolbarButton_OnClick;break;case -1:e.className='TB_Button_Disabled';e.onmouseover=null;e.onmouseout=null;e.onclick=null;break;};this.State=A;};function FCKToolbarButtonUI_Cleanup(){if (this.MainElement){this.MainElement._FCKButton=null;this.MainElement=null;}};function FCKToolbarButton_OnMouseOverOn(){this.className='TB_Button_On_Over';};function FCKToolbarButton_OnMouseOutOn(){this.className='TB_Button_On';};function FCKToolbarButton_OnMouseOverOff(){this.className='TB_Button_Off_Over';};function FCKToolbarButton_OnMouseOutOff(){this.className='TB_Button_Off';};function FCKToolbarButton_OnClick(e){if (this._FCKButton.OnClick) this._FCKButton.OnClick(this._FCKButton);};\r
+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];};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=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).GetState();if (A==this._UIButton.State) return;this._UIButton.ChangeState(A);};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);}\r
+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.SkinPath+'fck_editor.css');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(){this.className=this.originalClass;this.FCKSpecialCombo._Panel.Hide();this.FCKSpecialCombo.SetLabel(this.FCKItemLabel);if (typeof(this.FCKSpecialCombo.OnSelect)=='function') this.FCKSpecialCombo.OnSelect(this.FCKItemID,this);};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.FCKItemID=A;E.FCKItemLabel=C||A;E.FCKSpecialCombo=this;E.Selected=false;if (FCKBrowserInfo.IsIE) E.style.width='100%';if (D) E.style.backgroundColor=D;E.onmouseover=FCKSpecialCombo_ItemOnMouseOver;E.onmouseout=FCKSpecialCombo_ItemOnMouseOut;E.onclick=FCKSpecialCombo_ItemOnClick;this.Items[A.toString().toLowerCase()]=E;return E;};FCKSpecialCombo.prototype.SelectItem=function(A){A=A?A.toString().toLowerCase():'';var B=this.Items[A];if (B){B.className=B.originalClass='SC_ItemSelected';B.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){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){this.Label=A.length==0?'&nbsp;':A;if (this._LabelEl){this._LabelEl.innerHTML=this.Label;FCKTools.DisableSelection(this._LabelEl);}};FCKSpecialCombo.prototype.SetEnabled=function(A){this.Enabled=A;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>';};G.SpecialCombo=this;G.onmouseover=FCKSpecialCombo_OnMouseOver;G.onmouseout=FCKSpecialCombo_OnMouseOut;G.onclick=FCKSpecialCombo_OnClick;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(){if (this.SpecialCombo.Enabled){switch (this.SpecialCombo.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(){switch (this.SpecialCombo.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){var A=this.SpecialCombo;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);}};\r
+var FCKToolbarSpecialCombo=function(){this.SourceView=false;this.ContextSensitive=true;this._LastValue=null;};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;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);};\r
+var FCKToolbarFontsCombo=function(A,B){this.CommandName='FontName';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;};FCKToolbarFontsCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarFontsCombo.prototype.GetLabel=function(){return FCKLang.Font;};FCKToolbarFontsCombo.prototype.CreateItems=function(A){var B=FCKConfig.FontNames.split(';');for (var i=0;i<B.length;i++) this._Combo.AddItem(B[i],'<font face="'+B[i]+'" style="font-size: 12px">'+B[i]+'</font>');}\r
+var FCKToolbarFontSizeCombo=function(A,B){this.CommandName='FontSize';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;};FCKToolbarFontSizeCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarFontSizeCombo.prototype.GetLabel=function(){return FCKLang.FontSize;};FCKToolbarFontSizeCombo.prototype.CreateItems=function(A){A.FieldWidth=70;var B=FCKConfig.FontSizes.split(';');for (var i=0;i<B.length;i++){var C=B[i].split('/');this._Combo.AddItem(C[0],'<font size="'+C[0]+'">'+C[1]+'</font>',C[1]);}}\r
+var FCKToolbarFontFormatCombo=function(A,B){this.CommandName='FontFormat';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.NormalLabel='Normal';this.PanelWidth=190;};FCKToolbarFontFormatCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarFontFormatCombo.prototype.GetLabel=function(){return FCKLang.FontFormat;};FCKToolbarFontFormatCombo.prototype.CreateItems=function(A){var B=A._Panel.Document;FCKTools.AppendStyleSheet(B,FCKConfig.ToolbarComboPreviewCSS);if (FCKConfig.BodyId&&FCKConfig.BodyId.length>0) B.body.id=FCKConfig.BodyId;if (FCKConfig.BodyClass&&FCKConfig.BodyClass.length>0) B.body.className+=' '+FCKConfig.BodyClass;var C=FCKLang['FontFormats'].split(';');var D={p:C[0],pre:C[1],address:C[2],h1:C[3],h2:C[4],h3:C[5],h4:C[6],h5:C[7],h6:C[8],div:C[9]};var E=FCKConfig.FontFormats.split(';');for (var i=0;i<E.length;i++){var F=E[i];var G=D[F];if (F=='p') this.NormalLabel=G;this._Combo.AddItem(F,'<div class="BaseFont"><'+F+'>'+G+'</'+F+'></div>',G);}};if (FCKBrowserInfo.IsIE){FCKToolbarFontFormatCombo.prototype.RefreshActiveItems=function(A,B){if (B==this.NormalLabel){if (A.Label!='&nbsp;') A.DeselectAll(true);}else{if (this._LastValue==B) return;A.SelectItemByLabel(B,true);};this._LastValue=B;}}\r
+var FCKToolbarStyleCombo=function(A,B){this.CommandName='Style';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;};FCKToolbarStyleCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarStyleCombo.prototype.GetLabel=function(){return FCKLang.Style;};FCKToolbarStyleCombo.prototype.CreateItems=function(A){var B=A._Panel.Document;FCKTools.AppendStyleSheet(B,FCKConfig.ToolbarComboPreviewCSS);B.body.className+=' ForceBaseFont';if (FCKConfig.BodyId&&FCKConfig.BodyId.length>0) B.body.id=FCKConfig.BodyId;if (FCKConfig.BodyClass&&FCKConfig.BodyClass.length>0) B.body.className+=' '+FCKConfig.BodyClass;if (!(FCKBrowserInfo.IsGecko&&FCKBrowserInfo.IsGecko10)) A.OnBeforeClick=this.RefreshVisibleItems;var C=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).Styles;for (var s in C){var D=C[s];var E;if (D.IsObjectElement) E=A.AddItem(s,s);else E=A.AddItem(s,D.GetOpenerTag()+s+D.GetCloserTag());E.Style=D;}};FCKToolbarStyleCombo.prototype.RefreshActiveItems=function(A){A.DeselectAll();var B=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).GetActiveStyles();if (B.length>0){for (var i=0;i<B.length;i++) A.SelectItem(B[i].Name);A.SetLabelById(B[0].Name);}else A.SetLabel('');};FCKToolbarStyleCombo.prototype.RefreshVisibleItems=function(A){if (FCKSelection.GetType()=='Control') var B=FCKSelection.GetSelectedElement().tagName;for (var i in A.Items){var C=A.Items[i];if ((B&&C.Style.Element==B)||(!B&&!C.Style.IsObjectElement)) C.style.display='';else C.style.display='none';}}\r
+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;B._FCKToolbarPanelButton=this;var C=B.Document.body.appendChild(B.Document.createElement('div'));C.style.position='absolute';C.style.top='0px';var D=this.LineImg=C.appendChild(B.Document.createElement('IMG'));D.className='TB_ConnectionLine';D.src=FCK_SPACER_PATH;B.OnHide=FCKToolbarPanelButton_OnPanelHide;};function FCKToolbarPanelButton_OnButtonClick(A){var B=this._FCKToolbarPanelButton;var e=B._UIButton.MainElement;B._UIButton.ChangeState(1);B.LineImg.style.width=(e.offsetWidth-2)+'px';FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(B.CommandName).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;\r
+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 'SpellCheck':B=new FCKToolbarButton('SpellCheck',FCKLang.SpellCheck,null,null,null,null,13);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 '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('InsertHorizontalRule',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;default:alert(FCKLang.UnknownToolbarItem.replace(/%1/g,A));return null;};FCKToolbarItems.LoadedItems[A]=B;return B;}\r
+var FCKToolbar=function(){this.Items=[];if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKToolbar_Cleanup);};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){if (this.MainElement){if (this.MainElement.parentNode) this.MainElement.parentNode.removeChild(this.MainElement);this.MainElement=null;};var B=FCKTools.GetElementDocument(A);var e=this.MainElement=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;this.RowElement=e.insertRow(-1);var C;if (!this.HideStart){C=this.RowElement.insertCell(-1);C.appendChild(B.createElement('div')).className='TB_Start';};for (var i=0;i<this.Items.length;i++){this.Items[i].Create(this.RowElement.insertCell(-1));};if (!this.HideEnd){C=this.RowElement.insertCell(-1);C.appendChild(B.createElement('div')).className='TB_End';};A.appendChild(e);};function FCKToolbar_Cleanup(){this.MainElement=null;this.RowElement=null;};var FCKToolbarSeparator=function(){};FCKToolbarSeparator.prototype.Create=function(A){FCKTools.AppendElement(A,'div').className='TB_Separator';}\r
+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);}\r
+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;default:FCK.Events.AttachEvent('OnBlur',FCK_OnBlur);FCK.Events.AttachEvent('OnFocus',FCK_OnFocus);var D;var E=C.match(/^Out:(.+)\((\w+)\)$/);if (E){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 this._Init('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;G.open();G.write('<html><head><script type="text/javascript"> window.onload = window.onresize = function() { window.frameElement.height = document.body.scrollHeight ; } </script></head><body style="overflow: hidden">'+document.getElementById('xToolbarSpace').innerHTML+'</body></html>');G.close();G.oncontextmenu=FCKTools.CancelEvent;FCKTools.AppendStyleSheet(G,FCKConfig.SkinPath+'fck_editor.css');B=D.__FCKToolbarSet=new FCKToolbarSet(G);B._IFrame=F;if (FCK.IECleanup) FCK.IECleanup.AddItem(D,FCKToolbarSet_Target_Cleanup);};B.CurrentInstance=FCK;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;B.onclick=FCKToolbarSet_Expand_OnClick;C.title=FCKLang.ToolbarCollapse;C.onclick=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 (FCKBrowserInfo.IsGecko){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();};\r
+var FCKDialog={};FCKDialog.OpenDialog=function(A,B,C,D,E,F,G,H){var I={};I.Title=B;I.Page=C;I.Editor=window;I.CustomValue=F;var J=FCKConfig.BasePath+'fckdialog.html';this.Show(I,A,J,D,E,G,H);};\r
+FCKDialog.Show=function(A,B,C,D,E,F,G){var H=(FCKConfig.ScreenHeight-E)/2;var I=(FCKConfig.ScreenWidth-D)/2;var J="location=no,menubar=no,toolbar=no,dependent=yes,dialog=yes,minimizable=no,modal=yes,alwaysRaised=yes,resizable="+(G?'yes':'no')+",width="+D+",height="+E+",top="+H+",left="+I;if (!F) F=window;FCKFocusManager.Lock();var K=F.open('','FCKeditorDialog_'+B,J,true);if (!K){alert(FCKLang.DialogBlocked);FCKFocusManager.Unlock();return;};K.moveTo(I,H);K.resizeTo(D,E);K.focus();K.location.href=C;K.dialogArguments=A;F.FCKLastDialogInfo=A;this.Window=K;try{window.top.parent.addEventListener('mousedown',this.CheckFocus,true);window.top.parent.addEventListener('mouseup',this.CheckFocus,true);window.top.parent.addEventListener('click',this.CheckFocus,true);window.top.parent.addEventListener('focus',this.CheckFocus,true);}catch (e){}};FCKDialog.CheckFocus=function(){if (typeof(FCKDialog)!="object") return false;if (FCKDialog.Window&&!FCKDialog.Window.closed) FCKDialog.Window.focus();else{try{window.top.parent.removeEventListener('onmousedown',FCKDialog.CheckFocus,true);window.top.parent.removeEventListener('mouseup',FCKDialog.CheckFocus,true);window.top.parent.removeEventListener('click',FCKDialog.CheckFocus,true);window.top.parent.removeEventListener('onfocus',FCKDialog.CheckFocus,true);}catch (e){}};return false;};\r
+var FCKMenuItem=function(A,B,C,D,E){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);if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKMenuItem_Cleanup);};FCKMenuItem.prototype.AddItem=function(A,B,C,D){this.HasSubMenu=true;return this.SubMenu.AddItem(A,B,C,D);};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;}\r
+var FCKMenuBlock=function(){this._Items=[];};FCKMenuBlock.prototype.Count=function(){return this._Items.length;};FCKMenuBlock.prototype.AddItem=function(A,B,C,D){var E=new FCKMenuItem(this,A,B,C,D);E.OnClick=FCKTools.CreateEventListener(FCKMenuBlock_Item_OnClick,this);E.OnActivate=FCKTools.CreateEventListener(FCKMenuBlock_Item_OnActivate,this);this._Items.push(E);return E;};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){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();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';}\r
+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.SkinPath+'fck_editor.css');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();}\r
+var FCKContextMenu=function(A,B){this.CtrlDisable=false;var C=this._Panel=new FCKPanel(A);C.AppendStyleSheet(FCKConfig.SkinPath+'fck_editor.css');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;this._Document.addEventListener('contextmenu',FCKContextMenu_Document_OnContextMenu,false);}};FCKContextMenu.prototype.AddItem=function(A,B,C,D){var E=this._MenuBlock.AddItem(A,B,C,D);this._Redraw=true;return E;};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){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);};A=A.parentNode;}};function FCKContextMenu_AttachedElement_OnContextMenu(A,B,C){if (B.CtrlDisable&&(A.ctrlKey||A.metaKey)) 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);B._Panel.Show(A.pageX||A.screenX,A.pageY||A.screenY,A.currentTarget||null);return false;};function FCKContextMenu_MenuBlock_OnClick(A,B){B._Panel.Hide();FCKTools.RunFunction(B.OnItemClick,B,A);}\r
+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('TableInsertCell',FCKLang.InsertCell,58);D.AddItem('TableDeleteCells',FCKLang.DeleteCells,59);D.AddItem('TableMergeCells',FCKLang.MergeCells,60);D.AddItem('TableSplitCell',FCKLang.SplitCell,61);D.AddSeparator();D.AddItem('TableCellProp',FCKLang.CellProperties,57);menu.AddSeparator();D=menu.AddItem('Row',FCKLang.RowCM);D.AddItem('TableInsertRow',FCKLang.InsertRow,62);D.AddItem('TableDeleteRows',FCKLang.DeleteRows,63);menu.AddSeparator();D=menu.AddItem('Column',FCKLang.ColumnCM);D.AddItem('TableInsertColumn',FCKLang.InsertColumn,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();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);}}};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);}}};};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){FCK.Focus();FCKCommands.GetCommand(A.Name).Execute();};\r
+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');}\r
+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;}\r
diff --git a/httemplate/elements/fckeditor/editor/js/fckeditorcode_ie.js b/httemplate/elements/fckeditor/editor/js/fckeditorcode_ie.js
new file mode 100644 (file)
index 0000000..0d43957
--- /dev/null
@@ -0,0 +1,99 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ * \r
+ * == BEGIN LICENSE ==\r
+ * \r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ * \r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ * \r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ * \r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ * \r
+ * == END LICENSE ==\r
+ * \r
+ * This file has been compressed for better performance. The original source\r
+ * can be found at "editor/_source".\r
+ */\r
+\r
+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;\r
+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;};Array.prototype.AddItem=function(A){var i=this.length;this[i]=A;return i;};Array.prototype.IndexOf=function(A){for (var i=0;i<this.length;i++){if (this[i]==A) return i;};return-1;};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);}\r
+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) 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();}\r
+var s=navigator.userAgent.toLowerCase();var FCKBrowserInfo={IsIE:s.Contains('msie'),IsIE7:s.Contains('msie 7'),IsGecko:s.Contains('gecko/'),IsSafari:s.Contains('safari'),IsOpera:s.Contains('opera'),IsMac:s.Contains('macintosh')};(function(A){A.IsGeckoLike=(A.IsGecko||A.IsSafari||A.IsOpera);if (A.IsGecko){var B=s.match(/gecko\/(\d+)/)[1];A.IsGecko10=((B<20051111)||(/rv:1\.7/.test(s)));}else A.IsGecko10=false;})(FCKBrowserInfo);\r
+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;}})();\r
+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 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++) C=(D[i](this.Owner,B)&&C);};return C;};\r
+var FCK={Name:FCKURLParams['InstanceName'],Status:0,EditMode:0,Toolbar:null,HasFocus:false,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 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/*V*/) in A.Keystrokes) A.SetKeystrokes([CTRL+86,true]);if ((SHIFT+45/*INS*/) in A.Keystrokes) A.SetKeystrokes([SHIFT+45,true]);};this.EditingArea=new FCKEditingArea(document.getElementById('xEditingArea'));this.EditingArea.FFSpellChecker=FCKConfig.FirefoxSpellChecker;FCKListsLib.Setup();this.SetHTML(this.GetLinkedFieldValue(),true);},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:if (!FCKListsLib.BlockElements[D.nodeName.toLowerCase()]) F=true;break;case 3:if (E||D.nodeValue.Trim().length>0) F=true;};if (F){var G=D.parentNode;if (!E) E=G.insertBefore(B.createElement(A),D);E.appendChild(G.removeChild(D));D=E.nextSibling;}else{if (E){FCKDomTools.TrimNode(E);E=null;};D=D.nextSibling;}};if (E) FCKDomTools.TrimNode(E);},GetXHTML:function(A){if (FCK.EditMode==1) return FCK.EditingArea.Textarea.value;this.FixBody();var B;var C=FCK.EditorDocument;if (!C) return null;if (FCKConfig.FullPage){B=FCKXHtml.GetXHTML(C.getElementsByTagName('html')[0],true,A);if (FCK.DocTypeDeclaration&&FCK.DocTypeDeclaration.length>0) B=FCK.DocTypeDeclaration+'\n'+B;if (FCK.XmlDeclaration&&FCK.XmlDeclaration.length>0) B=FCK.XmlDeclaration+'\n'+B;}else{B=FCKXHtml.GetXHTML(C.body,false,A);if (FCKConfig.IgnoreEmptyParagraphValue&&FCKRegexLib.EmptyOutParagraph.test(B)) B='';};B=FCK.ProtectEventsRestore(B);if (FCKBrowserInfo.IsIE) B=B.replace(FCKRegexLib.ToReplace,'$1');return FCKConfig.ProtectedSource.Revert(B);},UpdateLinkedField:function(){FCK.LinkedField.value=FCK.GetXHTML(FCKConfig.FormatOutput);FCK.Events.FireEvent('OnAfterLinkedFieldUpdate');},RegisteredDoubleClickHandlers:{},OnDoubleClick:function(A){var B=FCK.RegisteredDoubleClickHandlers[A.tagName];if (B) B(A);},RegisterDoubleClickHandler:function(A,B){FCK.RegisteredDoubleClickHandlers[B.toUpperCase()]=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');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':'ABBR|XML';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;},SetHTML:function(A,B){this.EditingArea.Mode=FCK.EditMode;if (FCK.EditMode==0){A=FCKConfig.ProtectedSource.Protect(A);A=A.replace(FCKRegexLib.InvalidSelfCloseTags,'$1></$2>');A=FCK.ProtectEvents(A);A=FCK.ProtectUrls(A);A=FCK.ProtectTags(A);if (FCKBrowserInfo.IsGecko){A=A.replace(FCKRegexLib.StrongOpener,'<b$1');A=A.replace(FCKRegexLib.StrongCloser,'<\/b>');A=A.replace(FCKRegexLib.EmOpener,'<i$1');A=A.replace(FCKRegexLib.EmCloser,'<\/i>');};this._ForceResetIsDirty=(B===true);var C='';if (FCKConfig.FullPage){if (!FCKRegexLib.HeadOpener.test(A)){if (!FCKRegexLib.HtmlOpener.test(A)) A='<html dir="'+FCKConfig.ContentLangDirection+'">'+A+'</html>';A=A.replace(FCKRegexLib.HtmlOpener,'$&<head></head>');};FCK.DocTypeDeclaration=A.match(FCKRegexLib.DocTypeTag);if (FCKBrowserInfo.IsIE) C=FCK._GetBehaviorsStyle();else if (FCKConfig.ShowBorders) C='<link href="'+FCKConfig.FullBasePath+'css/fck_showtableborders_gecko.css" rel="stylesheet" type="text/css" _fcktemp="true" />';C+='<link href="'+FCKConfig.FullBasePath+'css/fck_internal.css" rel="stylesheet" type="text/css" _fcktemp="true" />';C=A.replace(FCKRegexLib.HeadCloser,C+'$&');if (FCK.TempBaseTag.length>0&&!FCKRegexLib.HasBaseTag.test(A)) C=C.replace(FCKRegexLib.HeadOpener,'$&'+FCK.TempBaseTag);}else{C=FCKConfig.DocType+'<html dir="'+FCKConfig.ContentLangDirection+'"';if (FCKBrowserInfo.IsIE&&!FCKRegexLib.Html4DocType.test(FCKConfig.DocType)) C+=' style="overflow-y: scroll"';C+='><head><title></title>'+_FCK_GetEditorAreaStyleTags()+'<link href="'+FCKConfig.FullBasePath+'css/fck_internal.css" rel="stylesheet" type="text/css" _fcktemp="true" />';if (FCKBrowserInfo.IsIE) C+=FCK._GetBehaviorsStyle();else if (FCKConfig.ShowBorders) C+='<link href="'+FCKConfig.FullBasePath+'css/fck_showtableborders_gecko.css" rel="stylesheet" type="text/css" _fcktemp="true" />';C+=FCK.TempBaseTag;var D='<body';if (FCKConfig.BodyId&&FCKConfig.BodyId.length>0) D+=' id="'+FCKConfig.BodyId+'"';if (FCKConfig.BodyClass&&FCKConfig.BodyClass.length>0) D+=' class="'+FCKConfig.BodyClass+'"';C+='</head>'+D+'>';if (FCKBrowserInfo.IsGecko&&(A.length==0||FCKRegexLib.EmptyParagraph.test(A))) C+=GECKO_BOGUS;else C+=A;C+='</body></html>';};this.EditingArea.OnLoad=_FCK_EditingArea_OnLoad;this.EditingArea.Start(C);}else{FCK.EditorWindow=null;FCK.EditorDocument=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 (FCKBrowserInfo.IsGecko) window.onresize();},HasFocus:false,RedirectNamedCommands:{},ExecuteNamedCommand:function(A,B,C){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');};FCKUndo.SaveUndoStep();},GetNamedCommandState:function(A){try{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:'';},PasteFromWord:function(){FCKDialog.OpenDialog('FCKDialog_Paste',FCKLang.PasteFromWord,'dialog/fck_paste.html',400,330,'Word');},Preview:function(){var A=FCKConfig.ScreenWidth*0.8;var B=FCKConfig.ScreenHeight*0.7;var C=(FCKConfig.ScreenWidth-A)/2;var D=window.open('',null,'toolbar=yes,location=no,status=yes,menubar=yes,scrollbars=yes,resizable=yes,width='+A+',height='+B+',left='+C);var E;if (FCKConfig.FullPage){if (FCK.TempBaseTag.length>0) E=FCK.TempBaseTag+FCK.GetXHTML();else E=FCK.GetXHTML();}else{E=FCKConfig.DocType+'<html dir="'+FCKConfig.ContentLangDirection+'"><head>'+FCK.TempBaseTag+'<title>'+FCKLang.Preview+'</title>'+_FCK_GetEditorAreaStyleTags()+'</head><body>'+FCK.GetXHTML()+'</body></html>';};D.document.write(E);D.document.close();},SwitchEditMode:function(A){var B=(FCK.EditMode==0);var C=FCK.IsDirty();var D;if (B){if (!A&&FCKBrowserInfo.IsIE) FCKUndo.SaveUndoStep();D=FCK.GetXHTML(FCKConfig.FormatSource);if (D==null) return false;}else D=this.EditingArea.Textarea.value;FCK.EditMode=B?1:0;FCK.SetHTML(D,!C);FCK.Focus();FCKTools.RunFunction(FCK.ToolbarSet.RefreshModeState,FCK.ToolbarSet);return true;},CreateElement:function(A){var e=FCK.EditorDocument.createElement(A);return FCK.InsertElementAndGetIt(e);},InsertElementAndGetIt:function(e){e.setAttribute('FCKTempLabel','true');this.InsertElement(e);var A=FCK.EditorDocument.getElementsByTagName(e.tagName);for (var i=0;i<A.length;i++){if (A[i].getAttribute('FCKTempLabel')){A[i].removeAttribute('FCKTempLabel');return A[i];}};return null;}};FCK.Events=new FCKEvents(FCK);FCK.GetHTML=FCK.GetXHTML;function _FCK_ProtectEvents_ReplaceTags(A){return A.replace(FCKRegexLib.EventAttributes,_FCK_ProtectEvents_ReplaceEvents);};function _FCK_ProtectEvents_ReplaceEvents(A,B){return ' '+B+'_fckprotectedatt="'+A.ReplaceAll([/&/g,/'/g,/"/g,/=/g,/</g,/>/g,/\r/g,/\n/g],['&apos;','&#39;','&quot;','&#61;','&lt;','&gt;','&#10;','&#13;'])+'"';};function _FCK_ProtectEvents_RestoreEvents(A,B){return B.ReplaceAll([/&#39;/g,/&quot;/g,/&#61;/g,/&lt;/g,/&gt;/g,/&#10;/g,/&#13;/g,/&apos;/g],["'",'"','=','<','>','\r','\n','&']);};function _FCK_EditingArea_OnLoad(){FCK.EditorWindow=FCK.EditingArea.Window;FCK.EditorDocument=FCK.EditingArea.Document;FCK.InitializeBehaviors();if (!FCKConfig.DisableEnterKeyHandler) FCK.EnterKeyHandler=new FCKEnterKey(FCK.EditorWindow,FCKConfig.EnterMode,FCKConfig.ShiftEnterMode);FCK.KeystrokeHandler.AttachToElement(FCK.EditorDocument);if (FCK._ForceResetIsDirty) FCK.ResetIsDirty();if (FCKBrowserInfo.IsIE&&FCK.HasFocus) FCK.EditorDocument.body.setActive();FCK.OnAfterSetHTML();if (FCK.Status!=0) return;FCK.SetStatus(1);};function _FCK_GetEditorAreaStyleTags(){var A='';var B=FCKConfig.EditorAreaCSS;for (var i=0;i<B.length;i++) A+='<link href="'+B[i]+'" rel="stylesheet" type="text/css" />';return A;};function _FCK_KeystrokeHandler_OnKeystroke(A,B){if (FCK.Status!=2) return false;if (FCK.EditMode==0){if (B=='Paste') return!FCK.Events.FireEvent('OnPaste');}else{if (B.Equals('Paste','Undo','Redo','SelectAll')) return false;};var C=FCK.Commands.GetCommand(B);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;}};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 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(){FCK.Focus();FCKFocusManager_Win_OnFocus();};function FCKFocusManager_Win_OnFocus(){FCKFocusManager._ResetTimer();if (!FCK.HasFocus&&!FCKFocusManager.IsLocked){FCK.HasFocus=true;FCK.Events.FireEvent("OnFocus");}};\r
+FCK.Description="FCKeditor for Internet Explorer 5.5+";FCK._GetBehaviorsStyle=function(){if (!FCK._BehaviorsStyle){var A=FCKConfig.FullBasePath;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(){return (FCK.Status==2&&FCK.Events.FireEvent("OnPaste"));};function Doc_OnKeyDown(){if (FCK.EditorWindow){var e=FCK.EditorWindow.event;if (!(e.keyCode>=16&&e.keyCode<=18)) Doc_OnKeyDownUndo();};return true;};function Doc_OnKeyDownUndo(){if (!FCKUndo.Typing){FCKUndo.SaveUndoStep();FCKUndo.Typing=true;FCK.Events.FireEvent("OnSelectionChange");};FCKUndo.TypesCount++;if (FCKUndo.TypesCount>FCKUndo.MaxTypes){FCKUndo.TypesCount=0;FCKUndo.SaveUndoStep();}};function Doc_OnDblClick(){FCK.OnDoubleClick(FCK.EditorWindow.event.srcElement);FCK.EditorWindow.event.cancelBubble=true;};function Doc_OnSelectionChange(){FCK.Events.FireEvent("OnSelectionChange");};FCK.InitializeBehaviors=function(A){this.EditorDocument.attachEvent('onmouseup',Doc_OnMouseUp);this.EditorDocument.body.attachEvent('onpaste',Doc_OnPaste);FCK.ContextMenu._InnerContextMenu.AttachToElement(FCK.EditorDocument.body);if (FCKConfig.TabSpaces>0){window.FCKTabHTML='';for (i=0;i<FCKConfig.TabSpaces;i++) window.FCKTabHTML+="&nbsp;";};this.EditorDocument.attachEvent("onkeydown",Doc_OnKeyDown);this.EditorDocument.attachEvent("ondblclick",Doc_OnDblClick);this.EditorDocument.attachEvent("onselectionchange",Doc_OnSelectionChange);};FCK.InsertHtml=function(A){A=FCKConfig.ProtectedSource.Protect(A);A=FCK.ProtectEvents(A);A=FCK.ProtectUrls(A);A=FCK.ProtectTags(A);FCK.EditorWindow.focus();FCKUndo.SaveUndoStep();var B=FCK.EditorDocument.selection;if (B.type.toLowerCase()=='control') B.clear();A='<span id="__fakeFCKRemove__">&nbsp;</span>'+A;B.createRange().pasteHTML(A);FCK.EditorDocument.getElementById('__fakeFCKRemove__').removeNode(true);FCKDocumentProcessor.Process(FCK.EditorDocument);};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.EditorWindow=null;this.EditorDocument=null;};FCK.Paste=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(){if (!FCK._CheckIsPastingEnabled()){FCKDialog.OpenDialog('FCKDialog_Paste',FCKLang.PasteAsText,'dialog/fck_paste.html',400,330,'PlainText');return;};var A=clipboardData.getData("Text");if (A&&A.length>0){A=FCKTools.HTMLEncode(A).replace(/\n/g,'<BR>');this.InsertHtml(A);}};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.InsertElement=function(A){FCK.InsertHtml(A.outerHTML);};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){var B=[];FCK.ExecuteNamedCommand('Unlink');if (A.length>0){if (FCKSelection.GetType()=='Control'){var C=this.EditorDocument.createElement('A');C.href=A;var D=FCKSelection.GetSelectedElement();D.parentNode.insertBefore(C,D);D.parentNode.removeChild(D);C.appendChild(D);return [C];};var E='javascript:void(0);/*'+(new Date().getTime())+'*/';FCK.ExecuteNamedCommand('CreateLink',E);var F=this.EditorDocument.links;for (i=0;i<F.length;i++){var C=F[i];if (C.getAttribute('href',2)==E){var H=C.innerHTML;C.href=A;C.innerHTML=H;var I=C.lastChild;if (I&&I.nodeName=='BR'){FCKDomTools.InsertAfterNode(C,C.removeChild(I));};B.push(C);}}};return B;};\r
+var FCKConfig=FCK.Config={};if (document.location.protocol=='file:'){FCKConfig.BasePath=decodeURIComponent(document.location.pathname.substr(1));FCKConfig.BasePath=FCKConfig.BasePath.replace(/\\/gi, '/');FCKConfig.BasePath='file://'+FCKConfig.BasePath.substring(0,FCKConfig.BasePath.lastIndexOf('/')+1);FCKConfig.FullBasePath=FCKConfig.BasePath;}else{FCKConfig.BasePath=document.location.pathname.substring(0,document.location.pathname.lastIndexOf('/')+1);FCKConfig.FullBasePath=document.location.protocol+'//'+document.location.host+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]=parseInt(E,10);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) {/*Ignore it. Much probably we are inside a FRAME where the "top" is in another domain (security error).*/}};if (!A.PluginsPath.EndsWith('/')) A.PluginsPath+='/';if (typeof(A.EditorAreaCSS)=='string') A.EditorAreaCSS=[A.EditorAreaCSS];var B=A.ToolbarComboPreviewCSS;if (!B||B.length==0) A.ToolbarComboPreviewCSS=A.EditorAreaCSS;else if (typeof(B)=='string') A.ToolbarComboPreviewCSS=[B];};FCKConfig.ToolbarSets={};FCKConfig.Plugins={};FCKConfig.Plugins.Items=[];FCKConfig.Plugins.Add=function(A,B,C){FCKConfig.Plugins.Items.AddItem([A,B,C]);};FCKConfig.ProtectedSource={};FCKConfig.ProtectedSource.RegexEntries=[/<!--[\s\S]*?-->/g,/<script[\s\S]*?<\/script>/gi,/<noscript[\s\S]*?<\/noscript>/gi,/<object[\s\S]+?<\/object>/gi];FCKConfig.ProtectedSource.Add=function(A){this.RegexEntries.AddItem(A);};FCKConfig.ProtectedSource.Protect=function(A){function _Replace(protectedSource){var B=FCKTempBin.AddElement(protectedSource);return '<!--{PS..'+B+'}-->';};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);};return A.replace(/(<|&lt;)!--\{PS..(\d+)\}--(>|&gt;)/g,_Replace);}\r
+var FCKDebug={};FCKDebug._GetWindow=function(){if (!this.DebugWindow||this.DebugWindow.closed) this.DebugWindow=window.open(FCKConfig.BasePath+'fckdebug.html','FCKeditorDebug','menubar=no,scrollbars=yes,resizable=yes,location=no,toolbar=no,width=600,height=500',true);return this.DebugWindow;};FCKDebug.Output=function(A,B,C){if (!FCKConfig.Debug) return;try{this._GetWindow().Output(A,B);}catch (e) {}};FCKDebug.OutputObject=function(A,B){if (!FCKConfig.Debug) return;try{this._GetWindow().OutputObject(A,B);}catch (e) {}}\r
+var FCKDomTools={MoveChildren:function(A,B){if (A==B) return;var C;while ((C=A.firstChild)) B.appendChild(A.removeChild(C));},TrimNode:function(A,B){this.LTrimNode(A);this.RTrimNode(A,B);},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,B){var C;while ((C=A.lastChild)){switch (C.nodeType){case 1:if (C.nodeName.toUpperCase()=='BR'&&(B||C.getAttribute('type',2)=='_moz')){C.parentNode.removeChild(C);continue;};break;case 3:var D=C.nodeValue.RTrim();var E=C.nodeValue.length;if (D.length==0){C.parentNode.removeChild(C);continue;}else if (D.length<E){C.splitText(D.length);A.lastChild.parentNode.removeChild(A.lastChild);}};break;}},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){if (!A) return null;if (A.nextSibling) A=A.nextSibling;else return this.GetNextSourceElement(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.firstChild) A=A.firstChild;else return this.GetNextSourceElement(A,B,C,D);};return null;},InsertAfterNode:function(A,B){return A.parentNode.insertBefore(B,A.nextSibling);},GetParents:function(A){var B=[];while (A){B.splice(0,0,A);A=A.parentNode;};return B;},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;}};\r
+var GECKO_BOGUS='<br type="_moz">';var FCKTools={};FCKTools.CreateBogusBR=function(A){var B=A.createElement('br');B.setAttribute('type','_moz');return B;};FCKTools.AppendStyleSheet=function(A,B){if (typeof(B)=='string') return this._AppendStyleSheet(A,B);else{var C=[];for (var i=0;i<B.length;i++) C.push(this._AppendStyleSheet(A,B[i]));return C;}};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){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.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||'CSS1Compat'));};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.GetLastItem=function(A){if (A.length>0) return A[A.length-1];return null;};\r
+FCKTools.CancelEvent=function(e){return false;};FCKTools._AppendStyleSheet=function(A,B){return A.createStyleSheet(B).owningElement;};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':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=top.document.body;if (B) return { Width:B.clientWidth,Height:B.clientHeight };else return { Width:0,Height:0 };};FCKTools.SaveStyles=function(A){var B={};if (A.className.length>0){B.Class=A.className;A.className='';};var C=A.style.cssText;if (C.length>0){B.Inline=C;A.style.cssText='';};return B;};FCKTools.RestoreStyles=function(A,B){A.className=B.Class||'';A.style.cssText=B.Inline||'';};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();}\r
+var FCKeditorAPI;function InitializeAPI(){var A=window.parent;if (!(FCKeditorAPI=A.FCKeditorAPI)){var B='var FCKeditorAPI = {Version : "2.4.3",VersionBuild : "15657",__Instances : new Object(),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       : {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.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[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(){delete FCKeditorAPI.__Instances[FCK.Name];};FCKTools.AddEventListener(window,'unload',FCKeditorAPI_Cleanup);\r
+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');B.onload=B.onerror=_FCKImagePreloader_OnImage;B._FCKImagePreloader=this;B.src=A[i];_FCKImagePreloader_ImageCache.push(B);}}};var _FCKImagePreloader_ImageCache=[];function _FCKImagePreloader_OnImage(){var A=this._FCKImagePreloader;if ((--A._PreloadCount)==0&&A.OnComplete) A.OnComplete();this._FCKImagePreloader=null;}\r
+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,BodyContents:/([\s\S]*\<body[^\>]*\>)([\s\S]*)(\<\/body\>[\s\S]*)/i,ToReplace:/___fcktoreplace:([\w]+)/ig,MetaHttpEquiv:/http-equiv\s*=\s*["']?([^"' ]+)/i,HasBaseTag:/<base /i,HtmlOpener:/<html\s?[^>]*>/i,HeadOpener:/<head\s?[^>]*>/i,HeadCloser:/<\/head\s*>/i,FCK_Class:/(\s*FCK__[A-Za-z]*\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;)(<\/\1>)?$/,TagBody:/></,StrongOpener:/<STRONG([ \>])/gi,StrongCloser:/<\/STRONG>/gi,EmOpener:/<EM([ \>])/gi,EmCloser:/<\/EM>/gi,GeckoEntitiesMarker:/#\?-\:/g,ProtectUrlsImg:/<img(?=\s).*?\ssrc=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,ProtectUrlsA:/<a(?=\s).*?\shref=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,Html4DocType:/HTML 4\.0 Transitional/i,DocTypeTag:/<!DOCTYPE[^>]*>/i,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};\r
+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,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,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 },EmptyElements:{ base: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,ol:1,ul:1,li:1,dt:1,de:1 },PathBlockLimitElements:{ body:1,td:1,th:1,caption:1,form:1 },Setup:function(){if (FCKConfig.EnterMode=='div') this.PathBlockElements.div=1;else this.PathBlockLimitElements.div=1;}};\r
+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',gl:'Galician',he:'Hebrew',hi:'Hindi',hr:'Croatian',hu:'Hungarian',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);eval('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);},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];}};\r
+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','◊':'loz','♠':'spades','♣':'clubs','♥':'hearts','♦':'diams','"':'quot','ˆ':'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'};for (e in B){FCKXHtmlEntities.Entities[e]=B[e];A+=e;};B=null;}}else{FCKXHtmlEntities.Entities={};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');}\r
+var FCKXHtml={};FCKXHtml.CurrentJobNum=0;FCKXHtml.GetXHTML=function(A,B,C){FCKXHtmlEntities.Initialize();this._NbspEntity=(FCKConfig.ProcessHTMLEntities?'nbsp':'#160');var D=FCK.IsDirty();this._CreateNode=FCKConfig.ForceStrongEm?FCKXHtml_CreateNode_StrongEm:FCKXHtml_CreateNode_Normal;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);var E=this._GetMainXmlString();this.XML=null;E=E.substr(7,E.length-15).Trim();if (FCKBrowserInfo.IsGecko) E=E.replace(/<br\/>$/,'');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();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) FCKDomTools.TrimNode(A,true);if (A.childNodes.length==0){if (C&&FCKConfig.FillEmptyBlocks){this._AppendEntity(A,this._NbspEntity);return A;};var E=A.nodeName;if (FCKListsLib.InlineChildReqElements[E]) return null;if (!FCKListsLib.EmptyElements[E]) A.appendChild(this.XML.createTextNode(''));};return A;};FCKXHtml._AppendNode=function(A,B){if (!B) return false;switch (B.nodeType){case 1:if (B.getAttribute('_fckfakelement')) return FCKXHtml._AppendNode(A,FCK.GetRealElement(B));if (FCKBrowserInfo.IsGecko&&B.hasAttribute('_moz_editor_bogus_node')) return false;if (B.getAttribute('_fcktemp')) return false;var C=B.tagName.toLowerCase();if (FCKBrowserInfo.IsIE){if (B.scopeName&&B.scopeName!='HTML'&&B.scopeName!='FCK') C=B.scopeName.toLowerCase()+':'+C;}else{if (C.StartsWith('fck:')) C=C.Remove(0,4);};if (!FCKRegexLib.ElementName.test(C)) return false;if (C=='br'&&B.getAttribute('type',2)=='_moz') return false;if (B._fckxhtmljob&&B._fckxhtmljob==FCKXHtml.CurrentJobNum) return false;var D=this._CreateNode(C);FCKXHtml._AppendAttributes(A,B,D,C);B._fckxhtmljob=FCKXHtml.CurrentJobNum;var E=FCKXHtml.TagProcessors[C];if (E) D=E(D,B,A);else D=this._AppendChildNodes(D,B,Boolean(FCKListsLib.NonEmptyBlockElements[C]));if (!D) return false;A.appendChild(D);break;case 3: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) {/*Do nothing... probably this is a wrong format comment.*/};break;default:A.appendChild(this.XML.createComment("Element not supported - Type: "+B.nodeType+" Name: "+B.nodeName));break;};return true;};function FCKXHtml_CreateNode_StrongEm(A){switch (A){case 'b':A='strong';break;case 'i':A='em';break;};return this.XML.createElement(A);};function FCKXHtml_CreateNode_Normal(A){return this.XML.createElement(A);};FCKXHtml._AppendSpecialItem=function(A){return '___FCKsi___'+FCKXHtml.SpecialBlocks.AddItem(A);};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._RemoveAttribute=function(A,B,C){var D=A.attributes.getNamedItem(C);if (D&&B.test(D.nodeValue)){var E=D.nodeValue.replace(B,'');if (E.length==0) A.attributes.removeNamedItem(C);else D.nodeValue=E;}};FCKXHtml.TagProcessors={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);return A;},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){FCKXHtml._RemoveAttribute(A,FCKRegexLib.FCK_Class,'class');if (B.name) FCKXHtml._AppendAttribute(A,'name',B.name);};A=FCKXHtml._AppendChildNodes(A,B,false);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;},style:function(A,B){if (!A.attributes.getNamedItem('type')) FCKXHtml._AppendAttribute(A,'type','text/css');A.appendChild(FCKXHtml.XML.createTextNode(FCKXHtml._AppendSpecialItem(B.innerHTML)));return A;},title:function(A,B){A.appendChild(FCKXHtml.XML.createTextNode(FCK.EditorDocument.title));return A;},table:function(A,B){if (FCKBrowserInfo.IsIE) FCKXHtml._RemoveAttribute(A,FCKRegexLib.FCK_Class,'class');A=FCKXHtml._AppendChildNodes(A,B,false);return A;},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;},span:function(A,B){if (B.innerHTML.length==0) return false;A=FCKXHtml._AppendChildNodes(A,B,false);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;}};FCKXHtml.TagProcessors.ul=FCKXHtml.TagProcessors.ol;\r
+FCKXHtml._GetMainXmlString=function(){return this.MainNode.xml;};FCKXHtml._AppendAttributes=function(A,B,C,D){var E=B.attributes;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') H=B.style.cssText.replace(FCKRegexLib.StyleProperties,FCKTools.ToLowerCase);else if (G=='class'||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);}}};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['font']=function(A,B){if (A.attributes.length==0) A=FCKXHtml.XML.createDocumentFragment();A=FCKXHtml._AppendChildNodes(A,B);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['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['area']=function(A,B){if (!A.attributes.getNamedItem('coords')){var C=B.getAttribute('coords',2);if (C&&C!='0,0,0') FCKXHtml._AppendAttribute(A,'coords',C);};if (!A.attributes.getNamedItem('shape')){var D=B.getAttribute('shape',2);if (D&&D.length>0) FCKXHtml._AppendAttribute(A,'shape',D);};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['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);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;};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;}\r
+var FCKCodeFormatter={};FCKCodeFormatter.Init=function(){var A=this.Regex={};A.BlocksOpener=/\<(P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|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|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)[ \/\>]/i;A.DecreaseIndent=/^\<\/(HTML|HEAD|BODY|FORM|TABLE|TBODY|THEAD|TR|UL|OL)[ \>]/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.AddItem(C)+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();}\r
+var FCKUndo={};FCKUndo.SavedData=[];FCKUndo.CurrentIndex=-1;FCKUndo.TypesCount=FCKUndo.MaxTypes=25;FCKUndo.Typing=false;FCKUndo.SaveUndoStep=function(){if (FCK.EditMode!=0) return;FCKUndo.SavedData=FCKUndo.SavedData.slice(0,FCKUndo.CurrentIndex+1);var A=FCK.EditorDocument.body.innerHTML;if (FCKUndo.CurrentIndex>=0&&A==FCKUndo.SavedData[FCKUndo.CurrentIndex][0]) return;if (FCKUndo.CurrentIndex+1>=FCKConfig.MaxUndoLevels) FCKUndo.SavedData.shift();else FCKUndo.CurrentIndex++;var B;if (FCK.EditorDocument.selection.type=='Text') B=FCK.EditorDocument.selection.createRange().getBookmark();FCKUndo.SavedData[FCKUndo.CurrentIndex]=[A,B];FCK.Events.FireEvent("OnSelectionChange");};FCKUndo.CheckUndoState=function(){return (FCKUndo.Typing||FCKUndo.CurrentIndex>0);};FCKUndo.CheckRedoState=function(){return (!FCKUndo.Typing&&FCKUndo.CurrentIndex<(FCKUndo.SavedData.length-1));};FCKUndo.Undo=function(){if (FCKUndo.CheckUndoState()){if (FCKUndo.CurrentIndex==(FCKUndo.SavedData.length-1)){FCKUndo.SaveUndoStep();};FCKUndo._ApplyUndoLevel(--FCKUndo.CurrentIndex);FCK.Events.FireEvent("OnSelectionChange");}};FCKUndo.Redo=function(){if (FCKUndo.CheckRedoState()){FCKUndo._ApplyUndoLevel(++FCKUndo.CurrentIndex);FCK.Events.FireEvent("OnSelectionChange");}};FCKUndo._ApplyUndoLevel=function(A){var B=FCKUndo.SavedData[A];if (!B) return;FCK.SetInnerHtml(B[0]);if (B[1]){var C=FCK.EditorDocument.selection.createRange();C.moveToBookmark(B[1]);C.select();};FCKUndo.TypesCount=0;FCKUndo.Typing=false;}\r
+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.childNodes.length>0) C.removeChild(C.childNodes[0]);if (this.Mode==0){var E=this.IFrame=D.createElement('iframe');E.src='javascript:void(0)';E.frameBorder=0;E.width=E.height='100%';C.appendChild(E);if (FCKBrowserInfo.IsIE) A=A.replace(/(<base[^>]*?)\s*\/?>(?!\s*<\/base>)/gi,'$1></base>');else if (!B){if (FCKBrowserInfo.IsGecko) A=A.replace(/(<body[^>]*>)\s*(<\/body>)/i,'$1'+GECKO_BOGUS+'$2');var F=A.match(FCKRegexLib.BodyContents);if (F){A=F[1]+'&nbsp;'+F[3];this._BodyHTML=F[2];}else this._BodyHTML=A;};this.Window=E.contentWindow;var G=this.Document=this.Window.document;G.open();G.write(A);G.close();if (FCKBrowserInfo.IsGecko10&&!B){this.Start(A,true);return;};this.Window._FCKEditingArea=this;if (FCKBrowserInfo.IsGecko10) this.Window.setTimeout(FCKEditingArea_CompleteStart,500);else FCKEditingArea_CompleteStart.call(this.Window);}else{var H=this.Textarea=D.createElement('textarea');H.className='SourceField';H.dir='ltr';H.style.width=H.style.height='100%';H.style.border='none';C.appendChild(H);H.value=A;FCKTools.RunFunction(this.OnLoad);}};function FCKEditingArea_CompleteStart(){if (!this.document.body){this.setTimeout(FCKEditingArea_CompleteStart,50);return;};var A=this._FCKEditingArea;A.MakeEditable();FCKTools.RunFunction(A.OnLoad);};FCKEditingArea.prototype.MakeEditable=function(){var A=this.Document;if (FCKBrowserInfo.IsIE){A.body.contentEditable=true;}else{try{A.body.spellcheck=(this.FFSpellChecker!==false);if (this._BodyHTML){A.body.innerHTML=this._BodyHTML;this._BodyHTML=null;};A.designMode='on';try{A.execCommand('styleWithCSS',false,FCKConfig.GeckoUseSPAN);}catch (e){A.execCommand('useCSS',false,!FCKConfig.GeckoUseSPAN);};A.execCommand('enableObjectResizing',false,!FCKConfig.DisableObjectResizing);A.execCommand('enableInlineTableEditing',false,!FCKConfig.DisableFFTableHandles);}catch (e) {}}};FCKEditingArea.prototype.Focus=function(){try{if (this.Mode==0){if (FCKBrowserInfo.IsIE&&this.Document.hasFocus()) return;if (FCKBrowserInfo.IsSafari) this.IFrame.focus();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) {}};function FCKEditingArea_Cleanup(){this.TargetElement=null;this.IFrame=null;this.Document=null;this.Textarea=null;if (this.Window){this.Window._FCKEditingArea=null;this.Window=null;}};\r
+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 (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;}\r
+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;}};\r
+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 (!C){if (!B&&FCKListsLib.PathBlockElements[E]!=null) B=e;if (FCKListsLib.PathBlockLimitElements[E]!=null) C=e;};D.push(e);if (E=='body') break;};e=e.parentNode;};this.Block=B;this.BlockLimit=C;this.Elements=D;};\r
+var FCKDomRange=function(A){this.Window=A;};FCKDomRange.prototype={_UpdateElementInfo:function(){if (!this._Range) this.Release(true);else{var A=this._Range.startContainer;var B=this._Range.endContainer;var C=new FCKElementPath(A);this.StartContainer=C.LastElement;this.StartBlock=C.Block;this.StartBlockLimit=C.BlockLimit;if (A!=B) C=new FCKElementPath(B);this.EndContainer=C.LastElement;this.EndBlock=C.Block;this.EndBlockLimit=C.BlockLimit;}},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;}},CheckIsCollapsed:function(){if (this._Range) return this._Range.collapsed;},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 ((B=A.firstChild)&&B.nodeType==1&&FCKListsLib.EmptyElements[B.nodeName.toLowerCase()]==null) A=B;this.MoveToElementStart(A);},InsertNode:function(A){if (this._Range) this._Range.insertNode(A);},CheckIsEmpty:function(A){if (this.CheckIsCollapsed()) return true;var B=this.Window.document.createElement('div');this._Range.cloneContents().AppendTo(B);FCKDomTools.TrimNode(B,A);return (B.innerHTML.length==0);},CheckStartOfBlock:function(){var A=this.Clone();A.Collapse(true);A.SetStart(A.StartBlock||A.StartBlockLimit,1);var B=A.CheckIsEmpty();A.Release();return B;},CheckEndOfBlock:function(A){var B=this.Clone();B.Collapse(false);B.SetEnd(B.EndBlock||B.EndBlockLimit,2);var C=B.CheckIsCollapsed();if (!C){var D=this.Window.document.createElement('div');B._Range.cloneContents().AppendTo(D);FCKDomTools.TrimNode(D,true);C=true;var E=D;while ((E=E.lastChild)){if (E.previousSibling||E.nodeType!=1||FCKListsLib.InlineChildReqElements[E.nodeName.toLowerCase()]==null){C=false;break;}}};B.Release();if (A) this.Select();return C;},CreateBookmark:function(){var A={StartId:'fck_dom_range_start_'+(new Date()).valueOf()+'_'+Math.floor(Math.random()*1000),EndId:'fck_dom_range_end_'+(new Date()).valueOf()+'_'+Math.floor(Math.random()*1000)};var B=this.Window.document;var C;var D;if (!this.CheckIsCollapsed()){C=B.createElement('span');C.id=A.EndId;C.innerHTML='&nbsp;';D=this.Clone();D.Collapse(false);D.InsertNode(C);};C=B.createElement('span');C.id=A.StartId;C.innerHTML='&nbsp;';D=this.Clone();D.Collapse(true);D.InsertNode(C);return A;},MoveToBookmark:function(A,B){var C=this.Window.document;var D=C.getElementById(A.StartId);var E=C.getElementById(A.EndId);this.SetStart(D,3);if (!B) FCKDomTools.RemoveNode(D);if (E){this.SetEnd(E,3);if (!B) FCKDomTools.RemoveNode(E);}else this.Collapse(true);},SetStart:function(A,B){var C=this._Range;if (!C) C=this._Range=this.CreateRange();switch(B){case 1:C.setStart(A,0);break;case 2:C.setStart(A,A.childNodes.length);break;case 3:C.setStartBefore(A);break;case 4:C.setStartAfter(A);};this._UpdateElementInfo();},SetEnd:function(A,B){var C=this._Range;if (!C) C=this._Range=this.CreateRange();switch(B){case 1:C.setEnd(A,0);break;case 2:C.setEnd(A,A.childNodes.length);break;case 3:C.setEndBefore(A);break;case 4:C.setEndAfter(A);};this._UpdateElementInfo();},Expand:function(A){var B,oSibling;switch (A){case 'block_contents':if (this.StartBlock) this.SetStart(this.StartBlock,1);else{B=this._Range.startContainer;if (B.nodeType==1){if (!(B=B.childNodes[this._Range.startOffset])) B=B.firstChild;};if (!B) return;while (true){oSibling=B.previousSibling;if (!oSibling){if (B.parentNode!=this.StartBlockLimit) B=B.parentNode;else break;}else if (oSibling.nodeType!=1||!(/^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/).test(oSibling.nodeName.toUpperCase())){B=oSibling;}else break;};this._Range.setStartBefore(B);};if (this.EndBlock) this.SetEnd(this.EndBlock,2);else{B=this._Range.endContainer;if (B.nodeType==1) B=B.childNodes[this._Range.endOffset]||B.lastChild;if (!B) return;while (true){oSibling=B.nextSibling;if (!oSibling){if (B.parentNode!=this.EndBlockLimit) B=B.parentNode;else break;}else if (oSibling.nodeType!=1||!(/^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/).test(oSibling.nodeName.toUpperCase())){B=oSibling;}else break;};this._Range.setEndAfter(B);};this._UpdateElementInfo();}},Release:function(A){if (!A) this.Window=null;this.StartContainer=null;this.StartBlock=null;this.StartBlockLimit=null;this.EndContainer=null;this.EndBlock=null;this.EndBlockLimit=null;this._Range=null;}};\r
+FCKDomRange.prototype.MoveToSelection=function(){this.Release(true);this._Range=new FCKW3CRange(this.Window.document);var A=this.Window.document.selection;if (A.type!='Control'){B=this._GetSelectionMarkerTag(true);this._Range.setStart(B.parentNode,FCKDomTools.GetIndexOf(B));B.parentNode.removeChild(B);var B=this._GetSelectionMarkerTag(false);this._Range.setEnd(B.parentNode,FCKDomTools.GetIndexOf(B));B.parentNode.removeChild(B);this._UpdateElementInfo();}else{var C=A.createRange().item(0);if (C){this._Range.setStartBefore(C);this._Range.setEndAfter(C);this._UpdateElementInfo();}}};FCKDomRange.prototype.Select=function(){if (this._Range){var A=this.CheckIsCollapsed();var B=this._GetRangeMarkerTag(true);if (!A) var C=this._GetRangeMarkerTag(false);var D=this.Window.document.body.createTextRange();D.moveToElementText(B);D.moveStart('character',1);if (!A){var E=this.Window.document.body.createTextRange();E.moveToElementText(C);D.setEndPoint('EndToEnd',E);D.moveEnd('character',-1);};this._Range.setStartBefore(B);B.parentNode.removeChild(B);if (A){try{D.pasteHTML('&nbsp;');D.moveStart('character',-1);}catch (e){};D.select();D.pasteHTML('');}else{this._Range.setEndBefore(C);C.parentNode.removeChild(C);D.select();}}};FCKDomRange.prototype._GetSelectionMarkerTag=function(A){var B=this.Window.document.selection.createRange();B.collapse(A===true);var C='fck_dom_range_temp_'+(new Date()).valueOf()+'_'+Math.floor(Math.random()*1000);B.pasteHTML('<span id="'+C+'"></span>');return this.Window.document.getElementById(C);};FCKDomRange.prototype._GetRangeMarkerTag=function(A){var B=this._Range;if (!A){B=B.cloneRange();B.collapse(A===true);};var C=this.Window.document.createElement('span');C.innerHTML='&nbsp;';B.insertNode(C);return C;}\r
+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));}};\r
+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 (C.childNodes.length>0&&E<=C.childNodes.length-1){if (E==0){C=C.insertBefore(this._Document.createTextNode(''),C.firstChild);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)) this.setStart(topEnd.parentNode,FCKDomTools.GetIndexOf(topEnd));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);},toString:function(){var A=this.cloneContents();var B=this._Document.createElement('div');A.AppendTo(B);return B.textContent||B.innerText;}};\r
+var FCKEnterKey=function(A,B,C){this.Window=A;this.EnterMode=B||'p';this.ShiftEnterMode=C||'br';var D=new FCKKeystrokeHandler(false);D._EnterKey=this;D.OnKeystroke=FCKEnterKey_OnKeystroke;D.SetKeystrokes([[13,'Enter'],[SHIFT+13,'ShiftEnter'],[8,'Backspace'],[46,'Delete']]);D.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();}}catch (e){};return false;};FCKEnterKey.prototype.DoEnter=function(A,B){this._HasShift=(B===true);var C=A||this.EnterMode;if (C=='br') return this._ExecuteEnterBr();else return this._ExecuteEnterBlock(C);};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 (!B.CheckIsCollapsed()) return false;var C=B.StartBlock;var D=B.EndBlock;if (B.StartBlockLimit==B.EndBlockLimit&&C&&D){if (!B.CheckIsCollapsed()){var E=B.CheckEndOfBlock();B.DeleteContents();if (C!=D){B.SetStart(D,1);B.SetEnd(D,1);};B.Select();A=(C==D);};if (B.CheckStartOfBlock()){var F=B.StartBlock;var G=FCKDomTools.GetPreviousSourceElement(F,true,['BODY',B.StartBlockLimit.nodeName],['UL','OL']);A=this._ExecuteBackspace(B,G,F);}else if (FCKBrowserInfo.IsGecko){B.Select();}};B.Release();return A;};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.TrimNode(C);FCKDomTools.TrimNode(B);A.SetStart(B,2);A.Collapse(true);var I=A.CreateBookmark();FCKDomTools.MoveChildren(C,B);A.MoveToBookmark(I);A.Select();D=true;}};return D;};FCKEnterKey.prototype.DoDelete=function(){var A=false;var B=new FCKDomRange(this.Window);B.MoveToSelection();if (B.CheckIsCollapsed()&&B.CheckEndOfBlock(FCKBrowserInfo.IsGeckoLike)){var C=B.StartBlock;var D=FCKDomTools.GetNextSourceElement(C,true,[B.StartBlockLimit.nodeName],['UL','OL']);A=this._ExecuteBackspace(B,C,D);};B.Release();return A;};FCKEnterKey.prototype._ExecuteEnterBlock=function(A,B){var C=B||new FCKDomRange(this.Window);if (!B) C.MoveToSelection();if (C.StartBlockLimit==C.EndBlockLimit){if (!C.StartBlock) this._FixBlock(C,true,A);if (!C.EndBlock) this._FixBlock(C,false,A);var D=C.StartBlock;var E=C.EndBlock;if (!C.CheckIsEmpty()) C.DeleteContents();if (D==E){var F;var G=C.CheckStartOfBlock();var H=C.CheckEndOfBlock();if (G&&!H){F=D.cloneNode(false);if (FCKBrowserInfo.IsGeckoLike) F.innerHTML=GECKO_BOGUS;D.parentNode.insertBefore(F,D);if (FCKBrowserInfo.IsIE){C.MoveToNodeContents(F);C.Select();};C.MoveToElementEditStart(D);}else{if (H){var I=D.tagName.toUpperCase();if (G&&I=='LI'){this._OutdentWithSelection(D,C);C.Release();return true;}else{if ((/^H[1-6]$/).test(I)||this._HasShift) F=this.Window.document.createElement(A);else{F=D.cloneNode(false);this._RecreateEndingTree(D,F);};if (FCKBrowserInfo.IsGeckoLike){F.innerHTML=GECKO_BOGUS;if (G) D.innerHTML=GECKO_BOGUS;}}}else{C.SetEnd(D,2);var J=C.ExtractContents();F=D.cloneNode(false);FCKDomTools.TrimNode(J.RootNode);if (J.RootNode.firstChild.nodeType==1&&J.RootNode.firstChild.tagName.toUpperCase().Equals('UL','OL')) F.innerHTML=GECKO_BOGUS;J.AppendTo(F);if (FCKBrowserInfo.IsGecko){this._AppendBogusBr(D);this._AppendBogusBr(F);}};if (F){FCKDomTools.InsertAfterNode(D,F);C.MoveToElementEditStart(F);if (FCKBrowserInfo.IsGeckoLike) F.scrollIntoView(false);}}}else{C.MoveToElementEditStart(E);};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;if (!F&&E=='LI') return this._ExecuteEnterBlock(null,B);if (!F&&D&&(/^H[1-6]$/).test(E)){FCKDebug.Output('BR - Header');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{FCKDebug.Output('BR - No Header');var G=this.Window.document.createElement('br');B.InsertNode(G);if (FCKBrowserInfo.IsGecko) FCKDomTools.InsertAfterNode(G,this.Window.document.createTextNode(''));if (D&&FCKBrowserInfo.IsGeckoLike) this._AppendBogusBr(G.parentNode);if (FCKBrowserInfo.IsIE) B.SetStart(G,4);else B.SetStart(G.nextSibling,1);};B.Collapse(true);B.Select();};B.Release();return true;};FCKEnterKey.prototype._FixBlock=function(A,B,C){var D=A.CreateBookmark();A.Collapse(B);A.Expand('block_contents');var E=this.Window.document.createElement(C);A.ExtractContents().AppendTo(E);FCKDomTools.TrimNode(E);A.InsertNode(E);A.MoveToBookmark(D);};FCKEnterKey.prototype._AppendBogusBr=function(A){if (!A) return;var B=FCKTools.GetLastItem(A.getElementsByTagName('br'));if (!B||B.getAttribute('type',2)!='_moz') A.appendChild(FCKTools.CreateBogusBR(this.Window.document));};FCKEnterKey.prototype._RecreateEndingTree=function(A,B){while ((A=A.lastChild)&&A.nodeType==1&&FCKListsLib.InlineChildReqElements[A.nodeName.toLowerCase()]!=null) B=B.insertBefore(A.cloneNode(false),B.firstChild);};FCKEnterKey.prototype._OutdentWithSelection=function(A,B){var C=B.CreateBookmark();FCKListHandler.OutdentListItem(A);B.MoveToBookmark(C);B.Select();}\r
+var FCKDocumentProcessor={};FCKDocumentProcessor._Items=[];FCKDocumentProcessor.AppendNew=function(){var A={};this._Items.AddItem(A);return A;};FCKDocumentProcessor.Process=function(A){var B,i=0;while((B=this._Items[i++])) B.ProcessDocument(A);};var FCKDocumentProcessor_CreateFakeImage=function(A,B){var C=FCK.EditorDocument.createElement('IMG');C.className=A;C.src=FCKConfig.FullBasePath+'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 FCKFlashProcessor=FCKDocumentProcessor.AppendNew();FCKFlashProcessor.ProcessDocument=function(A){var B=A.getElementsByTagName('EMBED');var C;var i=B.length-1;while (i>=0&&(C=B[i--])){var D=C.attributes['type'];if ((C.src&&C.src.EndsWith('.swf',true))||(D&&D.nodeValue=='application/x-shockwave-flash')){var E=C.cloneNode(true);if (FCKBrowserInfo.IsIE){var F=['scale','play','loop','menu','wmode','quality'];for (var G=0;G<F.length;G++){var H=C.getAttribute(F[G]);if (H) E.setAttribute(F[G],H);};E.setAttribute('type',D.nodeValue);};var I=FCKDocumentProcessor_CreateFakeImage('FCK__Flash',E);I.setAttribute('_fckflash','true',0);FCKFlashProcessor.RefreshView(I,C);C.parentNode.insertBefore(I,C);C.parentNode.removeChild(C);}}};FCKFlashProcessor.RefreshView=function(A,B){if (B.getAttribute('width')>0) A.style.width=FCKTools.ConvertHtmlSizeToStyle(B.getAttribute('width'));if (B.getAttribute('height')>0) A.style.height=FCKTools.ConvertHtmlSizeToStyle(B.getAttribute('height'));};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);}}}\r
+var FCKSelection=FCK.Selection={};\r
+FCKSelection.GetType=function(){return FCK.EditorDocument.selection.type;};FCKSelection.GetSelectedElement=function(){if (this.GetType()=='Control'){var A=FCK.EditorDocument.selection.createRange();if (A&&A.item) return FCK.EditorDocument.selection.createRange().item(0);};return null;};FCKSelection.GetParentElement=function(){switch (this.GetType()){case 'Control':return FCKSelection.GetSelectedElement().parentElement;case 'None':return null;default:return FCK.EditorDocument.selection.createRange().parentElement();}};FCKSelection.SelectNode=function(A){FCK.Focus();FCK.EditorDocument.selection.empty();var B;try{B=FCK.EditorDocument.body.createControlRange();B.addElement(A);}catch(e){B=FCK.EditorDocument.body.createTextRange();B.moveToElementText(A);};B.select();};FCKSelection.Collapse=function(A){FCK.Focus();if (this.GetType()=='Text'){var B=FCK.EditorDocument.selection.createRange();B.collapse(A==null||A===true);B.select();}};FCKSelection.HasAncestorNode=function(A){var B;if (FCK.EditorDocument.selection.type=="Control"){B=this.GetSelectedElement();}else{var C=FCK.EditorDocument.selection.createRange();B=C.parentElement();};while (B){if (B.tagName==A) return true;B=B.parentNode;};return false;};FCKSelection.MoveToAncestorNode=function(A){var B,oRange;if (!FCK.EditorDocument) return null;if (FCK.EditorDocument.selection.type=="Control"){oRange=FCK.EditorDocument.selection.createRange();for (i=0;i<oRange.length;i++){if (oRange(i).parentNode){B=oRange(i).parentNode;break;}}}else{oRange=FCK.EditorDocument.selection.createRange();B=oRange.parentElement();};while (B&&B.nodeName!=A) B=B.parentNode;return B;};FCKSelection.Delete=function(){var A=FCK.EditorDocument.selection;if (A.type.toLowerCase()!="none"){A.clear();};return A;};\r
+var FCKTableHandler={};FCKTableHandler.InsertRow=function(){var A=FCKSelection.MoveToAncestorNode('TR');if (!A) return;var B=A.cloneNode(true);A.parentNode.insertBefore(B,A);FCKTableHandler.ClearRow(A);};FCKTableHandler.DeleteRows=function(A){if (!A) A=FCKSelection.MoveToAncestorNode('TR');if (!A) return;var B=FCKTools.GetElementAscensor(A,'TABLE');if (B.rows.length==1){FCKTableHandler.DeleteTable(B);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();A.parentNode.removeChild(A);};FCKTableHandler.InsertColumn=function(){var A=FCKSelection.MoveToAncestorNode('TD')||FCKSelection.MoveToAncestorNode('TH');if (!A) return;var B=FCKTools.GetElementAscensor(A,'TABLE');var C=A.cellIndex+1;for (var i=0;i<B.rows.length;i++){var D=B.rows[i];if (D.cells.length<C) continue;A=D.cells[C-1].cloneNode(false);if (FCKBrowserInfo.IsGecko) A.innerHTML=GECKO_BOGUS;var E=D.cells[C];if (E) D.insertBefore(A,E);else D.appendChild(A);}};FCKTableHandler.DeleteColumns=function(){var A=FCKSelection.MoveToAncestorNode('TD')||FCKSelection.MoveToAncestorNode('TH');if (!A) return;var B=FCKTools.GetElementAscensor(A,'TABLE');var C=A.cellIndex;for (var i=B.rows.length-1;i>=0;i--){var D=B.rows[i];if (C==0&&D.cells.length==1){FCKTableHandler.DeleteRows(D);continue;};if (D.cells[C]) D.removeChild(D.cells[C]);}};FCKTableHandler.InsertCell=function(A){var B=A?A:FCKSelection.MoveToAncestorNode('TD');if (!B) return null;var C=FCK.EditorDocument.createElement('TD');if (FCKBrowserInfo.IsGecko) C.innerHTML=GECKO_BOGUS;if (B.cellIndex==B.parentNode.cells.length-1){B.parentNode.appendChild(C);}else{B.parentNode.insertBefore(C,B.nextSibling);};return C;};FCKTableHandler.DeleteCell=function(A){if (A.parentNode.cells.length==1){FCKTableHandler.DeleteRows(FCKTools.GetElementAscensor(A,'TR'));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.MergeCells=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length<2) return;if (A[0].parentNode!=A[A.length-1].parentNode) return;var B=isNaN(A[0].colSpan)?1:A[0].colSpan;var C='';var D=FCK.EditorDocument.createDocumentFragment();for (var i=A.length-1;i>=0;i--){var E=A[i];for (var c=E.childNodes.length-1;c>=0;c--){var F=E.removeChild(E.childNodes[c]);if ((F.hasAttribute&&F.hasAttribute('_moz_editor_bogus_node'))||(F.getAttribute&&F.getAttribute('type',2)=='_moz')) continue;D.insertBefore(F,D.firstChild);};if (i>0){B+=isNaN(E.colSpan)?1:E.colSpan;FCKTableHandler.DeleteCell(E);}};A[0].colSpan=B;if (FCKBrowserInfo.IsGecko&&D.childNodes.length==0) A[0].innerHTML=GECKO_BOGUS;else A[0].appendChild(D);};FCKTableHandler.SplitCell=function(){var A=FCKTableHandler.GetSelectedCells();if (A.length!=1) return;var B=this._CreateTableMap(A[0].parentNode.parentNode);var C=FCKTableHandler._GetCellIndexSpan(B,A[0].parentNode.rowIndex,A[0]);var D=this._GetCollumnCells(B,C);for (var i=0;i<D.length;i++){if (D[i]==A[0]){var E=this.InsertCell(A[0]);if (!isNaN(A[0].rowSpan)&&A[0].rowSpan>1) E.rowSpan=A[0].rowSpan;}else{if (isNaN(D[i].colSpan)) D[i].colSpan=2;else D[i].colSpan+=1;}}};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._GetCollumnCells=function(A,B){var C=[];for (var r=0;r<A.length;r++){var D=A[r][B];if (D&&(C.length==0||C[C.length-1]!=D)) C[C.length]=D;};return C;};FCKTableHandler._CreateTableMap=function(A){var B=A.rows;var r=-1;var C=[];for (var i=0;i<B.length;i++){r++;if (!C[r]) C[r]=[];var c=-1;for (var j=0;j<B[i].cells.length;j++){var D=B[i].cells[j];c++;while (C[r][c]) c++;var E=isNaN(D.colSpan)?1:D.colSpan;var F=isNaN(D.rowSpan)?1:D.rowSpan;for (var G=0;G<F;G++){if (!C[r+G]) C[r+G]=[];for (var H=0;H<E;H++){C[r+G][c+H]=B[i].cells[j];}};c+=E-1;}};return C;};FCKTableHandler.ClearRow=function(A){var B=A.cells;for (var i=0;i<B.length;i++){if (FCKBrowserInfo.IsGecko) B[i].innerHTML=GECKO_BOGUS;else B[i].innerHTML='';}};\r
+FCKTableHandler.GetSelectedCells=function(){var A=[];var B=FCK.EditorDocument.selection.createRange();var C=FCKSelection.GetParentElement();if (C&&C.tagName.Equals('TD','TH')) A[0]=C;else{C=FCKSelection.MoveToAncestorNode('TABLE');if (C){for (var i=0;i<C.cells.length;i++){var D=FCK.EditorDocument.selection.createRange();D.moveToElementText(C.cells[i]);if (B.inRange(D)||(B.compareEndPoints('StartToStart',D)>=0&&B.compareEndPoints('StartToEnd',D)<=0)||(B.compareEndPoints('EndToStart',D)>=0&&B.compareEndPoints('EndToEnd',D)<=0)){A[A.length]=C.cells[i];}}}};return A;};\r
+var FCKXml=function(){this.Error=false;};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) this.DOMDocument=B.responseXML;else if (B.status==0&&B.readyState==4){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);}};FCKXml.prototype.SelectNodes=function(A,B){if (this.Error) return [];if (B) return B.selectNodes(A);else return this.DOMDocument.selectNodes(A);};FCKXml.prototype.SelectSingleNode=function(A,B){if (this.Error) return null;if (B) return B.selectSingleNode(A);else return this.DOMDocument.selectSingleNode(A);}\r
+var FCKStyleDef=function(A,B){this.Name=A;this.Element=B.toUpperCase();this.IsObjectElement=FCKRegexLib.ObjectElements.test(this.Element);this.Attributes={};};FCKStyleDef.prototype.AddAttribute=function(A,B){this.Attributes[A]=B;};FCKStyleDef.prototype.GetOpenerTag=function(){var s='<'+this.Element;for (var a in this.Attributes) s+=' '+a+'="'+this.Attributes[a]+'"';return s+'>';};FCKStyleDef.prototype.GetCloserTag=function(){return '</'+this.Element+'>';};FCKStyleDef.prototype.RemoveFromSelection=function(){if (FCKSelection.GetType()=='Control') this._RemoveMe(FCK.ToolbarSet.CurrentInstance.Selection.GetSelectedElement());else this._RemoveMe(FCK.ToolbarSet.CurrentInstance.Selection.GetParentElement());}\r
+FCKStyleDef.prototype.ApplyToSelection=function(){var A=FCK.ToolbarSet.CurrentInstance.EditorDocument.selection;if (A.type=='Text'){var B=A.createRange();var e=document.createElement(this.Element);e.innerHTML=B.htmlText;this._AddAttributes(e);this._RemoveDuplicates(e);B.pasteHTML(e.outerHTML);}else if (A.type=='Control'){var C=FCK.ToolbarSet.CurrentInstance.Selection.GetSelectedElement();if (C.tagName==this.Element) this._AddAttributes(C);}};FCKStyleDef.prototype._AddAttributes=function(A){for (var a in this.Attributes){switch (a.toLowerCase()){case 'style':A.style.cssText=this.Attributes[a];break;case 'class':A.setAttribute('className',this.Attributes[a],0);break;case 'src':A.setAttribute('_fcksavedurl',this.Attributes[a],0);default:A.setAttribute(a,this.Attributes[a],0);}}};FCKStyleDef.prototype._RemoveDuplicates=function(A){for (var i=0;i<A.children.length;i++){var B=A.children[i];this._RemoveDuplicates(B);if (this.IsEqual(B)) FCKTools.RemoveOuterTags(B);}};FCKStyleDef.prototype.IsEqual=function(e){if (e.tagName!=this.Element) return false;for (var a in this.Attributes){switch (a.toLowerCase()){case 'style':if (e.style.cssText.toLowerCase()!=this.Attributes[a].toLowerCase()) return false;break;case 'class':if (e.getAttribute('className',0)!=this.Attributes[a]) return false;break;default:if (e.getAttribute(a,0)!=this.Attributes[a]) return false;}};return true;};FCKStyleDef.prototype._RemoveMe=function(A){if (!A) return;var B=A.parentElement;if (this.IsEqual(A)){if (this.IsObjectElement){for (var a in this.Attributes){switch (a.toLowerCase()){case 'class':A.removeAttribute('className',0);break;default:A.removeAttribute(a,0);}};return;}else FCKTools.RemoveOuterTags(A);};this._RemoveMe(B);}\r
+var FCKStylesLoader=function(){this.Styles={};this.StyleGroups={};this.Loaded=false;this.HasObjectElements=false;};FCKStylesLoader.prototype.Load=function(A){var B=new FCKXml();B.LoadUrl(A);var C=B.SelectNodes('Styles/Style');for (var i=0;i<C.length;i++){var D=C[i].attributes.getNamedItem('element').value.toUpperCase();var E=new FCKStyleDef(C[i].attributes.getNamedItem('name').value,D);if (E.IsObjectElement) this.HasObjectElements=true;var F=B.SelectNodes('Attribute',C[i]);for (var j=0;j<F.length;j++){var G=F[j].attributes.getNamedItem('name').value;var H=F[j].attributes.getNamedItem('value').value;if (G.toLowerCase()=='style'){var I=document.createElement('SPAN');I.style.cssText=H;H=I.style.cssText;};E.AddAttribute(G,H);};this.Styles[E.Name]=E;var J=this.StyleGroups[D];if (J==null){this.StyleGroups[D]=[];J=this.StyleGroups[D];};J[J.length]=E;};this.Loaded=true;}\r
+var FCKNamedCommand=function(A){this.Name=A;};FCKNamedCommand.prototype.Execute=function(){FCK.ExecuteNamedCommand(this.Name);};FCKNamedCommand.prototype.GetState=function(){return FCK.GetNamedCommandState(this.Name);};\r
+var FCKDialogCommand=function(A,B,C,D,E,F,G){this.Name=A;this.Title=B;this.Url=C;this.Width=D;this.Height=E;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,null,null,this.Resizable);};FCKDialogCommand.prototype.GetState=function(){if (this.GetStateFunction) return this.GetStateFunction(this.GetStateParam);else return 0;};var FCKUndefinedCommand=function(){this.Name='Undefined';};FCKUndefinedCommand.prototype.Execute=function(){alert(FCKLang.NotImplemented);};FCKUndefinedCommand.prototype.GetState=function(){return 0;};var FCKFontNameCommand=function(){this.Name='FontName';};FCKFontNameCommand.prototype.Execute=function(A){if (A==null||A==""){}else FCK.ExecuteNamedCommand('FontName',A);};FCKFontNameCommand.prototype.GetState=function(){return FCK.GetNamedCommandValue('FontName');};var FCKFontSizeCommand=function(){this.Name='FontSize';};FCKFontSizeCommand.prototype.Execute=function(A){if (typeof(A)=='string') A=parseInt(A,10);if (A==null||A==''){FCK.ExecuteNamedCommand('FontSize',3);}else FCK.ExecuteNamedCommand('FontSize',A);};FCKFontSizeCommand.prototype.GetState=function(){return FCK.GetNamedCommandValue('FontSize');};var FCKFormatBlockCommand=function(){this.Name='FormatBlock';};FCKFormatBlockCommand.prototype.Execute=function(A){if (A==null||A=='') FCK.ExecuteNamedCommand('FormatBlock','<P>');else if (A=='div'&&FCKBrowserInfo.IsGecko) FCK.ExecuteNamedCommand('FormatBlock','div');else FCK.ExecuteNamedCommand('FormatBlock','<'+A+'>');};FCKFormatBlockCommand.prototype.GetState=function(){return FCK.GetNamedCommandValue('FormatBlock');};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.SetHTML('');FCKUndo.Typing=true;};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,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(){if (FCKBrowserInfo.IsIE) FCKUndo.Undo();else FCK.ExecuteNamedCommand('Undo');};FCKUndoCommand.prototype.GetState=function(){if (FCKBrowserInfo.IsIE) return (FCKUndo.CheckUndoState()?0:-1);else return FCK.GetNamedCommandState('Undo');};var FCKRedoCommand=function(){this.Name='Redo';};FCKRedoCommand.prototype.Execute=function(){if (FCKBrowserInfo.IsIE) FCKUndo.Redo();else FCK.ExecuteNamedCommand('Redo');};FCKRedoCommand.prototype.GetState=function(){if (FCKBrowserInfo.IsIE) return (FCKUndo.CheckRedoState()?0:-1);else return FCK.GetNamedCommandState('Redo');};var FCKPageBreakCommand=function(){this.Name='PageBreak';};FCKPageBreakCommand.prototype.Execute=function(){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);A=FCK.InsertElement(A);};FCKPageBreakCommand.prototype.GetState=function(){return 0;};var FCKUnlinkCommand=function(){this.Name='Unlink';};FCKUnlinkCommand.prototype.Execute=function(){if (FCKBrowserInfo.IsGecko){var A=FCK.Selection.MoveToAncestorNode('A');if (A) FCKTools.RemoveOuterTags(A);return;};FCK.ExecuteNamedCommand(this.Name);};FCKUnlinkCommand.prototype.GetState=function(){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 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(){return 0;};var FCKPasteCommand=function(){this.Name='Paste';};FCKPasteCommand.prototype={Execute:function(){if (FCKBrowserInfo.IsIE) FCK.Paste();else FCK.ExecuteNamedCommand('Paste');},GetState:function(){return FCK.GetNamedCommandState('Paste');}};\r
+var FCKSpellCheckCommand=function(){this.Name='SpellCheck';this.IsEnabled=(FCKConfig.SpellChecker=='ieSpell'||FCKConfig.SpellChecker=='SpellerPages');};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;}};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(){return this.IsEnabled?0:-1;}\r
+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.SkinPath+'fck_editor.css');this._Panel.MainNode.className='FCK_Panel';this._CreatePanelBody(this._Panel.Document,this._Panel.MainNode);FCKTools.DisableSelection(this._Panel.Document.body);};FCKTextColorCommand.prototype.Execute=function(A,B,C){FCK._ActiveColorPanelType=this.Type;this._Panel.Show(A,B,C);};FCKTextColorCommand.prototype.SetColor=function(A){if (FCK._ActiveColorPanelType=='ForeColor') FCK.ExecuteNamedCommand('ForeColor',A);else if (FCKBrowserInfo.IsGeckoLike){if (FCKBrowserInfo.IsGecko&&!FCKConfig.GeckoUseSPAN) FCK.EditorDocument.execCommand('useCSS',false,false);FCK.ExecuteNamedCommand('hilitecolor',A);if (FCKBrowserInfo.IsGecko&&!FCKConfig.GeckoUseSPAN) FCK.EditorDocument.execCommand('useCSS',false,true);}else FCK.ExecuteNamedCommand('BackColor',A);delete FCK._ActiveColorPanelType;};FCKTextColorCommand.prototype.GetState=function(){return 0;};function FCKTextColorCommand_OnMouseOver()   { this.className='ColorSelected';};function FCKTextColorCommand_OnMouseOut()    { this.className='ColorDeselected';};function FCKTextColorCommand_OnClick(){this.className='ColorDeselected';this.Command.SetColor('#'+this.Color);this.Command._Panel.Hide();};function FCKTextColorCommand_AutoOnClick(){this.className='ColorDeselected';this.Command.SetColor('');this.Command._Panel.Hide();};function FCKTextColorCommand_MoreOnClick(){this.className='ColorDeselected';this.Command._Panel.Hide();FCKDialog.OpenDialog('FCKDialog_Color',FCKLang.DlgColorTitle,'dialog/fck_colorselector.html',400,330,this.Command.SetColor);};FCKTextColorCommand.prototype._CreatePanelBody=function(A,B){function CreateSelectionDiv(){var C=A.createElement("DIV");C.className='ColorDeselected';C.onmouseover=FCKTextColorCommand_OnMouseOver;C.onmouseout=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>';C.Command=this;C.onclick=FCKTextColorCommand_AutoOnClick;var G=FCKConfig.FontColors.toString().split(',');var H=0;while (H<G.length){var I=D.insertRow(-1);for (var i=0;i<8&&H<G.length;i++,H++){C=I.insertCell(-1).appendChild(CreateSelectionDiv());C.Color=G[H];C.innerHTML='<div class="ColorBoxBorder"><div class="ColorBox" style="background-color: #'+G[H]+'"></div></div>';C.Command=this;C.onclick=FCKTextColorCommand_OnClick;}};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>';C.Command=this;C.onclick=FCKTextColorCommand_MoreOnClick;}\r
+var FCKPastePlainTextCommand=function(){this.Name='PasteText';};FCKPastePlainTextCommand.prototype.Execute=function(){FCK.PasteAsPlainText();};FCKPastePlainTextCommand.prototype.GetState=function(){return FCK.GetNamedCommandState('Paste');};\r
+var FCKPasteWordCommand=function(){this.Name='PasteWord';};FCKPasteWordCommand.prototype.Execute=function(){FCK.PasteFromWord();};FCKPasteWordCommand.prototype.GetState=function(){if (FCKConfig.ForcePasteAsPlainText) return -1;else return FCK.GetNamedCommandState('Paste');};\r
+var FCKTableCommand=function(A){this.Name=A;};FCKTableCommand.prototype.Execute=function(){FCKUndo.SaveUndoStep();switch (this.Name){case 'TableInsertRow':FCKTableHandler.InsertRow();break;case 'TableDeleteRows':FCKTableHandler.DeleteRows();break;case 'TableInsertColumn':FCKTableHandler.InsertColumn();break;case 'TableDeleteColumns':FCKTableHandler.DeleteColumns();break;case 'TableInsertCell':FCKTableHandler.InsertCell();break;case 'TableDeleteCells':FCKTableHandler.DeleteCells();break;case 'TableMergeCells':FCKTableHandler.MergeCells();break;case 'TableSplitCell':FCKTableHandler.SplitCell();break;case 'TableDelete':FCKTableHandler.DeleteTable();break;default:alert(FCKLang.UnknownCommand.replace(/%1/g,this.Name));}};FCKTableCommand.prototype.GetState=function(){return 0;}\r
+var FCKStyleCommand=function(){this.Name='Style';this.StylesLoader=new FCKStylesLoader();this.StylesLoader.Load(FCKConfig.StylesXmlPath);this.Styles=this.StylesLoader.Styles;};FCKStyleCommand.prototype.Execute=function(A,B){FCKUndo.SaveUndoStep();if (B.Selected) B.Style.RemoveFromSelection();else B.Style.ApplyToSelection();FCKUndo.SaveUndoStep();FCK.Focus();FCK.Events.FireEvent("OnSelectionChange");};FCKStyleCommand.prototype.GetState=function(){if (!FCK.EditorDocument) return -1;var A=FCK.EditorDocument.selection;if (FCKSelection.GetType()=='Control'){var e=FCKSelection.GetSelectedElement();if (e) return this.StylesLoader.StyleGroups[e.tagName]?0:-1;};return 0;};FCKStyleCommand.prototype.GetActiveStyles=function(){var A=[];if (FCKSelection.GetType()=='Control') this._CheckStyle(FCKSelection.GetSelectedElement(),A,false);else this._CheckStyle(FCKSelection.GetParentElement(),A,true);return A;};FCKStyleCommand.prototype._CheckStyle=function(A,B,C){if (!A) return;if (A.nodeType==1){var D=this.StylesLoader.StyleGroups[A.tagName];if (D){for (var i=0;i<D.length;i++){if (D[i].IsEqual(A)) B[B.length]=D[i];}}};if (C) this._CheckStyle(A.parentNode,B,C);}\r
+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;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);};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 H=FCKTools.GetViewPaneSize(C);B.position="absolute";B.zIndex=FCKConfig.FloatingPanelsZIndex-1;B.left="0px";B.top="0px";B.width=H.Width+"px";B.height=H.Height+"px";if (!FCKBrowserInfo.IsIE){B.borderRight=B.borderBottom="9999px solid white";B.backgroundColor="white";};C.scrollTo(0,0);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();FCK.EditingArea.MakeEditable();FCK.Focus();};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';};\r
+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 'DocProps':B=new FCKDialogCommand('DocProps',FCKLang.DocProps,'dialog/fck_docprops.html',400,390,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,330);break;case 'Unlink':B=new FCKUnlinkCommand();break;case 'Anchor':B=new FCKDialogCommand('Anchor',FCKLang.DlgAnchorTitle,'dialog/fck_anchor.html',370,170);break;case 'BulletedList':B=new FCKDialogCommand('BulletedList',FCKLang.BulletedListProp,'dialog/fck_listprop.html?UL',370,170);break;case 'NumberedList':B=new FCKDialogCommand('NumberedList',FCKLang.NumberedListProp,'dialog/fck_listprop.html?OL',370,170);break;case 'About':B=new FCKDialogCommand('About',FCKLang.About,'dialog/fck_about.html',400,330);break;case 'Find':B=new FCKDialogCommand('Find',FCKLang.DlgFindTitle,'dialog/fck_find.html',340,170);break;case 'Replace':B=new FCKDialogCommand('Replace',FCKLang.DlgReplaceTitle,'dialog/fck_replace.html',340,200);break;case 'Image':B=new FCKDialogCommand('Image',FCKLang.DlgImgTitle,'dialog/fck_image.html',450,400);break;case 'Flash':B=new FCKDialogCommand('Flash',FCKLang.DlgFlashTitle,'dialog/fck_flash.html',450,400);break;case 'SpecialChar':B=new FCKDialogCommand('SpecialChar',FCKLang.DlgSpecialCharTitle,'dialog/fck_specialchar.html',400,320);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',450,250);break;case 'TableProp':B=new FCKDialogCommand('Table',FCKLang.DlgTableTitle,'dialog/fck_table.html?Parent',400,250);break;case 'TableCellProp':B=new FCKDialogCommand('TableCell',FCKLang.DlgCellTitle,'dialog/fck_tablecell.html',550,250);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 '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 'TableInsertRow':B=new FCKTableCommand('TableInsertRow');break;case 'TableDeleteRows':B=new FCKTableCommand('TableDeleteRows');break;case 'TableInsertColumn':B=new FCKTableCommand('TableInsertColumn');break;case 'TableDeleteColumns':B=new FCKTableCommand('TableDeleteColumns');break;case 'TableInsertCell':B=new FCKTableCommand('TableInsertCell');break;case 'TableDeleteCells':B=new FCKTableCommand('TableDeleteCells');break;case 'TableMergeCells':B=new FCKTableCommand('TableMergeCells');break;case 'TableSplitCell':B=new FCKTableCommand('TableSplitCell');break;case 'TableDelete':B=new FCKTableCommand('TableDelete');break;case 'Form':B=new FCKDialogCommand('Form',FCKLang.Form,'dialog/fck_form.html',380,230);break;case 'Checkbox':B=new FCKDialogCommand('Checkbox',FCKLang.Checkbox,'dialog/fck_checkbox.html',380,230);break;case 'Radio':B=new FCKDialogCommand('Radio',FCKLang.RadioButton,'dialog/fck_radiobutton.html',380,230);break;case 'TextField':B=new FCKDialogCommand('TextField',FCKLang.TextField,'dialog/fck_textfield.html',380,230);break;case 'Textarea':B=new FCKDialogCommand('Textarea',FCKLang.Textarea,'dialog/fck_textarea.html',380,230);break;case 'HiddenField':B=new FCKDialogCommand('HiddenField',FCKLang.HiddenField,'dialog/fck_hiddenfield.html',380,230);break;case 'Button':B=new FCKDialogCommand('Button',FCKLang.Button,'dialog/fck_button.html',380,230);break;case 'Select':B=new FCKDialogCommand('Select',FCKLang.SelectionField,'dialog/fck_select.html',400,380);break;case 'ImageButton':B=new FCKDialogCommand('ImageButton',FCKLang.ImageButton,'dialog/fck_image.html?ImageButton',450,400);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 'SelectAll':B=new FCKSelectAllCommand();break;case 'Undefined':B=new FCKUndefinedCommand();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;};\r
+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();B=this.Document=this._Popup.document;FCK.IECleanup.AddItem(this,FCKPanel_Cleanup);}else{var C=this._IFrame=this._Window.document.createElement('iframe');C.src='javascript:void(0)';C.allowTransparency=true;C.frameBorder='0';C.scrolling='no';C.style.position='absolute';C.style.zIndex=FCKConfig.FloatingPanelsZIndex;C.width=C.height=0;if (this._Window==window.parent&&window.frameElement) window.frameElement.parentNode.insertBefore(C,window.frameElement);else this._Window.document.body.appendChild(C);var D=C.contentWindow;B=this.Document=D.document;var E='';if (FCKBrowserInfo.IsSafari) E='<base href="'+window.document.location+'">';B.open();B.write('<html><head>'+E+'<\/head><body style="margin:0px;padding:0px;"><\/body><\/html>');B.close();FCKTools.AddEventListenerEx(D,'focus',FCKPanel_Window_OnFocus,this);FCKTools.AddEventListenerEx(D,'blur',FCKPanel_Window_OnBlur,this);};B.dir=FCKLang.Dir;B.oncontextmenu=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.Show=function(x,y,A,B,C){var D;if (this._Popup){this._Popup.show(x,y,0,0,A);this.MainNode.style.width=B?B+'px':'';this.MainNode.style.height=C?C+'px':'';D=this.MainNode.offsetWidth;if (this.IsRTL){if (this.IsContextMenu) x=x-D+1;else if (A) x=(x*-1)+A.offsetWidth-D;};this._Popup.show(x,y,D,this.MainNode.offsetHeight,A);if (this.OnHide){if (this._Timer) CheckPopupOnHide.call(this,true);this._Timer=FCKTools.SetInterval(CheckPopupOnHide,100,this);}}else{if (typeof(FCKFocusManager)!='undefined') FCKFocusManager.Lock();if (this.ParentPanel) this.ParentPanel.Lock();this.MainNode.style.width=B?B+'px':'';this.MainNode.style.height=C?C+'px':'';D=this.MainNode.offsetWidth;if (!B)  this._IFrame.width=1;if (!C)    this._IFrame.height=1;D=this.MainNode.offsetWidth;var E=FCKTools.GetElementPosition(A.nodeType==9?(FCKTools.IsStrictMode(A)?A.documentElement:A.body):A,this._Window);if (this.IsRTL&&!this.IsContextMenu) x=(x*-1);x+=E.X;y+=E.Y;if (this.IsRTL){if (this.IsContextMenu) x=x-D+1;else if (A) x=x+A.offsetWidth-D;}else{var F=FCKTools.GetViewPaneSize(this._Window);var G=FCKTools.GetScrollPosition(this._Window);var H=F.Height+G.Y;var I=F.Width+G.X;if ((x+D)>I) x-=x+D-I;if ((y+this.MainNode.offsetHeight)>H) y-=y+this.MainNode.offsetHeight-H;};if (x<0) x=0;this._IFrame.style.left=x+'px';this._IFrame.style.top=y+'px';var J=D;var K=this.MainNode.offsetHeight;this._IFrame.width=J;this._IFrame.height=K;this._IFrame.contentWindow.focus();};this._IsOpened=true;FCKTools.RunFunction(this.OnShow,this);};FCKPanel.prototype.Hide=function(A){if (this._Popup) this._Popup.hide();else{if (!this._IsOpened) return;if (typeof(FCKFocusManager)!='undefined') FCKFocusManager.Unlock();this._IFrame.width=this._IFrame.height=0;this._IsOpened=false;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;FCKTools.RunFunction(this.OnHide,this);}};function FCKPanel_Cleanup(){this._Popup=null;this._Window=null;this.Document=null;this.MainNode=null;}\r
+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;}\r
+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=this.MainElement;if (B){FCKToolbarButtonUI_Cleanup.call(this);if (B.parentNode) B.parentNode.removeChild(B);B=this.MainElement=null;};var C=FCKTools.GetElementDocument(A);B=this.MainElement=C.createElement('DIV');B._FCKButton=this;B.title=this.Tooltip;if (FCKBrowserInfo.IsGecko) B.onmousedown=FCKTools.CancelEvent;this.ChangeState(this.State,true);if (this.Style==0&&!this.ShowArrow){B.appendChild(this.Icon.CreateIconElement(C));}else{var D=B.appendChild(C.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(C));else F.appendChild(this._CreatePaddingElement(C));if (this.Style==1||this.Style==2){F=E.insertCell(-1);F.className='TB_Button_Text';F.noWrap=true;F.appendChild(C.createTextNode(this.Label));};if (this.ShowArrow){if (this.Style!=0){E.insertCell(-1).appendChild(this._CreatePaddingElement(C));};F=E.insertCell(-1);var G=F.appendChild(C.createElement('IMG'));G.src=FCKConfig.SkinPath+'images/toolbar.buttonarrow.gif';G.width=5;G.height=3;};F=E.insertCell(-1);F.appendChild(this._CreatePaddingElement(C));};A.appendChild(B);};FCKToolbarButtonUI.prototype.ChangeState=function(A,B){if (!B&&this.State==A) return;var e=this.MainElement;switch (parseInt(A,10)){case 0:e.className='TB_Button_Off';e.onmouseover=FCKToolbarButton_OnMouseOverOff;e.onmouseout=FCKToolbarButton_OnMouseOutOff;e.onclick=FCKToolbarButton_OnClick;break;case 1:e.className='TB_Button_On';e.onmouseover=FCKToolbarButton_OnMouseOverOn;e.onmouseout=FCKToolbarButton_OnMouseOutOn;e.onclick=FCKToolbarButton_OnClick;break;case -1:e.className='TB_Button_Disabled';e.onmouseover=null;e.onmouseout=null;e.onclick=null;break;};this.State=A;};function FCKToolbarButtonUI_Cleanup(){if (this.MainElement){this.MainElement._FCKButton=null;this.MainElement=null;}};function FCKToolbarButton_OnMouseOverOn(){this.className='TB_Button_On_Over';};function FCKToolbarButton_OnMouseOutOn(){this.className='TB_Button_On';};function FCKToolbarButton_OnMouseOverOff(){this.className='TB_Button_Off_Over';};function FCKToolbarButton_OnMouseOutOff(){this.className='TB_Button_Off';};function FCKToolbarButton_OnClick(e){if (this._FCKButton.OnClick) this._FCKButton.OnClick(this._FCKButton);};\r
+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];};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=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).GetState();if (A==this._UIButton.State) return;this._UIButton.ChangeState(A);};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);}\r
+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.SkinPath+'fck_editor.css');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(){this.className=this.originalClass;this.FCKSpecialCombo._Panel.Hide();this.FCKSpecialCombo.SetLabel(this.FCKItemLabel);if (typeof(this.FCKSpecialCombo.OnSelect)=='function') this.FCKSpecialCombo.OnSelect(this.FCKItemID,this);};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.FCKItemID=A;E.FCKItemLabel=C||A;E.FCKSpecialCombo=this;E.Selected=false;if (FCKBrowserInfo.IsIE) E.style.width='100%';if (D) E.style.backgroundColor=D;E.onmouseover=FCKSpecialCombo_ItemOnMouseOver;E.onmouseout=FCKSpecialCombo_ItemOnMouseOut;E.onclick=FCKSpecialCombo_ItemOnClick;this.Items[A.toString().toLowerCase()]=E;return E;};FCKSpecialCombo.prototype.SelectItem=function(A){A=A?A.toString().toLowerCase():'';var B=this.Items[A];if (B){B.className=B.originalClass='SC_ItemSelected';B.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){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){this.Label=A.length==0?'&nbsp;':A;if (this._LabelEl){this._LabelEl.innerHTML=this.Label;FCKTools.DisableSelection(this._LabelEl);}};FCKSpecialCombo.prototype.SetEnabled=function(A){this.Enabled=A;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>';};G.SpecialCombo=this;G.onmouseover=FCKSpecialCombo_OnMouseOver;G.onmouseout=FCKSpecialCombo_OnMouseOut;G.onclick=FCKSpecialCombo_OnClick;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(){if (this.SpecialCombo.Enabled){switch (this.SpecialCombo.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(){switch (this.SpecialCombo.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){var A=this.SpecialCombo;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);}};\r
+var FCKToolbarSpecialCombo=function(){this.SourceView=false;this.ContextSensitive=true;this._LastValue=null;};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;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);};\r
+var FCKToolbarFontsCombo=function(A,B){this.CommandName='FontName';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;};FCKToolbarFontsCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarFontsCombo.prototype.GetLabel=function(){return FCKLang.Font;};FCKToolbarFontsCombo.prototype.CreateItems=function(A){var B=FCKConfig.FontNames.split(';');for (var i=0;i<B.length;i++) this._Combo.AddItem(B[i],'<font face="'+B[i]+'" style="font-size: 12px">'+B[i]+'</font>');}\r
+var FCKToolbarFontSizeCombo=function(A,B){this.CommandName='FontSize';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;};FCKToolbarFontSizeCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarFontSizeCombo.prototype.GetLabel=function(){return FCKLang.FontSize;};FCKToolbarFontSizeCombo.prototype.CreateItems=function(A){A.FieldWidth=70;var B=FCKConfig.FontSizes.split(';');for (var i=0;i<B.length;i++){var C=B[i].split('/');this._Combo.AddItem(C[0],'<font size="'+C[0]+'">'+C[1]+'</font>',C[1]);}}\r
+var FCKToolbarFontFormatCombo=function(A,B){this.CommandName='FontFormat';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;this.NormalLabel='Normal';this.PanelWidth=190;};FCKToolbarFontFormatCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarFontFormatCombo.prototype.GetLabel=function(){return FCKLang.FontFormat;};FCKToolbarFontFormatCombo.prototype.CreateItems=function(A){var B=A._Panel.Document;FCKTools.AppendStyleSheet(B,FCKConfig.ToolbarComboPreviewCSS);if (FCKConfig.BodyId&&FCKConfig.BodyId.length>0) B.body.id=FCKConfig.BodyId;if (FCKConfig.BodyClass&&FCKConfig.BodyClass.length>0) B.body.className+=' '+FCKConfig.BodyClass;var C=FCKLang['FontFormats'].split(';');var D={p:C[0],pre:C[1],address:C[2],h1:C[3],h2:C[4],h3:C[5],h4:C[6],h5:C[7],h6:C[8],div:C[9]};var E=FCKConfig.FontFormats.split(';');for (var i=0;i<E.length;i++){var F=E[i];var G=D[F];if (F=='p') this.NormalLabel=G;this._Combo.AddItem(F,'<div class="BaseFont"><'+F+'>'+G+'</'+F+'></div>',G);}};if (FCKBrowserInfo.IsIE){FCKToolbarFontFormatCombo.prototype.RefreshActiveItems=function(A,B){if (B==this.NormalLabel){if (A.Label!='&nbsp;') A.DeselectAll(true);}else{if (this._LastValue==B) return;A.SelectItemByLabel(B,true);};this._LastValue=B;}}\r
+var FCKToolbarStyleCombo=function(A,B){this.CommandName='Style';this.Label=this.GetLabel();this.Tooltip=A?A:this.Label;this.Style=B?B:2;};FCKToolbarStyleCombo.prototype=new FCKToolbarSpecialCombo;FCKToolbarStyleCombo.prototype.GetLabel=function(){return FCKLang.Style;};FCKToolbarStyleCombo.prototype.CreateItems=function(A){var B=A._Panel.Document;FCKTools.AppendStyleSheet(B,FCKConfig.ToolbarComboPreviewCSS);B.body.className+=' ForceBaseFont';if (FCKConfig.BodyId&&FCKConfig.BodyId.length>0) B.body.id=FCKConfig.BodyId;if (FCKConfig.BodyClass&&FCKConfig.BodyClass.length>0) B.body.className+=' '+FCKConfig.BodyClass;if (!(FCKBrowserInfo.IsGecko&&FCKBrowserInfo.IsGecko10)) A.OnBeforeClick=this.RefreshVisibleItems;var C=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).Styles;for (var s in C){var D=C[s];var E;if (D.IsObjectElement) E=A.AddItem(s,s);else E=A.AddItem(s,D.GetOpenerTag()+s+D.GetCloserTag());E.Style=D;}};FCKToolbarStyleCombo.prototype.RefreshActiveItems=function(A){A.DeselectAll();var B=FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(this.CommandName).GetActiveStyles();if (B.length>0){for (var i=0;i<B.length;i++) A.SelectItem(B[i].Name);A.SetLabelById(B[0].Name);}else A.SetLabel('');};FCKToolbarStyleCombo.prototype.RefreshVisibleItems=function(A){if (FCKSelection.GetType()=='Control') var B=FCKSelection.GetSelectedElement().tagName;for (var i in A.Items){var C=A.Items[i];if ((B&&C.Style.Element==B)||(!B&&!C.Style.IsObjectElement)) C.style.display='';else C.style.display='none';}}\r
+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;B._FCKToolbarPanelButton=this;var C=B.Document.body.appendChild(B.Document.createElement('div'));C.style.position='absolute';C.style.top='0px';var D=this.LineImg=C.appendChild(B.Document.createElement('IMG'));D.className='TB_ConnectionLine';D.src=FCK_SPACER_PATH;B.OnHide=FCKToolbarPanelButton_OnPanelHide;};function FCKToolbarPanelButton_OnButtonClick(A){var B=this._FCKToolbarPanelButton;var e=B._UIButton.MainElement;B._UIButton.ChangeState(1);B.LineImg.style.width=(e.offsetWidth-2)+'px';FCK.ToolbarSet.CurrentInstance.Commands.GetCommand(B.CommandName).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;\r
+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 'SpellCheck':B=new FCKToolbarButton('SpellCheck',FCKLang.SpellCheck,null,null,null,null,13);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 '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('InsertHorizontalRule',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;default:alert(FCKLang.UnknownToolbarItem.replace(/%1/g,A));return null;};FCKToolbarItems.LoadedItems[A]=B;return B;}\r
+var FCKToolbar=function(){this.Items=[];if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKToolbar_Cleanup);};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){if (this.MainElement){if (this.MainElement.parentNode) this.MainElement.parentNode.removeChild(this.MainElement);this.MainElement=null;};var B=FCKTools.GetElementDocument(A);var e=this.MainElement=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;this.RowElement=e.insertRow(-1);var C;if (!this.HideStart){C=this.RowElement.insertCell(-1);C.appendChild(B.createElement('div')).className='TB_Start';};for (var i=0;i<this.Items.length;i++){this.Items[i].Create(this.RowElement.insertCell(-1));};if (!this.HideEnd){C=this.RowElement.insertCell(-1);C.appendChild(B.createElement('div')).className='TB_End';};A.appendChild(e);};function FCKToolbar_Cleanup(){this.MainElement=null;this.RowElement=null;};var FCKToolbarSeparator=function(){};FCKToolbarSeparator.prototype.Create=function(A){FCKTools.AppendElement(A,'div').className='TB_Separator';}\r
+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);}\r
+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;default:FCK.Events.AttachEvent('OnBlur',FCK_OnBlur);FCK.Events.AttachEvent('OnFocus',FCK_OnFocus);var D;var E=C.match(/^Out:(.+)\((\w+)\)$/);if (E){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 this._Init('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;G.open();G.write('<html><head><script type="text/javascript"> window.onload = window.onresize = function() { window.frameElement.height = document.body.scrollHeight ; } </script></head><body style="overflow: hidden">'+document.getElementById('xToolbarSpace').innerHTML+'</body></html>');G.close();G.oncontextmenu=FCKTools.CancelEvent;FCKTools.AppendStyleSheet(G,FCKConfig.SkinPath+'fck_editor.css');B=D.__FCKToolbarSet=new FCKToolbarSet(G);B._IFrame=F;if (FCK.IECleanup) FCK.IECleanup.AddItem(D,FCKToolbarSet_Target_Cleanup);};B.CurrentInstance=FCK;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;B.onclick=FCKToolbarSet_Expand_OnClick;C.title=FCKLang.ToolbarCollapse;C.onclick=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 (FCKBrowserInfo.IsGecko){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();};\r
+var FCKDialog={};FCKDialog.OpenDialog=function(A,B,C,D,E,F,G,H){var I={};I.Title=B;I.Page=C;I.Editor=window;I.CustomValue=F;var J=FCKConfig.BasePath+'fckdialog.html';this.Show(I,A,J,D,E,G,H);};\r
+FCKDialog.Show=function(A,B,C,D,E,F,G){if (!F) F=window;var H='help:no;scroll:no;status:no;resizable:'+(G?'yes':'no')+';dialogWidth:'+D+'px;dialogHeight:'+E+'px';FCKFocusManager.Lock();var I='B';try{I=F.showModalDialog(C,A,H);}catch(e) {};if ('B'===I) alert(FCKLang.DialogBlocked);FCKFocusManager.Unlock();};\r
+var FCKMenuItem=function(A,B,C,D,E){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);if (FCK.IECleanup) FCK.IECleanup.AddItem(this,FCKMenuItem_Cleanup);};FCKMenuItem.prototype.AddItem=function(A,B,C,D){this.HasSubMenu=true;return this.SubMenu.AddItem(A,B,C,D);};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;}\r
+var FCKMenuBlock=function(){this._Items=[];};FCKMenuBlock.prototype.Count=function(){return this._Items.length;};FCKMenuBlock.prototype.AddItem=function(A,B,C,D){var E=new FCKMenuItem(this,A,B,C,D);E.OnClick=FCKTools.CreateEventListener(FCKMenuBlock_Item_OnClick,this);E.OnActivate=FCKTools.CreateEventListener(FCKMenuBlock_Item_OnActivate,this);this._Items.push(E);return E;};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){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();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';}\r
+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.SkinPath+'fck_editor.css');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();}\r
+var FCKContextMenu=function(A,B){this.CtrlDisable=false;var C=this._Panel=new FCKPanel(A);C.AppendStyleSheet(FCKConfig.SkinPath+'fck_editor.css');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;this._Document.addEventListener('contextmenu',FCKContextMenu_Document_OnContextMenu,false);}};FCKContextMenu.prototype.AddItem=function(A,B,C,D){var E=this._MenuBlock.AddItem(A,B,C,D);this._Redraw=true;return E;};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){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);};A=A.parentNode;}};function FCKContextMenu_AttachedElement_OnContextMenu(A,B,C){if (B.CtrlDisable&&(A.ctrlKey||A.metaKey)) 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);B._Panel.Show(A.pageX||A.screenX,A.pageY||A.screenY,A.currentTarget||null);return false;};function FCKContextMenu_MenuBlock_OnClick(A,B){B._Panel.Hide();FCKTools.RunFunction(B.OnItemClick,B,A);}\r
+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('TableInsertCell',FCKLang.InsertCell,58);D.AddItem('TableDeleteCells',FCKLang.DeleteCells,59);D.AddItem('TableMergeCells',FCKLang.MergeCells,60);D.AddItem('TableSplitCell',FCKLang.SplitCell,61);D.AddSeparator();D.AddItem('TableCellProp',FCKLang.CellProperties,57);menu.AddSeparator();D=menu.AddItem('Row',FCKLang.RowCM);D.AddItem('TableInsertRow',FCKLang.InsertRow,62);D.AddItem('TableDeleteRows',FCKLang.DeleteRows,63);menu.AddSeparator();D=menu.AddItem('Column',FCKLang.ColumnCM);D.AddItem('TableInsertColumn',FCKLang.InsertColumn,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();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);}}};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);}}};};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){FCK.Focus();FCKCommands.GetCommand(A.Name).Execute();};\r
+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');}\r
+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;}\r
diff --git a/httemplate/elements/fckeditor/editor/lang/_getfontformat.html b/httemplate/elements/fckeditor/editor/lang/_getfontformat.html
new file mode 100644 (file)
index 0000000..a408642
--- /dev/null
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+-->\r
+<html>\r
+       <head>\r
+               <title></title>\r
+       </head>\r
+       <script language="javascript">\r
+\r
+window.onload = function()\r
+{\r
+       var oRange = document.selection.createRange() ;\r
+\r
+       var sNormal ;\r
+       var sFormats = '' ;\r
+       for ( var i = 1 ; i <= 9 ; i++ )\r
+       {\r
+               oRange.moveToElementText( document.getElementById( 'x' + i ) ) ;\r
+               sFormats += oRange.queryCommandValue( 'FormatBlock' ) ;\r
+               if ( i == 1 )\r
+                       sNormal = sFormats ;\r
+               sFormats += ';' ;\r
+       }\r
+\r
+       document.getElementById('xFontFormats').innerHTML = sFormats + sNormal + ' (DIV)' ;\r
+}\r
+       </script>\r
+       <body>\r
+               <table width="70%" align="center">\r
+                       <tr>\r
+                               <td>\r
+                                       <h3>FontFormats Localization</h3>\r
+                                       <p>\r
+                                               IE has some limits when handling the "Font Format". It actually uses localized\r
+                                               strings to retrieve the current format value. This makes it very difficult to\r
+                                               make a system that works on every single computer in the world.\r
+                                       </p>\r
+                                       <p>\r
+                                               With FCKeditor, this problem impacts in the "Format" toolbar command that\r
+                                               doesn't reflects the format of the current cursor position.\r
+                                       </p>\r
+                                       <p>\r
+                                               There is only one way to make it work. We must localize FCKeditor using the\r
+                                               strings used by IE. In this way, we will have the expected behavior at least\r
+                                               when using FCKeditor in the same language as the browser. So, when localizing\r
+                                               FCKeditor, go to a computer with IE in the target language, open this page and\r
+                                               use the following string to the "FontFormats" value:\r
+                                       </p>\r
+                                       <div style="white-space: nowrap">\r
+                                               FontFormats : "<span id="xFontFormats" style="COLOR: #000099"></span>",\r
+                                       </div>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+               <div style="DISPLAY: none">\r
+                       <p id="x1">&nbsp;</p>\r
+                       <pre id="x2">&nbsp;</pre>\r
+                       <address id="x3">&nbsp;</address>\r
+                       <h1 id="x4">&nbsp;</h1>\r
+                       <h2 id="x5">&nbsp;</h2>\r
+                       <h3 id="x6">&nbsp;</h3>\r
+                       <h4 id="x7">&nbsp;</h4>\r
+                       <h5 id="x8">&nbsp;</h5>\r
+                       <h6 id="x9">&nbsp;</h6>\r
+               </div>\r
+       </body>\r
+</html>\r
diff --git a/httemplate/elements/fckeditor/editor/lang/_translationstatus.txt b/httemplate/elements/fckeditor/editor/lang/_translationstatus.txt
new file mode 100644 (file)
index 0000000..a53ea8f
--- /dev/null
@@ -0,0 +1,76 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Translations Status.\r
+ */\r
+\r
+af.js      Found: 401   Missing: 1\r
+ar.js      Found: 401   Missing: 1\r
+bg.js      Found: 378   Missing: 24\r
+bn.js      Found: 386   Missing: 16\r
+bs.js      Found: 230   Missing: 172\r
+ca.js      Found: 402   Missing: 0\r
+cs.js      Found: 400   Missing: 2\r
+da.js      Found: 386   Missing: 16\r
+de.js      Found: 401   Missing: 1\r
+el.js      Found: 401   Missing: 1\r
+en-au.js   Found: 402   Missing: 0\r
+en-ca.js   Found: 402   Missing: 0\r
+en-uk.js   Found: 402   Missing: 0\r
+eo.js      Found: 350   Missing: 52\r
+es.js      Found: 386   Missing: 16\r
+et.js      Found: 402   Missing: 0\r
+eu.js      Found: 386   Missing: 16\r
+fa.js      Found: 402   Missing: 0\r
+fi.js      Found: 402   Missing: 0\r
+fo.js      Found: 401   Missing: 1\r
+fr.js      Found: 401   Missing: 1\r
+gl.js      Found: 386   Missing: 16\r
+he.js      Found: 402   Missing: 0\r
+hi.js      Found: 401   Missing: 1\r
+hr.js      Found: 401   Missing: 1\r
+hu.js      Found: 401   Missing: 1\r
+it.js      Found: 401   Missing: 1\r
+ja.js      Found: 401   Missing: 1\r
+km.js      Found: 376   Missing: 26\r
+ko.js      Found: 373   Missing: 29\r
+lt.js      Found: 381   Missing: 21\r
+lv.js      Found: 386   Missing: 16\r
+mn.js      Found: 230   Missing: 172\r
+ms.js      Found: 356   Missing: 46\r
+nb.js      Found: 400   Missing: 2\r
+nl.js      Found: 401   Missing: 1\r
+no.js      Found: 400   Missing: 2\r
+pl.js      Found: 386   Missing: 16\r
+pt-br.js   Found: 401   Missing: 1\r
+pt.js      Found: 386   Missing: 16\r
+ro.js      Found: 400   Missing: 2\r
+ru.js      Found: 401   Missing: 1\r
+sk.js      Found: 401   Missing: 1\r
+sl.js      Found: 378   Missing: 24\r
+sr-latn.js Found: 373   Missing: 29\r
+sr.js      Found: 373   Missing: 29\r
+sv.js      Found: 401   Missing: 1\r
+th.js      Found: 398   Missing: 4\r
+tr.js      Found: 401   Missing: 1\r
+uk.js      Found: 402   Missing: 0\r
+vi.js      Found: 401   Missing: 1\r
+zh-cn.js   Found: 401   Missing: 1\r
+zh.js      Found: 401   Missing: 1\r
diff --git a/httemplate/elements/fckeditor/editor/lang/af.js b/httemplate/elements/fckeditor/editor/lang/af.js
new file mode 100644 (file)
index 0000000..857dc3e
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Afrikaans language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Vou Gereedskaps balk toe",\r
+ToolbarExpand          : "Vou Gereedskaps balk oop",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Bewaar",\r
+NewPage                                : "Nuwe Bladsy",\r
+Preview                                : "Voorskou",\r
+Cut                                    : "Uitsny ",\r
+Copy                           : "Kopieer",\r
+Paste                          : "Byvoeg",\r
+PasteText                      : "Slegs inhoud byvoeg",\r
+PasteWord                      : "Van Word af byvoeg",\r
+Print                          : "Druk",\r
+SelectAll                      : "Selekteer alles",\r
+RemoveFormat           : "Formaat verweider",\r
+InsertLinkLbl          : "Skakel",\r
+InsertLink                     : "Skakel byvoeg/verander",\r
+RemoveLink                     : "Skakel verweider",\r
+Anchor                         : "Plekhouer byvoeg/verander",\r
+InsertImageLbl         : "Beeld",\r
+InsertImage                    : "Beeld byvoeg/verander",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Flash byvoeg/verander",\r
+InsertTableLbl         : "Tabel",\r
+InsertTable                    : "Tabel byvoeg/verander",\r
+InsertLineLbl          : "Lyn",\r
+InsertLine                     : "Horisontale lyn byvoeg",\r
+InsertSpecialCharLbl: "Spesiaale karakter",\r
+InsertSpecialChar      : "Spesiaale Karakter byvoeg",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Smiley byvoeg",\r
+About                          : "Meer oor FCKeditor",\r
+Bold                           : "Vet",\r
+Italic                         : "Skuins",\r
+Underline                      : "Onderstreep",\r
+StrikeThrough          : "Gestreik",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Links rig",\r
+CenterJustify          : "Rig Middel",\r
+RightJustify           : "Regs rig",\r
+BlockJustify           : "Blok paradeer",\r
+DecreaseIndent         : "Paradeering verkort",\r
+IncreaseIndent         : "Paradeering verleng",\r
+Undo                           : "Ont-skep",\r
+Redo                           : "Her-skep",\r
+NumberedListLbl                : "Genommerde lys",\r
+NumberedList           : "Genommerde lys byvoeg/verweider",\r
+BulletedListLbl                : "Gepunkte lys",\r
+BulletedList           : "Gepunkte lys byvoeg/verweider",\r
+ShowTableBorders       : "Wys tabel kante",\r
+ShowDetails                    : "Wys informasie",\r
+Style                          : "Styl",\r
+FontFormat                     : "Karakter formaat",\r
+Font                           : "Karakters",\r
+FontSize                       : "Karakter grote",\r
+TextColor                      : "Karakter kleur",\r
+BGColor                                : "Agtergrond kleur",\r
+Source                         : "Source",\r
+Find                           : "Vind",\r
+Replace                                : "Vervang",\r
+SpellCheck                     : "Spelling nagaan",\r
+UniversalKeyboard      : "Universeele Sleutelbord",\r
+PageBreakLbl           : "Bladsy breek",\r
+PageBreak                      : "Bladsy breek byvoeg",\r
+\r
+Form                   : "Form",\r
+Checkbox               : "HakBox",\r
+RadioButton            : "PuntBox",\r
+TextField              : "Byvoegbare karakter strook",\r
+Textarea               : "Byvoegbare karakter area",\r
+HiddenField            : "Blinde strook",\r
+Button                 : "Knop",\r
+SelectionField : "Opklapbare keuse strook",\r
+ImageButton            : "Beeld knop",\r
+\r
+FitWindow              : "Maksimaliseer venster grote",\r
+\r
+// Context Menu\r
+EditLink                       : "Verander skakel",\r
+CellCM                         : "Cell",\r
+RowCM                          : "Ry",\r
+ColumnCM                       : "Kolom",\r
+InsertRow                      : "Ry byvoeg",\r
+DeleteRows                     : "Ry verweider",\r
+InsertColumn           : "Kolom byvoeg",\r
+DeleteColumns          : "Kolom verweider",\r
+InsertCell                     : "Cell byvoeg",\r
+DeleteCells                    : "Cell verweider",\r
+MergeCells                     : "Cell verenig",\r
+SplitCell                      : "Cell verdeel",\r
+TableDelete                    : "Tabel verweider",\r
+CellProperties         : "Cell eienskappe",\r
+TableProperties                : "Tabel eienskappe",\r
+ImageProperties                : "Beeld eienskappe",\r
+FlashProperties                : "Flash eienskappe",\r
+\r
+AnchorProp                     : "Plekhouer eienskappe",\r
+ButtonProp                     : "Knop eienskappe",\r
+CheckboxProp           : "HakBox eienskappe",\r
+HiddenFieldProp                : "Blinde strook eienskappe",\r
+RadioButtonProp                : "PuntBox eienskappe",\r
+ImageButtonProp                : "Beeld knop eienskappe",\r
+TextFieldProp          : "Karakter strook eienskappe",\r
+SelectionFieldProp     : "Opklapbare keuse strook eienskappe",\r
+TextareaProp           : "Karakter area eienskappe",\r
+FormProp                       : "Form eienskappe",\r
+\r
+FontFormats                    : "Normaal;Geformateerd;Adres;Opskrif 1;Opskrif 2;Opskrif 3;Opskrif 4;Opskrif 5;Opskrif 6;Normaal (DIV)",               //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML word verarbeit. U geduld asseblief...",\r
+Done                           : "Kompleet",\r
+PasteWordConfirm       : "Die informasie wat U probeer byvoeg is warskynlik van Word. Wil U dit reinig voor die byvoeging?",\r
+NotCompatiblePaste     : "Die instruksie is beskikbaar vir Internet Explorer weergawe 5.5 of hor. Wil U dir byvoeg sonder reiniging?",\r
+UnknownToolbarItem     : "Unbekende gereedskaps balk item \"%1\"",\r
+UnknownCommand         : "Unbekende instruksie naam \"%1\"",\r
+NotImplemented         : "Instruksie is nie geimplementeer nie.",\r
+UnknownToolbarSet      : "Gereedskaps balk \"%1\" bestaan nie",\r
+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.",\r
+BrowseServerBlocked : "Die vorraad venster word geblok! Verseker asseblief dat U die \"popup blocker\" instelling verander.",\r
+DialogBlocked          : "Die dialoog venster vir verdere informasie word geblok. De-aktiveer asseblief die \"popup blocker\" instellings wat dit behinder.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Kanseleer",\r
+DlgBtnClose                    : "Sluit",\r
+DlgBtnBrowseServer     : "Server deurblaai",\r
+DlgAdvancedTag         : "Ingewikkeld",\r
+DlgOpOther                     : "<Ander>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Voeg asseblief die URL in",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<geen instelling>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Taal rigting",\r
+DlgGenLangDirLtr       : "Links na regs (LTR)",\r
+DlgGenLangDirRtl       : "Regs na links (RTL)",\r
+DlgGenLangCode         : "Taal kode",\r
+DlgGenAccessKey                : "Toegang sleutel",\r
+DlgGenName                     : "Naam",\r
+DlgGenTabIndex         : "Tab Index",\r
+DlgGenLongDescr                : "Lang beskreiwing URL",\r
+DlgGenClass                    : "Skakel Tiepe",\r
+DlgGenTitle                    : "Voorbeveelings Titel",\r
+DlgGenContType         : "Voorbeveelings inhoud soort",\r
+DlgGenLinkCharset      : "Geskakelde voorbeeld karakterstel",\r
+DlgGenStyle                    : "Styl",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Beeld eienskappe",\r
+DlgImgInfoTab          : "Beeld informasie",\r
+DlgImgBtnUpload                : "Stuur dit na die Server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Uplaai",\r
+DlgImgAlt                      : "Alternatiewe beskrywing",\r
+DlgImgWidth                    : "Weidte",\r
+DlgImgHeight           : "Hoogde",\r
+DlgImgLockRatio                : "Behou preporsie",\r
+DlgBtnResetSize                : "Herstel groote",\r
+DlgImgBorder           : "Kant",\r
+DlgImgHSpace           : "HSpasie",\r
+DlgImgVSpace           : "VSpasie",\r
+DlgImgAlign                    : "Paradeer",\r
+DlgImgAlignLeft                : "Links",\r
+DlgImgAlignAbsBottom: "Abs Onder",\r
+DlgImgAlignAbsMiddle: "Abs Middel",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Onder",\r
+DlgImgAlignMiddle      : "Middel",\r
+DlgImgAlignRight       : "Regs",\r
+DlgImgAlignTextTop     : "Text Bo",\r
+DlgImgAlignTop         : "Bo",\r
+DlgImgPreview          : "Voorskou",\r
+DlgImgAlertUrl         : "Voeg asseblief Beeld URL in.",\r
+DlgImgLinkTab          : "Skakel",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash eienskappe",\r
+DlgFlashChkPlay                : "Automaties Speel",\r
+DlgFlashChkLoop                : "Herhaling",\r
+DlgFlashChkMenu                : "Laat Flash Menu toe",\r
+DlgFlashScale          : "Scale",\r
+DlgFlashScaleAll       : "Wys alles",\r
+DlgFlashScaleNoBorder  : "Geen kante",\r
+DlgFlashScaleFit       : "Presiese pas",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Skakel",\r
+DlgLnkInfoTab          : "Skakel informasie",\r
+DlgLnkTargetTab                : "Mikpunt",\r
+\r
+DlgLnkType                     : "Skakel soort",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Skakel na plekhouers in text",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<ander>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Kies 'n plekhouer",\r
+DlgLnkAnchorByName     : "Volgens plekhouer naam",\r
+DlgLnkAnchorById       : "Volgens element Id",\r
+DlgLnkNoAnchors                : "<Geen plekhouers beskikbaar in dokument>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail Adres",\r
+DlgLnkEMailSubject     : "Boodskap Opskrif",\r
+DlgLnkEMailBody                : "Boodskap Inhoud",\r
+DlgLnkUpload           : "Oplaai",\r
+DlgLnkBtnUpload                : "Stuur na Server",\r
+\r
+DlgLnkTarget           : "Mikpunt",\r
+DlgLnkTargetFrame      : "<raam>",\r
+DlgLnkTargetPopup      : "<popup venster>",\r
+DlgLnkTargetBlank      : "Nuwe Venster (_blank)",\r
+DlgLnkTargetParent     : "Vorige Venster (_parent)",\r
+DlgLnkTargetSelf       : "Selfde Venster (_self)",\r
+DlgLnkTargetTop                : "Boonste Venster (_top)",\r
+DlgLnkTargetFrameName  : "Mikpunt Venster Naam",\r
+DlgLnkPopWinName       : "Popup Venster Naam",\r
+DlgLnkPopWinFeat       : "Popup Venster Geaartheid",\r
+DlgLnkPopResize                : "Verstelbare Groote",\r
+DlgLnkPopLocation      : "Adres Balk",\r
+DlgLnkPopMenu          : "Menu Balk",\r
+DlgLnkPopScroll                : "Gleibalkstuk",\r
+DlgLnkPopStatus                : "Status Balk",\r
+DlgLnkPopToolbar       : "Gereedskap Balk",\r
+DlgLnkPopFullScrn      : "Voll Skerm (IE)",\r
+DlgLnkPopDependent     : "Afhanklik (Netscape)",\r
+DlgLnkPopWidth         : "Weite",\r
+DlgLnkPopHeight                : "Hoogde",\r
+DlgLnkPopLeft          : "Links Posisie",\r
+DlgLnkPopTop           : "Bo Posisie",\r
+\r
+DlnLnkMsgNoUrl         : "Voeg asseblief die URL in",\r
+DlnLnkMsgNoEMail       : "Voeg asseblief die e-mail adres in",\r
+DlnLnkMsgNoAnchor      : "Kies asseblief 'n plekhouer",\r
+DlnLnkMsgInvPopName    : "Die popup naam moet begin met alphabetiese karakters sonder spasies.",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Kies Kleur",\r
+DlgColorBtnClear       : "Maak skoon",\r
+DlgColorHighlight      : "Highlight",\r
+DlgColorSelected       : "Geselekteer",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Voeg Smiley by",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Kies spesiale karakter",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Tabel eienskappe",\r
+DlgTableRows           : "Reie",\r
+DlgTableColumns                : "Kolome",\r
+DlgTableBorder         : "Kant groote",\r
+DlgTableAlign          : "Parideering",\r
+DlgTableAlignNotSet    : "<geen instelling>",\r
+DlgTableAlignLeft      : "Links",\r
+DlgTableAlignCenter    : "Middel",\r
+DlgTableAlignRight     : "Regs",\r
+DlgTableWidth          : "Weite",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "percent",\r
+DlgTableHeight         : "Hoogde",\r
+DlgTableCellSpace      : "Cell spasieering",\r
+DlgTableCellPad                : "Cell buffer",\r
+DlgTableCaption                : "Beskreiwing",\r
+DlgTableSummary                : "Opsomming",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Cell eienskappe",\r
+DlgCellWidth           : "Weite",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "percent",\r
+DlgCellHeight          : "Hoogde",\r
+DlgCellWordWrap                : "Woord Wrap",\r
+DlgCellWordWrapNotSet  : "<geen instelling>",\r
+DlgCellWordWrapYes     : "Ja",\r
+DlgCellWordWrapNo      : "Nee",\r
+DlgCellHorAlign                : "Horisontale rigting",\r
+DlgCellHorAlignNotSet  : "<geen instelling>",\r
+DlgCellHorAlignLeft    : "Links",\r
+DlgCellHorAlignCenter  : "Middel",\r
+DlgCellHorAlignRight: "Regs",\r
+DlgCellVerAlign                : "Vertikale rigting",\r
+DlgCellVerAlignNotSet  : "<geen instelling>",\r
+DlgCellVerAlignTop     : "Bo",\r
+DlgCellVerAlignMiddle  : "Middel",\r
+DlgCellVerAlignBottom  : "Onder",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Rei strekking",\r
+DlgCellCollSpan                : "Kolom strekking",\r
+DlgCellBackColor       : "Agtergrond Kleur",\r
+DlgCellBorderColor     : "Kant Kleur",\r
+DlgCellBtnSelect       : "Keuse...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Vind",\r
+DlgFindFindBtn         : "Vind",\r
+DlgFindNotFoundMsg     : "Die gespesifiseerde karakters word nie gevind nie.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Vervang",\r
+DlgReplaceFindLbl              : "Soek wat:",\r
+DlgReplaceReplaceLbl   : "Vervang met:",\r
+DlgReplaceCaseChk              : "Vergelyk karakter skryfweise",\r
+DlgReplaceReplaceBtn   : "Vervang",\r
+DlgReplaceReplAllBtn   : "Vervang alles",\r
+DlgReplaceWordChk              : "Vergelyk komplete woord",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "U browser se sekuriteit instelling behinder die uitsny aksie. Gebruik asseblief die sleutel kombenasie(Ctrl+X).",\r
+PasteErrorCopy : "U browser se sekuriteit instelling behinder die kopieerings aksie. Gebruik asseblief die sleutel kombenasie(Ctrl+C).",\r
+\r
+PasteAsText            : "Voeg slegs karakters by",\r
+PasteFromWord  : "Byvoeging uit Word",\r
+\r
+DlgPasteMsg2   : "Voeg asseblief die inhoud in die gegewe box by met sleutel kombenasie(<STRONG>Ctrl+V</STRONG>) en druk <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignoreer karakter soort defenisies",\r
+DlgPasteRemoveStyles   : "Verweider Styl defenisies",\r
+DlgPasteCleanBox               : "Maak Box Skoon",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automaties",\r
+ColorMoreColors        : "Meer Kleure...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokument Eienskappe",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Plekhouer Eienskappe",\r
+DlgAnchorName          : "Plekhouer Naam",\r
+DlgAnchorErrorName     : "Voltooi die plekhouer naam asseblief",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Nie in woordeboek nie",\r
+DlgSpellChangeTo               : "Verander na",\r
+DlgSpellBtnIgnore              : "Ignoreer",\r
+DlgSpellBtnIgnoreAll   : "Ignoreer na-volgende",\r
+DlgSpellBtnReplace             : "Vervang",\r
+DlgSpellBtnReplaceAll  : "vervang na-volgende",\r
+DlgSpellBtnUndo                        : "Ont-skep",\r
+DlgSpellNoSuggestions  : "- Geen voorstel -",\r
+DlgSpellProgress               : "Spelling word beproef...",\r
+DlgSpellNoMispell              : "Spellproef kompleet: Geen foute",\r
+DlgSpellNoChanges              : "Spellproef kompleet: Geen woord veranderings",\r
+DlgSpellOneChange              : "Spellproef kompleet: Een woord verander",\r
+DlgSpellManyChanges            : "Spellproef kompleet: %1 woorde verander",\r
+\r
+IeSpellDownload                        : "Geen Spellproefer geinstaleer nie. Wil U dit aflaai?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Karakters (Waarde)",\r
+DlgButtonType          : "Soort",\r
+DlgButtonTypeBtn       : "Knop",\r
+DlgButtonTypeSbm       : "Indien",\r
+DlgButtonTypeRst       : "Reset",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Naam",\r
+DlgCheckboxValue       : "Waarde",\r
+DlgCheckboxSelected    : "Uitgekies",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Naam",\r
+DlgFormAction  : "Aksie",\r
+DlgFormMethod  : "Metode",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Naam",\r
+DlgSelectValue         : "Waarde",\r
+DlgSelectSize          : "Grote",\r
+DlgSelectLines         : "lyne",\r
+DlgSelectChkMulti      : "Laat meerere keuses toe",\r
+DlgSelectOpAvail       : "Beskikbare Opsies",\r
+DlgSelectOpText                : "Karakters",\r
+DlgSelectOpValue       : "Waarde",\r
+DlgSelectBtnAdd                : "Byvoeg",\r
+DlgSelectBtnModify     : "Verander",\r
+DlgSelectBtnUp         : "Op",\r
+DlgSelectBtnDown       : "Af",\r
+DlgSelectBtnSetValue : "Stel as uitgekiesde waarde",\r
+DlgSelectBtnDelete     : "Verweider",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Naam",\r
+DlgTextareaCols        : "Kolom",\r
+DlgTextareaRows        : "Reie",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Naam",\r
+DlgTextValue           : "Waarde",\r
+DlgTextCharWidth       : "Karakter weite",\r
+DlgTextMaxChars                : "Maximale karakters",\r
+DlgTextType                    : "Soort",\r
+DlgTextTypeText                : "Karakters",\r
+DlgTextTypePass                : "Wagwoord",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Naam",\r
+DlgHiddenValue : "Waarde",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Gepunkte lys eienskappe",\r
+NumberedListProp       : "Genommerde lys eienskappe",\r
+DlgLstStart                    : "Begin",\r
+DlgLstType                     : "Soort",\r
+DlgLstTypeCircle       : "Sirkel",\r
+DlgLstTypeDisc         : "Skyf",\r
+DlgLstTypeSquare       : "Vierkant",\r
+DlgLstTypeNumbers      : "Nommer (1, 2, 3)",\r
+DlgLstTypeLCase                : "Klein Letters (a, b, c)",\r
+DlgLstTypeUCase                : "Hoof Letters (A, B, C)",\r
+DlgLstTypeSRoman       : "Klein Romeinse nommers (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Groot Romeinse nommers (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Algemeen",\r
+DlgDocBackTab          : "Agtergrond",\r
+DlgDocColorsTab                : "Kleure en Rante",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Bladsy Opskrif",\r
+DlgDocLangDir          : "Taal rigting",\r
+DlgDocLangDirLTR       : "Link na Regs (LTR)",\r
+DlgDocLangDirRTL       : "Regs na Links (RTL)",\r
+DlgDocLangCode         : "Taal Kode",\r
+DlgDocCharSet          : "Karakterstel Kodeering",\r
+DlgDocCharSetCE                : "Sentraal Europa",\r
+DlgDocCharSetCT                : "Chinees Traditioneel (Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Grieks",\r
+DlgDocCharSetJP                : "Japanees",\r
+DlgDocCharSetKR                : "Koreans",\r
+DlgDocCharSetTR                : "Turks",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Western European",\r
+DlgDocCharSetOther     : "Ander Karakterstel Kodeering",\r
+\r
+DlgDocDocType          : "Dokument Opskrif Soort",\r
+DlgDocDocTypeOther     : "Ander Dokument Opskrif Soort",\r
+DlgDocIncXHTML         : "Voeg XHTML verklaring by",\r
+DlgDocBgColor          : "Agtergrond kleur",\r
+DlgDocBgImage          : "Agtergrond Beeld URL",\r
+DlgDocBgNoScroll       : "Vasgeklemde Agtergrond",\r
+DlgDocCText                    : "Karakters",\r
+DlgDocCLink                    : "Skakel",\r
+DlgDocCVisited         : "Besoekte Skakel",\r
+DlgDocCActive          : "Aktiewe Skakel",\r
+DlgDocMargins          : "Bladsy Rante",\r
+DlgDocMaTop                    : "Bo",\r
+DlgDocMaLeft           : "Links",\r
+DlgDocMaRight          : "Regs",\r
+DlgDocMaBottom         : "Onder",\r
+DlgDocMeIndex          : "Dokument Index Sleutelwoorde(comma verdeelt)",\r
+DlgDocMeDescr          : "Dokument Beskrywing",\r
+DlgDocMeAuthor         : "Skrywer",\r
+DlgDocMeCopy           : "Kopiereg",\r
+DlgDocPreview          : "Voorskou",\r
+\r
+// Templates Dialog\r
+Templates                      : "Templates",\r
+DlgTemplatesTitle      : "Inhoud Templates",\r
+DlgTemplatesSelMsg     : "Kies die template om te gebruik in die editor<br>(Inhoud word vervang!):",\r
+DlgTemplatesLoading    : "Templates word gelaai. U geduld asseblief...",\r
+DlgTemplatesNoTpl      : "(Geen templates gedefinieerd)",\r
+DlgTemplatesReplace    : "Vervang bestaande inhoud",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Meer oor",\r
+DlgAboutBrowserInfoTab : "Blaai Informasie deur",\r
+DlgAboutLicenseTab     : "Lesensie",\r
+DlgAboutVersion                : "weergawe",\r
+DlgAboutInfo           : "Vir meer informasie gaan na "\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/ar.js b/httemplate/elements/fckeditor/editor/lang/ar.js
new file mode 100644 (file)
index 0000000..91d34f1
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Arabic language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "rtl",\r
+\r
+ToolbarCollapse                : "ضم شريط الأدوات",\r
+ToolbarExpand          : "تمدد شريط الأدوات",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "حفظ",\r
+NewPage                                : "صفحة جديدة",\r
+Preview                                : "معاينة الصفحة",\r
+Cut                                    : "قص",\r
+Copy                           : "نسخ",\r
+Paste                          : "لصق",\r
+PasteText                      : "لصق كنص بسيط",\r
+PasteWord                      : "لصق من وورد",\r
+Print                          : "طباعة",\r
+SelectAll                      : "تحديد الكل",\r
+RemoveFormat           : "إزالة التنسيقات",\r
+InsertLinkLbl          : "رابط",\r
+InsertLink                     : "إدراج/تحرير رابط",\r
+RemoveLink                     : "إزالة رابط",\r
+Anchor                         : "إدراج/تحرير إشارة مرجعية",\r
+InsertImageLbl         : "صورة",\r
+InsertImage                    : "إدراج/تحرير صورة",\r
+InsertFlashLbl         : "فلاش",\r
+InsertFlash                    : "إدراج/تحرير فيلم فلاش",\r
+InsertTableLbl         : "جدول",\r
+InsertTable                    : "إدراج/تحرير جدول",\r
+InsertLineLbl          : "خط فاصل",\r
+InsertLine                     : "إدراج خط فاصل",\r
+InsertSpecialCharLbl: "رموز",\r
+InsertSpecialChar      : "إدراج  رموز..ِ",\r
+InsertSmileyLbl                : "ابتسامات",\r
+InsertSmiley           : "إدراج ابتسامات",\r
+About                          : "حول FCKeditor",\r
+Bold                           : "غامق",\r
+Italic                         : "مائل",\r
+Underline                      : "تسطير",\r
+StrikeThrough          : "يتوسطه خط",\r
+Subscript                      : "منخفض",\r
+Superscript                    : "مرتفع",\r
+LeftJustify                    : "محاذاة إلى اليسار",\r
+CenterJustify          : "توسيط",\r
+RightJustify           : "محاذاة إلى اليمين",\r
+BlockJustify           : "ضبط",\r
+DecreaseIndent         : "إنقاص المسافة البادئة",\r
+IncreaseIndent         : "زيادة المسافة البادئة",\r
+Undo                           : "تراجع",\r
+Redo                           : "إعادة",\r
+NumberedListLbl                : "تعداد رقمي",\r
+NumberedList           : "إدراج/إلغاء تعداد رقمي",\r
+BulletedListLbl                : "تعداد نقطي",\r
+BulletedList           : "إدراج/إلغاء تعداد نقطي",\r
+ShowTableBorders       : "معاينة حدود الجداول",\r
+ShowDetails                    : "معاينة التفاصيل",\r
+Style                          : "نمط",\r
+FontFormat                     : "تنسيق",\r
+Font                           : "خط",\r
+FontSize                       : "حجم الخط",\r
+TextColor                      : "لون النص",\r
+BGColor                                : "لون الخلفية",\r
+Source                         : "شفرة المصدر",\r
+Find                           : "بحث",\r
+Replace                                : "إستبدال",\r
+SpellCheck                     : "تدقيق إملائي",\r
+UniversalKeyboard      : "لوحة المفاتيح العالمية",\r
+PageBreakLbl           : "فصل الصفحة",\r
+PageBreak                      : "إدخال صفحة جديدة",\r
+\r
+Form                   : "نموذج",\r
+Checkbox               : "خانة إختيار",\r
+RadioButton            : "زر خيار",\r
+TextField              : "مربع نص",\r
+Textarea               : "ناحية نص",\r
+HiddenField            : "إدراج حقل خفي",\r
+Button                 : "زر ضغط",\r
+SelectionField : "قائمة منسدلة",\r
+ImageButton            : "زر صورة",\r
+\r
+FitWindow              : "تكبير حجم المحرر",\r
+\r
+// Context Menu\r
+EditLink                       : "تحرير رابط",\r
+CellCM                         : "خلية",\r
+RowCM                          : "صف",\r
+ColumnCM                       : "عمود",\r
+InsertRow                      : "إدراج صف",\r
+DeleteRows                     : "حذف صفوف",\r
+InsertColumn           : "إدراج عمود",\r
+DeleteColumns          : "حذف أعمدة",\r
+InsertCell                     : "إدراج خلية",\r
+DeleteCells                    : "حذف خلايا",\r
+MergeCells                     : "دمج خلايا",\r
+SplitCell                      : "تقسيم خلية",\r
+TableDelete                    : "حذف الجدول",\r
+CellProperties         : "خصائص الخلية",\r
+TableProperties                : "خصائص الجدول",\r
+ImageProperties                : "خصائص الصورة",\r
+FlashProperties                : "خصائص فيلم الفلاش",\r
+\r
+AnchorProp                     : "خصائص الإشارة المرجعية",\r
+ButtonProp                     : "خصائص زر الضغط",\r
+CheckboxProp           : "خصائص خانة الإختيار",\r
+HiddenFieldProp                : "خصائص الحقل الخفي",\r
+RadioButtonProp                : "خصائص زر الخيار",\r
+ImageButtonProp                : "خصائص زر الصورة",\r
+TextFieldProp          : "خصائص مربع النص",\r
+SelectionFieldProp     : "خصائص القائمة المنسدلة",\r
+TextareaProp           : "خصائص ناحية النص",\r
+FormProp                       : "خصائص النموذج",\r
+\r
+FontFormats                    : "عادي;منسّق;دوس;العنوان 1;العنوان  2;العنوان  3;العنوان  4;العنوان  5;العنوان  6",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "إنتظر قليلاً ريثما تتم   معالَجة‏ XHTML. لن يستغرق طويلاً...",\r
+Done                           : "تم",\r
+PasteWordConfirm       : "يبدو أن النص المراد لصقه منسوخ من برنامج وورد. هل تود تنظيفه قبل الشروع في عملية اللصق؟",\r
+NotCompatiblePaste     : "هذه الميزة تحتاج لمتصفح من النوعInternet Explorer إصدار 5.5 فما فوق. هل تود اللصق دون تنظيف الكود؟",\r
+UnknownToolbarItem     : "عنصر شريط أدوات غير معروف \"%1\"",\r
+UnknownCommand         : "أمر غير معروف \"%1\"",\r
+NotImplemented         : "لم يتم دعم هذا الأمر",\r
+UnknownToolbarSet      : "لم أتمكن من العثور على طقم الأدوات \"%1\" ",\r
+NoActiveX                      : "لتأمين متصفحك يجب أن تحدد بعض مميزات المحرر. يتوجب عليك تمكين الخيار \"Run ActiveX controls and plug-ins\". قد تواجة أخطاء وتلاحظ مميزات مفقودة",\r
+BrowseServerBlocked : "لايمكن فتح مصدر المتصفح. فضلا يجب التأكد بأن جميع موانع النوافذ المنبثقة معطلة",\r
+DialogBlocked          : "لايمكن فتح نافذة الحوار . فضلا تأكد من أن  مانع النوافذ المنبثة معطل .",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "موافق",\r
+DlgBtnCancel           : "إلغاء الأمر",\r
+DlgBtnClose                    : "إغلاق",\r
+DlgBtnBrowseServer     : "تصفح الخادم",\r
+DlgAdvancedTag         : "متقدم",\r
+DlgOpOther                     : "<أخرى>",\r
+DlgInfoTab                     : "معلومات",\r
+DlgAlertUrl                    : "الرجاء كتابة عنوان الإنترنت",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<بدون تحديد>",\r
+DlgGenId                       : "الرقم",\r
+DlgGenLangDir          : "إتجاه النص",\r
+DlgGenLangDirLtr       : "اليسار لليمين (LTR)",\r
+DlgGenLangDirRtl       : "اليمين لليسار (RTL)",\r
+DlgGenLangCode         : "رمز اللغة",\r
+DlgGenAccessKey                : "مفاتيح الإختصار",\r
+DlgGenName                     : "الاسم",\r
+DlgGenTabIndex         : "الترتيب",\r
+DlgGenLongDescr                : "عنوان الوصف المفصّل",\r
+DlgGenClass                    : "فئات التنسيق",\r
+DlgGenTitle                    : "تلميح الشاشة",\r
+DlgGenContType         : "نوع التلميح",\r
+DlgGenLinkCharset      : "ترميز المادة المطلوبة",\r
+DlgGenStyle                    : "نمط",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "خصائص الصورة",\r
+DlgImgInfoTab          : "معلومات الصورة",\r
+DlgImgBtnUpload                : "أرسلها للخادم",\r
+DlgImgURL                      : "موقع الصورة",\r
+DlgImgUpload           : "رفع",\r
+DlgImgAlt                      : "الوصف",\r
+DlgImgWidth                    : "العرض",\r
+DlgImgHeight           : "الإرتفاع",\r
+DlgImgLockRatio                : "تناسق الحجم",\r
+DlgBtnResetSize                : "إستعادة الحجم الأصلي",\r
+DlgImgBorder           : "سمك الحدود",\r
+DlgImgHSpace           : "تباعد أفقي",\r
+DlgImgVSpace           : "تباعد عمودي",\r
+DlgImgAlign                    : "محاذاة",\r
+DlgImgAlignLeft                : "يسار",\r
+DlgImgAlignAbsBottom: "أسفل النص",\r
+DlgImgAlignAbsMiddle: "وسط السطر",\r
+DlgImgAlignBaseline    : "على السطر",\r
+DlgImgAlignBottom      : "أسفل",\r
+DlgImgAlignMiddle      : "وسط",\r
+DlgImgAlignRight       : "يمين",\r
+DlgImgAlignTextTop     : "أعلى النص",\r
+DlgImgAlignTop         : "أعلى",\r
+DlgImgPreview          : "معاينة",\r
+DlgImgAlertUrl         : "فضلاً أكتب الموقع الذي توجد عليه هذه الصورة.",\r
+DlgImgLinkTab          : "الرابط",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "خصائص فيلم الفلاش",\r
+DlgFlashChkPlay                : "تشغيل تلقائي",\r
+DlgFlashChkLoop                : "تكرار",\r
+DlgFlashChkMenu                : "تمكين قائمة فيلم الفلاش",\r
+DlgFlashScale          : "الحجم",\r
+DlgFlashScaleAll       : "إظهار الكل",\r
+DlgFlashScaleNoBorder  : "بلا حدود",\r
+DlgFlashScaleFit       : "ضبط تام",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "إرتباط تشعبي",\r
+DlgLnkInfoTab          : "معلومات الرابط",\r
+DlgLnkTargetTab                : "الهدف",\r
+\r
+DlgLnkType                     : "نوع الربط",\r
+DlgLnkTypeURL          : "العنوان",\r
+DlgLnkTypeAnchor       : "مكان في هذا المستند",\r
+DlgLnkTypeEMail                : "بريد إلكتروني",\r
+DlgLnkProto                    : "البروتوكول",\r
+DlgLnkProtoOther       : "<أخرى>",\r
+DlgLnkURL                      : "الموقع",\r
+DlgLnkAnchorSel                : "اختر علامة مرجعية",\r
+DlgLnkAnchorByName     : "حسب اسم العلامة",\r
+DlgLnkAnchorById       : "حسب تعريف العنصر",\r
+DlgLnkNoAnchors                : "<لا يوجد علامات مرجعية في هذا المستند>",               //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "عنوان بريد إلكتروني",\r
+DlgLnkEMailSubject     : "موضوع الرسالة",\r
+DlgLnkEMailBody                : "محتوى الرسالة",\r
+DlgLnkUpload           : "رفع",\r
+DlgLnkBtnUpload                : "أرسلها للخادم",\r
+\r
+DlgLnkTarget           : "الهدف",\r
+DlgLnkTargetFrame      : "<إطار>",\r
+DlgLnkTargetPopup      : "<نافذة منبثقة>",\r
+DlgLnkTargetBlank      : "إطار جديد (_blank)",\r
+DlgLnkTargetParent     : "الإطار الأصل (_parent)",\r
+DlgLnkTargetSelf       : "نفس الإطار (_self)",\r
+DlgLnkTargetTop                : "صفحة كاملة (_top)",\r
+DlgLnkTargetFrameName  : "اسم الإطار الهدف",\r
+DlgLnkPopWinName       : "تسمية النافذة المنبثقة",\r
+DlgLnkPopWinFeat       : "خصائص النافذة المنبثقة",\r
+DlgLnkPopResize                : "قابلة للتحجيم",\r
+DlgLnkPopLocation      : "شريط العنوان",\r
+DlgLnkPopMenu          : "القوائم الرئيسية",\r
+DlgLnkPopScroll                : "أشرطة التمرير",\r
+DlgLnkPopStatus                : "شريط الحالة السفلي",\r
+DlgLnkPopToolbar       : "شريط الأدوات",\r
+DlgLnkPopFullScrn      : "ملئ الشاشة (IE)",\r
+DlgLnkPopDependent     : "تابع (Netscape)",\r
+DlgLnkPopWidth         : "العرض",\r
+DlgLnkPopHeight                : "الإرتفاع",\r
+DlgLnkPopLeft          : "التمركز لليسار",\r
+DlgLnkPopTop           : "التمركز للأعلى",\r
+\r
+DlnLnkMsgNoUrl         : "فضلاً أدخل عنوان الموقع الذي يشير إليه الرابط",\r
+DlnLnkMsgNoEMail       : "فضلاً أدخل عنوان البريد الإلكتروني",\r
+DlnLnkMsgNoAnchor      : "فضلاً حدد العلامة المرجعية المرغوبة",\r
+DlnLnkMsgInvPopName    : "اسم النافذة المنبثقة يجب أن يبدأ بحرف أبجدي دون مسافات",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "اختر لوناً",\r
+DlgColorBtnClear       : "مسح",\r
+DlgColorHighlight      : "تحديد",\r
+DlgColorSelected       : "إختيار",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "إدراج إبتسامات ",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "إدراج رمز",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "إدراج جدول",\r
+DlgTableRows           : "صفوف",\r
+DlgTableColumns                : "أعمدة",\r
+DlgTableBorder         : "سمك الحدود",\r
+DlgTableAlign          : "المحاذاة",\r
+DlgTableAlignNotSet    : "<بدون تحديد>",\r
+DlgTableAlignLeft      : "يسار",\r
+DlgTableAlignCenter    : "وسط",\r
+DlgTableAlignRight     : "يمين",\r
+DlgTableWidth          : "العرض",\r
+DlgTableWidthPx                : "بكسل",\r
+DlgTableWidthPc                : "بالمئة",\r
+DlgTableHeight         : "الإرتفاع",\r
+DlgTableCellSpace      : "تباعد الخلايا",\r
+DlgTableCellPad                : "المسافة البادئة",\r
+DlgTableCaption                : "الوصف",\r
+DlgTableSummary                : "الخلاصة",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "خصائص الخلية",\r
+DlgCellWidth           : "العرض",\r
+DlgCellWidthPx         : "بكسل",\r
+DlgCellWidthPc         : "بالمئة",\r
+DlgCellHeight          : "الإرتفاع",\r
+DlgCellWordWrap                : "التفاف النص",\r
+DlgCellWordWrapNotSet  : "<بدون تحديد>",\r
+DlgCellWordWrapYes     : "نعم",\r
+DlgCellWordWrapNo      : "لا",\r
+DlgCellHorAlign                : "المحاذاة الأفقية",\r
+DlgCellHorAlignNotSet  : "<بدون تحديد>",\r
+DlgCellHorAlignLeft    : "يسار",\r
+DlgCellHorAlignCenter  : "وسط",\r
+DlgCellHorAlignRight: "يمين",\r
+DlgCellVerAlign                : "المحاذاة العمودية",\r
+DlgCellVerAlignNotSet  : "<بدون تحديد>",\r
+DlgCellVerAlignTop     : "أعلى",\r
+DlgCellVerAlignMiddle  : "وسط",\r
+DlgCellVerAlignBottom  : "أسفل",\r
+DlgCellVerAlignBaseline        : "على السطر",\r
+DlgCellRowSpan         : "إمتداد الصفوف",\r
+DlgCellCollSpan                : "إمتداد الأعمدة",\r
+DlgCellBackColor       : "لون الخلفية",\r
+DlgCellBorderColor     : "لون الحدود",\r
+DlgCellBtnSelect       : "حدّد...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "بحث",\r
+DlgFindFindBtn         : "ابحث",\r
+DlgFindNotFoundMsg     : "لم يتم العثور على النص المحدد.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "إستبدال",\r
+DlgReplaceFindLbl              : "البحث عن:",\r
+DlgReplaceReplaceLbl   : "إستبدال بـ:",\r
+DlgReplaceCaseChk              : "مطابقة حالة الأحرف",\r
+DlgReplaceReplaceBtn   : "إستبدال",\r
+DlgReplaceReplAllBtn   : "إستبدال الكل",\r
+DlgReplaceWordChk              : "الكلمة بالكامل فقط",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "الإعدادات الأمنية للمتصفح الذي تستخدمه تمنع القص التلقائي. فضلاً إستخدم لوحة المفاتيح لفعل ذلك (Ctrl+X).",\r
+PasteErrorCopy : "الإعدادات الأمنية للمتصفح الذي تستخدمه تمنع النسخ التلقائي. فضلاً إستخدم لوحة المفاتيح لفعل ذلك (Ctrl+C).",\r
+\r
+PasteAsText            : "لصق كنص بسيط",\r
+PasteFromWord  : "لصق من وورد",\r
+\r
+DlgPasteMsg2   : "الصق داخل الصندوق بإستخدام زرّي (<STRONG>Ctrl+V</STRONG>) في لوحة المفاتيح، ثم اضغط زر  <STRONG>موافق</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "تجاهل تعريفات أسماء الخطوط",\r
+DlgPasteRemoveStyles   : "إزالة تعريفات الأنماط",\r
+DlgPasteCleanBox               : "نظّف محتوى الصندوق",\r
+\r
+// Color Picker\r
+ColorAutomatic : "تلقائي",\r
+ColorMoreColors        : "ألوان إضافية...",\r
+\r
+// Document Properties\r
+DocProps               : "خصائص الصفحة",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "خصائص إشارة مرجعية",\r
+DlgAnchorName          : "اسم الإشارة المرجعية",\r
+DlgAnchorErrorName     : "الرجاء كتابة اسم الإشارة المرجعية",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "ليست في القاموس",\r
+DlgSpellChangeTo               : "التغيير إلى",\r
+DlgSpellBtnIgnore              : "تجاهل",\r
+DlgSpellBtnIgnoreAll   : "تجاهل الكل",\r
+DlgSpellBtnReplace             : "تغيير",\r
+DlgSpellBtnReplaceAll  : "تغيير الكل",\r
+DlgSpellBtnUndo                        : "تراجع",\r
+DlgSpellNoSuggestions  : "- لا توجد إقتراحات -",\r
+DlgSpellProgress               : "جاري التدقيق إملائياً",\r
+DlgSpellNoMispell              : "تم إكمال التدقيق الإملائي: لم يتم العثور على أي أخطاء إملائية",\r
+DlgSpellNoChanges              : "تم إكمال التدقيق الإملائي: لم يتم تغيير أي كلمة",\r
+DlgSpellOneChange              : "تم إكمال التدقيق الإملائي: تم تغيير كلمة واحدة فقط",\r
+DlgSpellManyChanges            : "تم إكمال التدقيق الإملائي: تم تغيير %1 كلمات\كلمة",\r
+\r
+IeSpellDownload                        : "المدقق الإملائي (الإنجليزي) غير مثبّت. هل تود تحميله الآن؟",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "القيمة/التسمية",\r
+DlgButtonType          : "نوع الزر",\r
+DlgButtonTypeBtn       : "زر",\r
+DlgButtonTypeSbm       : "إرسال",\r
+DlgButtonTypeRst       : "إعادة تعيين",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "الاسم",\r
+DlgCheckboxValue       : "القيمة",\r
+DlgCheckboxSelected    : "محدد",\r
+\r
+// Form Dialog\r
+DlgFormName            : "الاسم",\r
+DlgFormAction  : "اسم الملف",\r
+DlgFormMethod  : "الأسلوب",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "الاسم",\r
+DlgSelectValue         : "القيمة",\r
+DlgSelectSize          : "الحجم",\r
+DlgSelectLines         : "الأسطر",\r
+DlgSelectChkMulti      : "السماح بتحديدات متعددة",\r
+DlgSelectOpAvail       : "الخيارات المتاحة",\r
+DlgSelectOpText                : "النص",\r
+DlgSelectOpValue       : "القيمة",\r
+DlgSelectBtnAdd                : "إضافة",\r
+DlgSelectBtnModify     : "تعديل",\r
+DlgSelectBtnUp         : "تحريك لأعلى",\r
+DlgSelectBtnDown       : "تحريك لأسفل",\r
+DlgSelectBtnSetValue : "إجعلها محددة",\r
+DlgSelectBtnDelete     : "إزالة",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "الاسم",\r
+DlgTextareaCols        : "الأعمدة",\r
+DlgTextareaRows        : "الصفوف",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "الاسم",\r
+DlgTextValue           : "القيمة",\r
+DlgTextCharWidth       : "العرض بالأحرف",\r
+DlgTextMaxChars                : "عدد الحروف الأقصى",\r
+DlgTextType                    : "نوع المحتوى",\r
+DlgTextTypeText                : "نص",\r
+DlgTextTypePass                : "كلمة مرور",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "الاسم",\r
+DlgHiddenValue : "القيمة",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "خصائص التعداد النقطي",\r
+NumberedListProp       : "خصائص التعداد الرقمي",\r
+DlgLstStart                    : "البدء عند",\r
+DlgLstType                     : "النوع",\r
+DlgLstTypeCircle       : "دائرة",\r
+DlgLstTypeDisc         : "قرص",\r
+DlgLstTypeSquare       : "مربع",\r
+DlgLstTypeNumbers      : "أرقام (1، 2، 3)َ",\r
+DlgLstTypeLCase                : "حروف صغيرة (a, b, c)َ",\r
+DlgLstTypeUCase                : "حروف كبيرة (A, B, C)َ",\r
+DlgLstTypeSRoman       : "ترقيم روماني صغير (i, ii, iii)َ",\r
+DlgLstTypeLRoman       : "ترقيم روماني كبير (I, II, III)َ",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "عام",\r
+DlgDocBackTab          : "الخلفية",\r
+DlgDocColorsTab                : "الألوان والهوامش",\r
+DlgDocMetaTab          : "المعرّفات الرأسية",\r
+\r
+DlgDocPageTitle                : "عنوان الصفحة",\r
+DlgDocLangDir          : "إتجاه اللغة",\r
+DlgDocLangDirLTR       : "اليسار لليمين (LTR)",\r
+DlgDocLangDirRTL       : "اليمين لليسار (RTL)",\r
+DlgDocLangCode         : "رمز اللغة",\r
+DlgDocCharSet          : "ترميز الحروف",\r
+DlgDocCharSetCE                : "أوروبا الوسطى",\r
+DlgDocCharSetCT                : "الصينية التقليدية (Big5)",\r
+DlgDocCharSetCR                : "السيريلية",\r
+DlgDocCharSetGR                : "اليونانية",\r
+DlgDocCharSetJP                : "اليابانية",\r
+DlgDocCharSetKR                : "الكورية",\r
+DlgDocCharSetTR                : "التركية",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "أوروبا الغربية",\r
+DlgDocCharSetOther     : "ترميز آخر",\r
+\r
+DlgDocDocType          : "ترويسة نوع  الصفحة",\r
+DlgDocDocTypeOther     : "ترويسة نوع  صفحة أخرى",\r
+DlgDocIncXHTML         : "تضمين   إعلانات‏ لغة XHTMLَ",\r
+DlgDocBgColor          : "لون الخلفية",\r
+DlgDocBgImage          : "رابط الصورة الخلفية",\r
+DlgDocBgNoScroll       : "جعلها علامة مائية",\r
+DlgDocCText                    : "النص",\r
+DlgDocCLink                    : "الروابط",\r
+DlgDocCVisited         : "المزارة",\r
+DlgDocCActive          : "النشطة",\r
+DlgDocMargins          : "هوامش الصفحة",\r
+DlgDocMaTop                    : "علوي",\r
+DlgDocMaLeft           : "أيسر",\r
+DlgDocMaRight          : "أيمن",\r
+DlgDocMaBottom         : "سفلي",\r
+DlgDocMeIndex          : "الكلمات الأساسية (مفصولة بفواصل)َ",\r
+DlgDocMeDescr          : "وصف الصفحة",\r
+DlgDocMeAuthor         : "الكاتب",\r
+DlgDocMeCopy           : "المالك",\r
+DlgDocPreview          : "معاينة",\r
+\r
+// Templates Dialog\r
+Templates                      : "القوالب",\r
+DlgTemplatesTitle      : "قوالب المحتوى",\r
+DlgTemplatesSelMsg     : "اختر القالب الذي تود وضعه في المحرر <br>(سيتم فقدان المحتوى الحالي):",\r
+DlgTemplatesLoading    : "جاري تحميل قائمة القوالب، الرجاء الإنتظار...",\r
+DlgTemplatesNoTpl      : "(لم يتم تعريف أي قالب)",\r
+DlgTemplatesReplace    : "استبدال المحتوى",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "نبذة",\r
+DlgAboutBrowserInfoTab : "معلومات متصفحك",\r
+DlgAboutLicenseTab     : "الترخيص",\r
+DlgAboutVersion                : "الإصدار",\r
+DlgAboutInfo           : "لمزيد من المعلومات تفضل بزيارة"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/bg.js b/httemplate/elements/fckeditor/editor/lang/bg.js
new file mode 100644 (file)
index 0000000..423bd02
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Bulgarian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Скрий панела с инструментите",\r
+ToolbarExpand          : "Покажи панела с инструментите",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Запази",\r
+NewPage                                : "Нова страница",\r
+Preview                                : "Предварителен изглед",\r
+Cut                                    : "Изрежи",\r
+Copy                           : "Запамети",\r
+Paste                          : "Вмъкни",\r
+PasteText                      : "Вмъкни само текст",\r
+PasteWord                      : "Вмъкни от MS Word",\r
+Print                          : "Печат",\r
+SelectAll                      : "Селектирай всичко",\r
+RemoveFormat           : "Изтрий форматирането",\r
+InsertLinkLbl          : "Връзка",\r
+InsertLink                     : "Добави/Редактирай връзка",\r
+RemoveLink                     : "Изтрий връзка",\r
+Anchor                         : "Добави/Редактирай котва",\r
+InsertImageLbl         : "Изображение",\r
+InsertImage                    : "Добави/Редактирай изображение",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Добави/Редактиай Flash обект",\r
+InsertTableLbl         : "Таблица",\r
+InsertTable                    : "Добави/Редактирай таблица",\r
+InsertLineLbl          : "Линия",\r
+InsertLine                     : "Вмъкни хоризонтална линия",\r
+InsertSpecialCharLbl: "Специален символ",\r
+InsertSpecialChar      : "Вмъкни специален символ",\r
+InsertSmileyLbl                : "Усмивка",\r
+InsertSmiley           : "Добави усмивка",\r
+About                          : "За FCKeditor",\r
+Bold                           : "Удебелен",\r
+Italic                         : "Курсив",\r
+Underline                      : "Подчертан",\r
+StrikeThrough          : "Зачертан",\r
+Subscript                      : "Индекс за база",\r
+Superscript                    : "Индекс за степен",\r
+LeftJustify                    : "Подравняване в ляво",\r
+CenterJustify          : "Подравнявне в средата",\r
+RightJustify           : "Подравняване в дясно",\r
+BlockJustify           : "Двустранно подравняване",\r
+DecreaseIndent         : "Намали отстъпа",\r
+IncreaseIndent         : "Увеличи отстъпа",\r
+Undo                           : "Отмени",\r
+Redo                           : "Повтори",\r
+NumberedListLbl                : "Нумериран списък",\r
+NumberedList           : "Добави/Изтрий нумериран списък",\r
+BulletedListLbl                : "Ненумериран списък",\r
+BulletedList           : "Добави/Изтрий ненумериран списък",\r
+ShowTableBorders       : "Покажи рамките на таблицата",\r
+ShowDetails                    : "Покажи подробности",\r
+Style                          : "Стил",\r
+FontFormat                     : "Формат",\r
+Font                           : "Шрифт",\r
+FontSize                       : "Размер",\r
+TextColor                      : "Цвят на текста",\r
+BGColor                                : "Цвят на фона",\r
+Source                         : "Код",\r
+Find                           : "Търси",\r
+Replace                                : "Замести",\r
+SpellCheck                     : "Провери правописа",\r
+UniversalKeyboard      : "Универсална клавиатура",\r
+PageBreakLbl           : "Нов ред",\r
+PageBreak                      : "Вмъкни нов ред",\r
+\r
+Form                   : "Формуляр",\r
+Checkbox               : "Поле за отметка",\r
+RadioButton            : "Поле за опция",\r
+TextField              : "Текстово поле",\r
+Textarea               : "Текстова област",\r
+HiddenField            : "Скрито поле",\r
+Button                 : "Бутон",\r
+SelectionField : "Падащо меню с опции",\r
+ImageButton            : "Бутон-изображение",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Редактирай връзка",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Добави ред",\r
+DeleteRows                     : "Изтрий редовете",\r
+InsertColumn           : "Добави колона",\r
+DeleteColumns          : "Изтрий колоните",\r
+InsertCell                     : "Добави клетка",\r
+DeleteCells                    : "Изтрий клетките",\r
+MergeCells                     : "Обедини клетките",\r
+SplitCell                      : "Раздели клетката",\r
+TableDelete                    : "Изтрий таблицата",\r
+CellProperties         : "Параметри на клетката",\r
+TableProperties                : "Параметри на таблицата",\r
+ImageProperties                : "Параметри на изображението",\r
+FlashProperties                : "Параметри на Flash обекта",\r
+\r
+AnchorProp                     : "Параметри на котвата",\r
+ButtonProp                     : "Параметри на бутона",\r
+CheckboxProp           : "Параметри на полето за отметка",\r
+HiddenFieldProp                : "Параметри на скритото поле",\r
+RadioButtonProp                : "Параметри на полето за опция",\r
+ImageButtonProp                : "Параметри на бутона-изображение",\r
+TextFieldProp          : "Параметри на текстовото-поле",\r
+SelectionFieldProp     : "Параметри на падащото меню с опции",\r
+TextareaProp           : "Параметри на текстовата област",\r
+FormProp                       : "Параметри на формуляра",\r
+\r
+FontFormats                    : "Нормален;Форматиран;Адрес;Заглавие 1;Заглавие 2;Заглавие 3;Заглавие 4;Заглавие 5;Заглавие 6;Параграф (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Обработка на XHTML. Моля изчакайте...",\r
+Done                           : "Готово",\r
+PasteWordConfirm       : "Текстът, който искате да вмъкнете е копиран от MS Word. Желаете ли да бъде изчистен преди вмъкването?",\r
+NotCompatiblePaste     : "Тази операция изисква MS Internet Explorer версия 5.5 или по-висока. Желаете ли да вмъкнете запаметеното без изчистване?",\r
+UnknownToolbarItem     : "Непознат инструмент \"%1\"",\r
+UnknownCommand         : "Непозната команда \"%1\"",\r
+NotImplemented         : "Командата не е имплементирана",\r
+UnknownToolbarSet      : "Панелът \"%1\" не съществува",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "ОК",\r
+DlgBtnCancel           : "Отказ",\r
+DlgBtnClose                    : "Затвори",\r
+DlgBtnBrowseServer     : "Разгледай сървъра",\r
+DlgAdvancedTag         : "Подробности...",\r
+DlgOpOther                     : "<Друго>",\r
+DlgInfoTab                     : "Информация",\r
+DlgAlertUrl                    : "Моля, въведете пълния път (URL)",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<не е настроен>",\r
+DlgGenId                       : "Идентификатор",\r
+DlgGenLangDir          : "посока на речта",\r
+DlgGenLangDirLtr       : "От ляво на дясно",\r
+DlgGenLangDirRtl       : "От дясно на ляво",\r
+DlgGenLangCode         : "Код на езика",\r
+DlgGenAccessKey                : "Бърз клавиш",\r
+DlgGenName                     : "Име",\r
+DlgGenTabIndex         : "Ред на достъп",\r
+DlgGenLongDescr                : "Описание на връзката",\r
+DlgGenClass                    : "Клас от стиловите таблици",\r
+DlgGenTitle                    : "Препоръчително заглавие",\r
+DlgGenContType         : "Препоръчителен тип на съдържанието",\r
+DlgGenLinkCharset      : "Тип на свързания ресурс",\r
+DlgGenStyle                    : "Стил",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Параметри на изображението",\r
+DlgImgInfoTab          : "Информация за изображението",\r
+DlgImgBtnUpload                : "Прати към сървъра",\r
+DlgImgURL                      : "Пълен път (URL)",\r
+DlgImgUpload           : "Качи",\r
+DlgImgAlt                      : "Алтернативен текст",\r
+DlgImgWidth                    : "Ширина",\r
+DlgImgHeight           : "Височина",\r
+DlgImgLockRatio                : "Запази пропорцията",\r
+DlgBtnResetSize                : "Възстанови размера",\r
+DlgImgBorder           : "Рамка",\r
+DlgImgHSpace           : "Хоризонтален отстъп",\r
+DlgImgVSpace           : "Вертикален отстъп",\r
+DlgImgAlign                    : "Подравняване",\r
+DlgImgAlignLeft                : "Ляво",\r
+DlgImgAlignAbsBottom: "Най-долу",\r
+DlgImgAlignAbsMiddle: "Точно по средата",\r
+DlgImgAlignBaseline    : "По базовата линия",\r
+DlgImgAlignBottom      : "Долу",\r
+DlgImgAlignMiddle      : "По средата",\r
+DlgImgAlignRight       : "Дясно",\r
+DlgImgAlignTextTop     : "Върху текста",\r
+DlgImgAlignTop         : "Отгоре",\r
+DlgImgPreview          : "Изглед",\r
+DlgImgAlertUrl         : "Моля, въведете пълния път до изображението",\r
+DlgImgLinkTab          : "Връзка",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Параметри на Flash обекта",\r
+DlgFlashChkPlay                : "Автоматично стартиране",\r
+DlgFlashChkLoop                : "Ново стартиране след завършването",\r
+DlgFlashChkMenu                : "Разрешено Flash меню",\r
+DlgFlashScale          : "Оразмеряване",\r
+DlgFlashScaleAll       : "Покажи целия обект",\r
+DlgFlashScaleNoBorder  : "Без рамка",\r
+DlgFlashScaleFit       : "Според мястото",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Връзка",\r
+DlgLnkInfoTab          : "Информация за връзката",\r
+DlgLnkTargetTab                : "Цел",\r
+\r
+DlgLnkType                     : "Вид на връзката",\r
+DlgLnkTypeURL          : "Пълен път (URL)",\r
+DlgLnkTypeAnchor       : "Котва в текущата страница",\r
+DlgLnkTypeEMail                : "Е-поща",\r
+DlgLnkProto                    : "Протокол",\r
+DlgLnkProtoOther       : "<друго>",\r
+DlgLnkURL                      : "Пълен път (URL)",\r
+DlgLnkAnchorSel                : "Изберете котва",\r
+DlgLnkAnchorByName     : "По име на котвата",\r
+DlgLnkAnchorById       : "По идентификатор на елемент",\r
+DlgLnkNoAnchors                : "<Няма котви в текущия документ>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Адрес за е-поща",\r
+DlgLnkEMailSubject     : "Тема на писмото",\r
+DlgLnkEMailBody                : "Текст на писмото",\r
+DlgLnkUpload           : "Качи",\r
+DlgLnkBtnUpload                : "Прати на сървъра",\r
+\r
+DlgLnkTarget           : "Цел",\r
+DlgLnkTargetFrame      : "<рамка>",\r
+DlgLnkTargetPopup      : "<дъщерен прозорец>",\r
+DlgLnkTargetBlank      : "Нов прозорец (_blank)",\r
+DlgLnkTargetParent     : "Родителски прозорец (_parent)",\r
+DlgLnkTargetSelf       : "Активния прозорец (_self)",\r
+DlgLnkTargetTop                : "Целия прозорец (_top)",\r
+DlgLnkTargetFrameName  : "Име на целевия прозорец",\r
+DlgLnkPopWinName       : "Име на дъщерния прозорец",\r
+DlgLnkPopWinFeat       : "Параметри на дъщерния прозорец",\r
+DlgLnkPopResize                : "С променливи размери",\r
+DlgLnkPopLocation      : "Поле за адрес",\r
+DlgLnkPopMenu          : "Меню",\r
+DlgLnkPopScroll                : "Плъзгач",\r
+DlgLnkPopStatus                : "Поле за статус",\r
+DlgLnkPopToolbar       : "Панел с бутони",\r
+DlgLnkPopFullScrn      : "Голям екран (MS IE)",\r
+DlgLnkPopDependent     : "Зависим (Netscape)",\r
+DlgLnkPopWidth         : "Ширина",\r
+DlgLnkPopHeight                : "Височина",\r
+DlgLnkPopLeft          : "Координати - X",\r
+DlgLnkPopTop           : "Координати - Y",\r
+\r
+DlnLnkMsgNoUrl         : "Моля, напишете пълния път (URL)",\r
+DlnLnkMsgNoEMail       : "Моля, напишете адреса за е-поща",\r
+DlnLnkMsgNoAnchor      : "Моля, изберете котва",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Изберете цвят",\r
+DlgColorBtnClear       : "Изчисти",\r
+DlgColorHighlight      : "Текущ",\r
+DlgColorSelected       : "Избран",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Добави усмивка",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Изберете специален символ",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Параметри на таблицата",\r
+DlgTableRows           : "Редове",\r
+DlgTableColumns                : "Колони",\r
+DlgTableBorder         : "Размер на рамката",\r
+DlgTableAlign          : "Подравняване",\r
+DlgTableAlignNotSet    : "<Не е избрано>",\r
+DlgTableAlignLeft      : "Ляво",\r
+DlgTableAlignCenter    : "Център",\r
+DlgTableAlignRight     : "Дясно",\r
+DlgTableWidth          : "Ширина",\r
+DlgTableWidthPx                : "пиксели",\r
+DlgTableWidthPc                : "проценти",\r
+DlgTableHeight         : "Височина",\r
+DlgTableCellSpace      : "Разстояние между клетките",\r
+DlgTableCellPad                : "Отстъп на съдържанието в клетките",\r
+DlgTableCaption                : "Заглавие",\r
+DlgTableSummary                : "Резюме",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Параметри на клетката",\r
+DlgCellWidth           : "Ширина",\r
+DlgCellWidthPx         : "пиксели",\r
+DlgCellWidthPc         : "проценти",\r
+DlgCellHeight          : "Височина",\r
+DlgCellWordWrap                : "пренасяне на нов ред",\r
+DlgCellWordWrapNotSet  : "<Не е настроено>",\r
+DlgCellWordWrapYes     : "Да",\r
+DlgCellWordWrapNo      : "не",\r
+DlgCellHorAlign                : "Хоризонтално подравняване",\r
+DlgCellHorAlignNotSet  : "<Не е настроено>",\r
+DlgCellHorAlignLeft    : "Ляво",\r
+DlgCellHorAlignCenter  : "Център",\r
+DlgCellHorAlignRight: "Дясно",\r
+DlgCellVerAlign                : "Вертикално подравняване",\r
+DlgCellVerAlignNotSet  : "<Не е настроено>",\r
+DlgCellVerAlignTop     : "Горе",\r
+DlgCellVerAlignMiddle  : "По средата",\r
+DlgCellVerAlignBottom  : "Долу",\r
+DlgCellVerAlignBaseline        : "По базовата линия",\r
+DlgCellRowSpan         : "повече от един ред",\r
+DlgCellCollSpan                : "повече от една колона",\r
+DlgCellBackColor       : "фонов цвят",\r
+DlgCellBorderColor     : "цвят на рамката",\r
+DlgCellBtnSelect       : "Изберете...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Търси",\r
+DlgFindFindBtn         : "Търси",\r
+DlgFindNotFoundMsg     : "Указания текст не беше намерен.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Замести",\r
+DlgReplaceFindLbl              : "Търси:",\r
+DlgReplaceReplaceLbl   : "Замести с:",\r
+DlgReplaceCaseChk              : "Със същия регистър",\r
+DlgReplaceReplaceBtn   : "Замести",\r
+DlgReplaceReplAllBtn   : "Замести всички",\r
+DlgReplaceWordChk              : "Търси същата дума",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Настройките за сигурност на вашия бразуър не разрешават на редактора да изпълни изрязването. За целта използвайте клавиатурата (Ctrl+X).",\r
+PasteErrorCopy : "Настройките за сигурност на вашия бразуър не разрешават на редактора да изпълни запаметяването. За целта използвайте клавиатурата (Ctrl+C).",\r
+\r
+PasteAsText            : "Вмъкни като чист текст",\r
+PasteFromWord  : "Вмъкни от MS Word",\r
+\r
+DlgPasteMsg2   : "Вмъкнете тук съдъжанието с клавиатуарата (<STRONG>Ctrl+V</STRONG>) и натиснете <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Игнорирай шрифтовите дефиниции",\r
+DlgPasteRemoveStyles   : "Изтрий стиловите дефиниции",\r
+DlgPasteCleanBox               : "Изчисти",\r
+\r
+// Color Picker\r
+ColorAutomatic : "По подразбиране",\r
+ColorMoreColors        : "Други цветове...",\r
+\r
+// Document Properties\r
+DocProps               : "Параметри на документа",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Параметри на котвата",\r
+DlgAnchorName          : "Име на котвата",\r
+DlgAnchorErrorName     : "Моля, въведете име на котвата",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Липсва в речника",\r
+DlgSpellChangeTo               : "Промени на",\r
+DlgSpellBtnIgnore              : "Игнорирай",\r
+DlgSpellBtnIgnoreAll   : "Игнорирай всички",\r
+DlgSpellBtnReplace             : "Замести",\r
+DlgSpellBtnReplaceAll  : "Замести всички",\r
+DlgSpellBtnUndo                        : "Отмени",\r
+DlgSpellNoSuggestions  : "- Няма предложения -",\r
+DlgSpellProgress               : "Извършване на проверката за правопис...",\r
+DlgSpellNoMispell              : "Проверката за правопис завършена: не са открити правописни грешки",\r
+DlgSpellNoChanges              : "Проверката за правопис завършена: няма променени думи",\r
+DlgSpellOneChange              : "Проверката за правопис завършена: една дума е променена",\r
+DlgSpellManyChanges            : "Проверката за правопис завършена: %1 думи са променени",\r
+\r
+IeSpellDownload                        : "Инструментът за проверка на правопис не е инсталиран. Желаете ли да го инсталирате ?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Текст (Стойност)",\r
+DlgButtonType          : "Тип",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Име",\r
+DlgCheckboxValue       : "Стойност",\r
+DlgCheckboxSelected    : "Отметнато",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Име",\r
+DlgFormAction  : "Действие",\r
+DlgFormMethod  : "Метод",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Име",\r
+DlgSelectValue         : "Стойност",\r
+DlgSelectSize          : "Размер",\r
+DlgSelectLines         : "линии",\r
+DlgSelectChkMulti      : "Разрешено множествено селектиране",\r
+DlgSelectOpAvail       : "Възможни опции",\r
+DlgSelectOpText                : "Текст",\r
+DlgSelectOpValue       : "Стойност",\r
+DlgSelectBtnAdd                : "Добави",\r
+DlgSelectBtnModify     : "Промени",\r
+DlgSelectBtnUp         : "Нагоре",\r
+DlgSelectBtnDown       : "Надолу",\r
+DlgSelectBtnSetValue : "Настрой като избрана стойност",\r
+DlgSelectBtnDelete     : "Изтрий",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Име",\r
+DlgTextareaCols        : "Колони",\r
+DlgTextareaRows        : "Редове",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Име",\r
+DlgTextValue           : "Стойност",\r
+DlgTextCharWidth       : "Ширина на символите",\r
+DlgTextMaxChars                : "Максимум символи",\r
+DlgTextType                    : "Тип",\r
+DlgTextTypeText                : "Текст",\r
+DlgTextTypePass                : "Парола",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Име",\r
+DlgHiddenValue : "Стойност",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Параметри на ненумерирания списък",\r
+NumberedListProp       : "Параметри на нумерирания списък",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Тип",\r
+DlgLstTypeCircle       : "Окръжност",\r
+DlgLstTypeDisc         : "Кръг",\r
+DlgLstTypeSquare       : "Квадрат",\r
+DlgLstTypeNumbers      : "Числа (1, 2, 3)",\r
+DlgLstTypeLCase                : "Малки букви (a, b, c)",\r
+DlgLstTypeUCase                : "Големи букви (A, B, C)",\r
+DlgLstTypeSRoman       : "Малки римски числа (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Големи римски числа (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Общи",\r
+DlgDocBackTab          : "Фон",\r
+DlgDocColorsTab                : "Цветове и отстъпи",\r
+DlgDocMetaTab          : "Мета данни",\r
+\r
+DlgDocPageTitle                : "Заглавие на страницата",\r
+DlgDocLangDir          : "Посока на речта",\r
+DlgDocLangDirLTR       : "От ляво на дясно",\r
+DlgDocLangDirRTL       : "От дясно на ляво",\r
+DlgDocLangCode         : "Код на езика",\r
+DlgDocCharSet          : "Кодиране на символите",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Друго кодиране на символите",\r
+\r
+DlgDocDocType          : "Тип на документа",\r
+DlgDocDocTypeOther     : "Друг тип на документа",\r
+DlgDocIncXHTML         : "Включи XHTML декларация",\r
+DlgDocBgColor          : "Цвят на фона",\r
+DlgDocBgImage          : "Пълен път до фоновото изображение",\r
+DlgDocBgNoScroll       : "Не-повтарящо се фоново изображение",\r
+DlgDocCText                    : "Текст",\r
+DlgDocCLink                    : "Връзка",\r
+DlgDocCVisited         : "Посетена връзка",\r
+DlgDocCActive          : "Активна връзка",\r
+DlgDocMargins          : "Отстъпи на страницата",\r
+DlgDocMaTop                    : "Горе",\r
+DlgDocMaLeft           : "Ляво",\r
+DlgDocMaRight          : "Дясно",\r
+DlgDocMaBottom         : "Долу",\r
+DlgDocMeIndex          : "Ключови думи за документа (разделени със запетаи)",\r
+DlgDocMeDescr          : "Описание на документа",\r
+DlgDocMeAuthor         : "Автор",\r
+DlgDocMeCopy           : "Авторски права",\r
+DlgDocPreview          : "Изглед",\r
+\r
+// Templates Dialog\r
+Templates                      : "Шаблони",\r
+DlgTemplatesTitle      : "Шаблони",\r
+DlgTemplatesSelMsg     : "Изберете шаблон <br>(текущото съдържание на редактора ще бъде загубено):",\r
+DlgTemplatesLoading    : "Зареждане на списъка с шаблоните. Моля изчакайте...",\r
+DlgTemplatesNoTpl      : "(Няма дефинирани шаблони)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "За",\r
+DlgAboutBrowserInfoTab : "Информация за браузъра",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "версия",\r
+DlgAboutInfo           : "За повече информация посетете"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/bn.js b/httemplate/elements/fckeditor/editor/lang/bn.js
new file mode 100644 (file)
index 0000000..8f76754
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Bengali/Bangla language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "টূলবার গুটিয়ে দাও",\r
+ToolbarExpand          : "টূলবার ছড়িয়ে দাও",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "সংরক্ষন কর",\r
+NewPage                                : "নতুন পেজ",\r
+Preview                                : "প্রিভিউ",\r
+Cut                                    : "কাট",\r
+Copy                           : "কপি",\r
+Paste                          : "পেস্ট",\r
+PasteText                      : "পেস্ট (সাদা টেক্সট)",\r
+PasteWord                      : "পেস্ট (শব্দ)",\r
+Print                          : "প্রিন্ট",\r
+SelectAll                      : "সব সিলেক্ট কর",\r
+RemoveFormat           : "ফরমেট সরাও",\r
+InsertLinkLbl          : "লিংকের যুক্ত করার লেবেল",\r
+InsertLink                     : "লিংক যুক্ত কর",\r
+RemoveLink                     : "লিংক সরাও",\r
+Anchor                         : "নোঙ্গর",\r
+InsertImageLbl         : "ছবির লেবেল যুক্ত কর",\r
+InsertImage                    : "ছবি যুক্ত কর",\r
+InsertFlashLbl         : "ফ্লাশ লেবেল যুক্ত কর",\r
+InsertFlash                    : "ফ্লাশ যুক্ত কর",\r
+InsertTableLbl         : "টেবিলের লেবেল যুক্ত কর",\r
+InsertTable                    : "টেবিল যুক্ত কর",\r
+InsertLineLbl          : "রেখা যুক্ত কর",\r
+InsertLine                     : "রেখা যুক্ত কর",\r
+InsertSpecialCharLbl: "বিশেষ অক্ষরের লেবেল যুক্ত কর",\r
+InsertSpecialChar      : "বিশেষ অক্ষর যুক্ত কর",\r
+InsertSmileyLbl                : "স্মাইলী",\r
+InsertSmiley           : "স্মাইলী যুক্ত কর",\r
+About                          : "FCKeditor কে বানিয়েছে",\r
+Bold                           : "বোল্ড",\r
+Italic                         : "ইটালিক",\r
+Underline                      : "আন্ডারলাইন",\r
+StrikeThrough          : "স্ট্রাইক থ্রু",\r
+Subscript                      : "অধোলেখ",\r
+Superscript                    : "অভিলেখ",\r
+LeftJustify                    : "বা দিকে ঘেঁষা",\r
+CenterJustify          : "মাঝ বরাবর ঘেষা",\r
+RightJustify           : "ডান দিকে ঘেঁষা",\r
+BlockJustify           : "ব্লক জাস্টিফাই",\r
+DecreaseIndent         : "ইনডেন্ট কমাও",\r
+IncreaseIndent         : "ইনডেন্ট বাড়াও",\r
+Undo                           : "আনডু",\r
+Redo                           : "রি-ডু",\r
+NumberedListLbl                : "সাংখ্যিক লিস্টের লেবেল",\r
+NumberedList           : "সাংখ্যিক লিস্ট",\r
+BulletedListLbl                : "বুলেট লিস্ট লেবেল",\r
+BulletedList           : "বুলেটেড লিস্ট",\r
+ShowTableBorders       : "টেবিল বর্ডার",\r
+ShowDetails                    : "সবটুকু দেখাও",\r
+Style                          : "স্টাইল",\r
+FontFormat                     : "ফন্ট ফরমেট",\r
+Font                           : "ফন্ট",\r
+FontSize                       : "সাইজ",\r
+TextColor                      : "টেক্স্ট রং",\r
+BGColor                                : "বেকগ্রাউন্ড রং",\r
+Source                         : "সোর্স",\r
+Find                           : "খোজো",\r
+Replace                                : "রিপ্লেস",\r
+SpellCheck                     : "বানান চেক",\r
+UniversalKeyboard      : "সার্বজনীন কিবোর্ড",\r
+PageBreakLbl           : "পেজ ব্রেক লেবেল",\r
+PageBreak                      : "পেজ ব্রেক",\r
+\r
+Form                   : "ফর্ম",\r
+Checkbox               : "চেক বাক্স",\r
+RadioButton            : "রেডিও বাটন",\r
+TextField              : "টেক্সট ফীল্ড",\r
+Textarea               : "টেক্সট এরিয়া",\r
+HiddenField            : "গুপ্ত ফীল্ড",\r
+Button                 : "বাটন",\r
+SelectionField : "বাছাই ফীল্ড",\r
+ImageButton            : "ছবির বাটন",\r
+\r
+FitWindow              : "উইন্ডো ফিট কর",\r
+\r
+// Context Menu\r
+EditLink                       : "লিংক সম্পাদন",\r
+CellCM                         : "সেল",\r
+RowCM                          : "রো",\r
+ColumnCM                       : "কলাম",\r
+InsertRow                      : "রো যুক্ত কর",\r
+DeleteRows                     : "রো মুছে দাও",\r
+InsertColumn           : "কলাম যুক্ত কর",\r
+DeleteColumns          : "কলাম মুছে দাও",\r
+InsertCell                     : "সেল যুক্ত কর",\r
+DeleteCells                    : "সেল মুছে দাও",\r
+MergeCells                     : "সেল জোড়া দাও",\r
+SplitCell                      : "সেল আলাদা কর",\r
+TableDelete                    : "টেবিল ডিলীট কর",\r
+CellProperties         : "সেলের প্রোপার্টিজ",\r
+TableProperties                : "টেবিল প্রোপার্টি",\r
+ImageProperties                : "ছবি প্রোপার্টি",\r
+FlashProperties                : "ফ্লাশ প্রোপার্টি",\r
+\r
+AnchorProp                     : "নোঙর প্রোপার্টি",\r
+ButtonProp                     : "বাটন প্রোপার্টি",\r
+CheckboxProp           : "চেক বক্স প্রোপার্টি",\r
+HiddenFieldProp                : "গুপ্ত ফীল্ড প্রোপার্টি",\r
+RadioButtonProp                : "রেডিও বাটন প্রোপার্টি",\r
+ImageButtonProp                : "ছবি বাটন প্রোপার্টি",\r
+TextFieldProp          : "টেক্সট ফীল্ড প্রোপার্টি",\r
+SelectionFieldProp     : "বাছাই ফীল্ড প্রোপার্টি",\r
+TextareaProp           : "টেক্সট এরিয়া প্রোপার্টি",\r
+FormProp                       : "ফর্ম প্রোপার্টি",\r
+\r
+FontFormats                    : "সাধারণ;ফর্মেটেড;ঠিকানা;শীর্ষক ১;শীর্ষক ২;শীর্ষক ৩;শীর্ষক ৪;শীর্ষক ৫;শীর্ষক ৬;শীর্ষক (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML প্রসেস করা হচ্ছে",\r
+Done                           : "শেষ হয়েছে",\r
+PasteWordConfirm       : "যে টেকস্টটি আপনি পেস্ট করতে চাচ্ছেন মনে হচ্ছে সেটি ওয়ার্ড থেকে কপি করা। আপনি কি পেস্ট করার আগে একে পরিষ্কার করতে চান?",\r
+NotCompatiblePaste     : "এই কমান্ডটি শুধুমাত্র ইন্টারনেট এক্সপ্লোরার ৫.০ বা তার পরের ভার্সনে পাওয়া সম্ভব। আপনি কি পরিষ্কার না করেই পেস্ট করতে চান?",\r
+UnknownToolbarItem     : "অজানা টুলবার আইটেম \"%1\"",\r
+UnknownCommand         : "অজানা কমান্ড \"%1\"",\r
+NotImplemented         : "কমান্ড ইমপ্লিমেন্ট করা হয়নি",\r
+UnknownToolbarSet      : "টুলবার সেট \"%1\" এর অস্তিত্ব নেই",\r
+NoActiveX                      : "আপনার ব্রাউজারের সুরক্ষা সেটিংস কারনে এডিটরের কিছু ফিচার পাওয়া নাও যেতে পারে। আপনাকে অবশ্যই \"Run ActiveX controls and plug-ins\" এনাবেল করে নিতে হবে। আপনি ভুলভ্রান্তি কিছু কিছু ফিচারের অনুপস্থিতি উপলব্ধি করতে পারেন।",\r
+BrowseServerBlocked : "রিসোর্স ব্রাউজার খোলা গেল না। নিশ্চিত করুন যে সব পপআপ ব্লকার বন্ধ করা আছে।",\r
+DialogBlocked          : "ডায়ালগ ইউন্ডো খোলা গেল না। নিশ্চিত করুন যে সব পপআপ ব্লকার বন্ধ করা আছে।",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "ওকে",\r
+DlgBtnCancel           : "বাতিল",\r
+DlgBtnClose                    : "বন্ধ কর",\r
+DlgBtnBrowseServer     : "ব্রাউজ সার্ভার",\r
+DlgAdvancedTag         : "এডভান্সড",\r
+DlgOpOther                     : "<অন্য>",\r
+DlgInfoTab                     : "তথ্য",\r
+DlgAlertUrl                    : "দয়া করে URL যুক্ত করুন",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<সেট নেই>",\r
+DlgGenId                       : "আইডি",\r
+DlgGenLangDir          : "ভাষা লেখার দিক",\r
+DlgGenLangDirLtr       : "বাম থেকে ডান (LTR)",\r
+DlgGenLangDirRtl       : "ডান থেকে বাম (RTL)",\r
+DlgGenLangCode         : "ভাষা কোড",\r
+DlgGenAccessKey                : "এক্সেস কী",\r
+DlgGenName                     : "নাম",\r
+DlgGenTabIndex         : "ট্যাব ইন্ডেক্স",\r
+DlgGenLongDescr                : "URL এর লম্বা বর্ণনা",\r
+DlgGenClass                    : "স্টাইল-শীট ক্লাস",\r
+DlgGenTitle                    : "পরামর্শ শীর্ষক",\r
+DlgGenContType         : "পরামর্শ কন্টেন্টের প্রকার",\r
+DlgGenLinkCharset      : "লিংক রিসোর্স ক্যারেক্টর সেট",\r
+DlgGenStyle                    : "স্টাইল",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "ছবির প্রোপার্টি",\r
+DlgImgInfoTab          : "ছবির তথ্য",\r
+DlgImgBtnUpload                : "ইহাকে সার্ভারে প্রেরন কর",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "আপলোড",\r
+DlgImgAlt                      : "বিকল্প টেক্সট",\r
+DlgImgWidth                    : "প্রস্থ",\r
+DlgImgHeight           : "দৈর্ঘ্য",\r
+DlgImgLockRatio                : "অনুপাত লক কর",\r
+DlgBtnResetSize                : "সাইজ পূর্বাবস্থায় ফিরিয়ে দাও",\r
+DlgImgBorder           : "বর্ডার",\r
+DlgImgHSpace           : "হরাইজন্টাল স্পেস",\r
+DlgImgVSpace           : "ভার্টিকেল স্পেস",\r
+DlgImgAlign                    : "এলাইন",\r
+DlgImgAlignLeft                : "বামে",\r
+DlgImgAlignAbsBottom: "Abs নীচে",\r
+DlgImgAlignAbsMiddle: "Abs উপর",\r
+DlgImgAlignBaseline    : "মূল রেখা",\r
+DlgImgAlignBottom      : "নীচে",\r
+DlgImgAlignMiddle      : "মধ্য",\r
+DlgImgAlignRight       : "ডানে",\r
+DlgImgAlignTextTop     : "টেক্সট উপর",\r
+DlgImgAlignTop         : "উপর",\r
+DlgImgPreview          : "প্রীভিউ",\r
+DlgImgAlertUrl         : "অনুগ্রহক করে ছবির URL টাইপ করুন",\r
+DlgImgLinkTab          : "লিংক",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "ফ্ল্যাশ প্রোপার্টি",\r
+DlgFlashChkPlay                : "অটো প্লে",\r
+DlgFlashChkLoop                : "লূপ",\r
+DlgFlashChkMenu                : "ফ্ল্যাশ মেনু এনাবল কর",\r
+DlgFlashScale          : "স্কেল",\r
+DlgFlashScaleAll       : "সব দেখাও",\r
+DlgFlashScaleNoBorder  : "কোনো বর্ডার নেই",\r
+DlgFlashScaleFit       : "নিখুঁত ফিট",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "লিংক",\r
+DlgLnkInfoTab          : "লিংক তথ্য",\r
+DlgLnkTargetTab                : "টার্গেট",\r
+\r
+DlgLnkType                     : "লিংক প্রকার",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "এই পেজে নোঙর কর",\r
+DlgLnkTypeEMail                : "ইমেইল",\r
+DlgLnkProto                    : "প্রোটোকল",\r
+DlgLnkProtoOther       : "<অন্য>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "নোঙর বাছাই",\r
+DlgLnkAnchorByName     : "নোঙরের নাম দিয়ে",\r
+DlgLnkAnchorById       : "নোঙরের আইডি দিয়ে",\r
+DlgLnkNoAnchors                : "<ডকুমেন্টে আর কোন নোঙর নেই>",              //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "ইমেইল ঠিকানা",\r
+DlgLnkEMailSubject     : "মেসেজের বিষয়",\r
+DlgLnkEMailBody                : "মেসেজের দেহ",\r
+DlgLnkUpload           : "আপলোড",\r
+DlgLnkBtnUpload                : "একে সার্ভারে পাঠাও",\r
+\r
+DlgLnkTarget           : "টার্গেট",\r
+DlgLnkTargetFrame      : "<ফ্রেম>",\r
+DlgLnkTargetPopup      : "<পপআপ উইন্ডো>",\r
+DlgLnkTargetBlank      : "নতুন উইন্ডো (_blank)",\r
+DlgLnkTargetParent     : "মূল উইন্ডো (_parent)",\r
+DlgLnkTargetSelf       : "এই উইন্ডো (_self)",\r
+DlgLnkTargetTop                : "শীর্ষ উইন্ডো (_top)",\r
+DlgLnkTargetFrameName  : "টার্গেট ফ্রেমের নাম",\r
+DlgLnkPopWinName       : "পপআপ উইন্ডোর নাম",\r
+DlgLnkPopWinFeat       : "পপআপ উইন্ডো ফীচার সমূহ",\r
+DlgLnkPopResize                : "রিসাইজ করা সম্ভব",\r
+DlgLnkPopLocation      : "লোকেশন বার",\r
+DlgLnkPopMenu          : "মেন্যু বার",\r
+DlgLnkPopScroll                : "স্ক্রল বার",\r
+DlgLnkPopStatus                : "স্ট্যাটাস বার",\r
+DlgLnkPopToolbar       : "টুল বার",\r
+DlgLnkPopFullScrn      : "পূর্ণ পর্দা জুড়ে (IE)",\r
+DlgLnkPopDependent     : "ডিপেন্ডেন্ট (Netscape)",\r
+DlgLnkPopWidth         : "প্রস্থ",\r
+DlgLnkPopHeight                : "দৈর্ঘ্য",\r
+DlgLnkPopLeft          : "বামের পজিশন",\r
+DlgLnkPopTop           : "ডানের পজিশন",\r
+\r
+DlnLnkMsgNoUrl         : "অনুগ্রহ করে URL লিংক টাইপ করুন",\r
+DlnLnkMsgNoEMail       : "অনুগ্রহ করে ইমেইল এড্রেস টাইপ করুন",\r
+DlnLnkMsgNoAnchor      : "অনুগ্রহ করে নোঙর বাছাই করুন",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "রং বাছাই কর",\r
+DlgColorBtnClear       : "পরিষ্কার কর",\r
+DlgColorHighlight      : "হাইলাইট",\r
+DlgColorSelected       : "সিলেক্টেড",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "স্মাইলী যুক্ত কর",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "বিশেষ ক্যারেক্টার বাছাই কর",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "টেবিল প্রোপার্টি",\r
+DlgTableRows           : "রো",\r
+DlgTableColumns                : "কলাম",\r
+DlgTableBorder         : "বর্ডার সাইজ",\r
+DlgTableAlign          : "এলাইনমেন্ট",\r
+DlgTableAlignNotSet    : "<সেট নেই>",\r
+DlgTableAlignLeft      : "বামে",\r
+DlgTableAlignCenter    : "মাঝখানে",\r
+DlgTableAlignRight     : "ডানে",\r
+DlgTableWidth          : "প্রস্থ",\r
+DlgTableWidthPx                : "পিক্সেল",\r
+DlgTableWidthPc                : "শতকরা",\r
+DlgTableHeight         : "দৈর্ঘ্য",\r
+DlgTableCellSpace      : "সেল স্পেস",\r
+DlgTableCellPad                : "সেল প্যাডিং",\r
+DlgTableCaption                : "শীর্ষক",\r
+DlgTableSummary                : "সারাংশ",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "সেল প্রোপার্টি",\r
+DlgCellWidth           : "প্রস্থ",\r
+DlgCellWidthPx         : "পিক্সেল",\r
+DlgCellWidthPc         : "শতকরা",\r
+DlgCellHeight          : "দৈর্ঘ্য",\r
+DlgCellWordWrap                : "ওয়ার্ড রেপ",\r
+DlgCellWordWrapNotSet  : "<সেট নেই>",\r
+DlgCellWordWrapYes     : "হাঁ",\r
+DlgCellWordWrapNo      : "না",\r
+DlgCellHorAlign                : "হরাইজন্টাল এলাইনমেন্ট",\r
+DlgCellHorAlignNotSet  : "<সেট নেই>",\r
+DlgCellHorAlignLeft    : "বামে",\r
+DlgCellHorAlignCenter  : "মাঝখানে",\r
+DlgCellHorAlignRight: "ডানে",\r
+DlgCellVerAlign                : "ভার্টিক্যাল এলাইনমেন্ট",\r
+DlgCellVerAlignNotSet  : "<সেট নেই>",\r
+DlgCellVerAlignTop     : "উপর",\r
+DlgCellVerAlignMiddle  : "মধ্য",\r
+DlgCellVerAlignBottom  : "নীচে",\r
+DlgCellVerAlignBaseline        : "মূলরেখা",\r
+DlgCellRowSpan         : "রো স্প্যান",\r
+DlgCellCollSpan                : "কলাম স্প্যান",\r
+DlgCellBackColor       : "ব্যাকগ্রাউন্ড রং",\r
+DlgCellBorderColor     : "বর্ডারের রং",\r
+DlgCellBtnSelect       : "বাছাই কর",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "খোঁজো",\r
+DlgFindFindBtn         : "খোঁজো",\r
+DlgFindNotFoundMsg     : "আপনার উল্লেখিত টেকস্ট পাওয়া যায়নি",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "বদলে দাও",\r
+DlgReplaceFindLbl              : "যা খুঁজতে হবে:",\r
+DlgReplaceReplaceLbl   : "যার সাথে বদলাতে হবে:",\r
+DlgReplaceCaseChk              : "কেস মিলাও",\r
+DlgReplaceReplaceBtn   : "বদলে দাও",\r
+DlgReplaceReplAllBtn   : "সব বদলে দাও",\r
+DlgReplaceWordChk              : "পুরা শব্দ মেলাও",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "আপনার ব্রাউজারের সুরক্ষা সেটিংস এডিটরকে অটোমেটিক কাট করার অনুমতি দেয়নি। দয়া করে এই কাজের জন্য কিবোর্ড ব্যবহার করুন (Ctrl+X)।",\r
+PasteErrorCopy : "আপনার ব্রাউজারের সুরক্ষা সেটিংস এডিটরকে অটোমেটিক কপি করার অনুমতি দেয়নি। দয়া করে এই কাজের জন্য কিবোর্ড ব্যবহার করুন (Ctrl+C)।",\r
+\r
+PasteAsText            : "সাদা টেক্সট হিসেবে পেস্ট কর",\r
+PasteFromWord  : "ওয়ার্ড থেকে পেস্ট কর",\r
+\r
+DlgPasteMsg2   : "অনুগ্রহ করে নীচের বাক্সে কিবোর্ড ব্যবহার করে (<STRONG>Ctrl+V</STRONG>) পেস্ট করুন এবং <STRONG>OK</STRONG> চাপ দিন",\r
+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\r
+DlgPasteIgnoreFont             : "ফন্ট ফেস ডেফিনেশন ইগনোর করুন",\r
+DlgPasteRemoveStyles   : "স্টাইল ডেফিনেশন সরিয়ে দিন",\r
+DlgPasteCleanBox               : "বাক্স পরিষ্কার করুন",\r
+\r
+// Color Picker\r
+ColorAutomatic : "অটোমেটিক",\r
+ColorMoreColors        : "আরও রং...",\r
+\r
+// Document Properties\r
+DocProps               : "ডক্যুমেন্ট প্রোপার্টি",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "নোঙরের প্রোপার্টি",\r
+DlgAnchorName          : "নোঙরের নাম",\r
+DlgAnchorErrorName     : "নোঙরের নাম টাইপ করুন",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "শব্দকোষে নেই",\r
+DlgSpellChangeTo               : "এতে বদলাও",\r
+DlgSpellBtnIgnore              : "ইগনোর কর",\r
+DlgSpellBtnIgnoreAll   : "সব ইগনোর কর",\r
+DlgSpellBtnReplace             : "বদলে দাও",\r
+DlgSpellBtnReplaceAll  : "সব বদলে দাও",\r
+DlgSpellBtnUndo                        : "আন্ডু",\r
+DlgSpellNoSuggestions  : "- কোন সাজেশন নেই -",\r
+DlgSpellProgress               : "বানান পরীক্ষা চলছে...",\r
+DlgSpellNoMispell              : "বানান পরীক্ষা শেষ: কোন ভুল বানান পাওয়া যায়নি",\r
+DlgSpellNoChanges              : "বানান পরীক্ষা শেষ: কোন শব্দ পরিবর্তন করা হয়নি",\r
+DlgSpellOneChange              : "বানান পরীক্ষা শেষ: একটি মাত্র শব্দ পরিবর্তন করা হয়েছে",\r
+DlgSpellManyChanges            : "বানান পরীক্ষা শেষ: %1 গুলো শব্দ বদলে গ্যাছে",\r
+\r
+IeSpellDownload                        : "বানান পরীক্ষক ইনস্টল করা নেই। আপনি কি এখনই এটা ডাউনলোড করতে চান?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "টেক্সট (ভ্যালু)",\r
+DlgButtonType          : "প্রকার",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "নাম",\r
+DlgCheckboxValue       : "ভ্যালু",\r
+DlgCheckboxSelected    : "সিলেক্টেড",\r
+\r
+// Form Dialog\r
+DlgFormName            : "নাম",\r
+DlgFormAction  : "একশ্যন",\r
+DlgFormMethod  : "পদ্ধতি",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "নাম",\r
+DlgSelectValue         : "ভ্যালু",\r
+DlgSelectSize          : "সাইজ",\r
+DlgSelectLines         : "লাইন সমূহ",\r
+DlgSelectChkMulti      : "একাধিক সিলেকশন এলাউ কর",\r
+DlgSelectOpAvail       : "অন্যান্য বিকল্প",\r
+DlgSelectOpText                : "টেক্সট",\r
+DlgSelectOpValue       : "ভ্যালু",\r
+DlgSelectBtnAdd                : "যুক্ত",\r
+DlgSelectBtnModify     : "বদলে দাও",\r
+DlgSelectBtnUp         : "উপর",\r
+DlgSelectBtnDown       : "নীচে",\r
+DlgSelectBtnSetValue : "বাছাই করা ভ্যালু হিসেবে সেট কর",\r
+DlgSelectBtnDelete     : "ডিলীট",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "নাম",\r
+DlgTextareaCols        : "কলাম",\r
+DlgTextareaRows        : "রো",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "নাম",\r
+DlgTextValue           : "ভ্যালু",\r
+DlgTextCharWidth       : "ক্যারেক্টার প্রশস্ততা",\r
+DlgTextMaxChars                : "সর্বাধিক ক্যারেক্টার",\r
+DlgTextType                    : "টাইপ",\r
+DlgTextTypeText                : "টেক্সট",\r
+DlgTextTypePass                : "পাসওয়ার্ড",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "নাম",\r
+DlgHiddenValue : "ভ্যালু",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "বুলেটেড সূচী প্রোপার্টি",\r
+NumberedListProp       : "সাংখ্যিক সূচী প্রোপার্টি",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "প্রকার",\r
+DlgLstTypeCircle       : "গোল",\r
+DlgLstTypeDisc         : "ডিস্ক",\r
+DlgLstTypeSquare       : "চৌকোণা",\r
+DlgLstTypeNumbers      : "সংখ্যা (1, 2, 3)",\r
+DlgLstTypeLCase                : "ছোট অক্ষর (a, b, c)",\r
+DlgLstTypeUCase                : "বড় অক্ষর (A, B, C)",\r
+DlgLstTypeSRoman       : "ছোট রোমান সংখ্যা (i, ii, iii)",\r
+DlgLstTypeLRoman       : "বড় রোমান সংখ্যা (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "সাধারন",\r
+DlgDocBackTab          : "ব্যাকগ্রাউন্ড",\r
+DlgDocColorsTab                : "রং এবং মার্জিন",\r
+DlgDocMetaTab          : "মেটাডেটা",\r
+\r
+DlgDocPageTitle                : "পেজ শীর্ষক",\r
+DlgDocLangDir          : "ভাষা লিখার দিক",\r
+DlgDocLangDirLTR       : "বাম থেকে ডানে (LTR)",\r
+DlgDocLangDirRTL       : "ডান থেকে বামে (RTL)",\r
+DlgDocLangCode         : "ভাষা কোড",\r
+DlgDocCharSet          : "ক্যারেক্টার সেট এনকোডিং",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "অন্য ক্যারেক্টার সেট এনকোডিং",\r
+\r
+DlgDocDocType          : "ডক্যুমেন্ট টাইপ হেডিং",\r
+DlgDocDocTypeOther     : "অন্য ডক্যুমেন্ট টাইপ হেডিং",\r
+DlgDocIncXHTML         : "XHTML ডেক্লারেশন যুক্ত কর",\r
+DlgDocBgColor          : "ব্যাকগ্রাউন্ড রং",\r
+DlgDocBgImage          : "ব্যাকগ্রাউন্ড ছবির URL",\r
+DlgDocBgNoScroll       : "স্ক্রলহীন ব্যাকগ্রাউন্ড",\r
+DlgDocCText                    : "টেক্সট",\r
+DlgDocCLink                    : "লিংক",\r
+DlgDocCVisited         : "ভিজিট করা লিংক",\r
+DlgDocCActive          : "সক্রিয় লিংক",\r
+DlgDocMargins          : "পেজ মার্জিন",\r
+DlgDocMaTop                    : "উপর",\r
+DlgDocMaLeft           : "বামে",\r
+DlgDocMaRight          : "ডানে",\r
+DlgDocMaBottom         : "নীচে",\r
+DlgDocMeIndex          : "ডক্যুমেন্ট ইন্ডেক্স কিওয়ার্ড (কমা দ্বারা বিচ্ছিন্ন)",\r
+DlgDocMeDescr          : "ডক্যূমেন্ট বর্ণনা",\r
+DlgDocMeAuthor         : "লেখক",\r
+DlgDocMeCopy           : "কপীরাইট",\r
+DlgDocPreview          : "প্রীভিউ",\r
+\r
+// Templates Dialog\r
+Templates                      : "টেমপ্লেট",\r
+DlgTemplatesTitle      : "কনটেন্ট টেমপ্লেট",\r
+DlgTemplatesSelMsg     : "অনুগ্রহ করে এডিটরে ওপেন করার জন্য টেমপ্লেট বাছাই করুন<br>(আসল কনটেন্ট হারিয়ে যাবে):",\r
+DlgTemplatesLoading    : "টেমপ্লেট লিস্ট হারিয়ে যাবে। অনুগ্রহ করে অপেক্ষা করুন...",\r
+DlgTemplatesNoTpl      : "(কোন টেমপ্লেট ডিফাইন করা নেই)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "কে বানিয়েছে",\r
+DlgAboutBrowserInfoTab : "ব্রাউজারের ব্যাপারে তথ্য",\r
+DlgAboutLicenseTab     : "লাইসেন্স",\r
+DlgAboutVersion                : "ভার্সন",\r
+DlgAboutInfo           : "আরও তথ্যের জন্য যান"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/bs.js b/httemplate/elements/fckeditor/editor/lang/bs.js
new file mode 100644 (file)
index 0000000..fbaa451
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Bosnian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Skupi trake sa alatima",\r
+ToolbarExpand          : "Otvori trake sa alatima",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Snimi",\r
+NewPage                                : "Novi dokument",\r
+Preview                                : "Prikaži",\r
+Cut                                    : "Izreži",\r
+Copy                           : "Kopiraj",\r
+Paste                          : "Zalijepi",\r
+PasteText                      : "Zalijepi kao obièan tekst",\r
+PasteWord                      : "Zalijepi iz Word-a",\r
+Print                          : "Štampaj",\r
+SelectAll                      : "Selektuj sve",\r
+RemoveFormat           : "Poništi format",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Ubaci/Izmjeni link",\r
+RemoveLink                     : "Izbriši link",\r
+Anchor                         : "Insert/Edit Anchor", //MISSING\r
+InsertImageLbl         : "Slika",\r
+InsertImage                    : "Ubaci/Izmjeni sliku",\r
+InsertFlashLbl         : "Flash",      //MISSING\r
+InsertFlash                    : "Insert/Edit Flash",  //MISSING\r
+InsertTableLbl         : "Tabela",\r
+InsertTable                    : "Ubaci/Izmjeni tabelu",\r
+InsertLineLbl          : "Linija",\r
+InsertLine                     : "Ubaci horizontalnu liniju",\r
+InsertSpecialCharLbl: "Specijalni karakter",\r
+InsertSpecialChar      : "Ubaci specijalni karater",\r
+InsertSmileyLbl                : "Smješko",\r
+InsertSmiley           : "Ubaci smješka",\r
+About                          : "O FCKeditor-u",\r
+Bold                           : "Boldiraj",\r
+Italic                         : "Ukosi",\r
+Underline                      : "Podvuci",\r
+StrikeThrough          : "Precrtaj",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Lijevo poravnanje",\r
+CenterJustify          : "Centralno poravnanje",\r
+RightJustify           : "Desno poravnanje",\r
+BlockJustify           : "Puno poravnanje",\r
+DecreaseIndent         : "Smanji uvod",\r
+IncreaseIndent         : "Poveæaj uvod",\r
+Undo                           : "Vrati",\r
+Redo                           : "Ponovi",\r
+NumberedListLbl                : "Numerisana lista",\r
+NumberedList           : "Ubaci/Izmjeni numerisanu listu",\r
+BulletedListLbl                : "Lista",\r
+BulletedList           : "Ubaci/Izmjeni listu",\r
+ShowTableBorders       : "Pokaži okvire tabela",\r
+ShowDetails                    : "Pokaži detalje",\r
+Style                          : "Stil",\r
+FontFormat                     : "Format",\r
+Font                           : "Font",\r
+FontSize                       : "Velièina",\r
+TextColor                      : "Boja teksta",\r
+BGColor                                : "Boja pozadine",\r
+Source                         : "HTML kôd",\r
+Find                           : "Naði",\r
+Replace                                : "Zamjeni",\r
+SpellCheck                     : "Check Spelling",     //MISSING\r
+UniversalKeyboard      : "Universal Keyboard", //MISSING\r
+PageBreakLbl           : "Page Break", //MISSING\r
+PageBreak                      : "Insert Page Break",  //MISSING\r
+\r
+Form                   : "Form",       //MISSING\r
+Checkbox               : "Checkbox",   //MISSING\r
+RadioButton            : "Radio Button",       //MISSING\r
+TextField              : "Text Field", //MISSING\r
+Textarea               : "Textarea",   //MISSING\r
+HiddenField            : "Hidden Field",       //MISSING\r
+Button                 : "Button",     //MISSING\r
+SelectionField : "Selection Field",    //MISSING\r
+ImageButton            : "Image Button",       //MISSING\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Izmjeni link",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Ubaci red",\r
+DeleteRows                     : "Briši redove",\r
+InsertColumn           : "Ubaci kolonu",\r
+DeleteColumns          : "Briši kolone",\r
+InsertCell                     : "Ubaci æeliju",\r
+DeleteCells                    : "Briši æelije",\r
+MergeCells                     : "Spoji æelije",\r
+SplitCell                      : "Razdvoji æeliju",\r
+TableDelete                    : "Delete Table",       //MISSING\r
+CellProperties         : "Svojstva æelije",\r
+TableProperties                : "Svojstva tabele",\r
+ImageProperties                : "Svojstva slike",\r
+FlashProperties                : "Flash Properties",   //MISSING\r
+\r
+AnchorProp                     : "Anchor Properties",  //MISSING\r
+ButtonProp                     : "Button Properties",  //MISSING\r
+CheckboxProp           : "Checkbox Properties",        //MISSING\r
+HiddenFieldProp                : "Hidden Field Properties",    //MISSING\r
+RadioButtonProp                : "Radio Button Properties",    //MISSING\r
+ImageButtonProp                : "Image Button Properties",    //MISSING\r
+TextFieldProp          : "Text Field Properties",      //MISSING\r
+SelectionFieldProp     : "Selection Field Properties", //MISSING\r
+TextareaProp           : "Textarea Properties",        //MISSING\r
+FormProp                       : "Form Properties",    //MISSING\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6",               //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Procesiram XHTML. Molim saèekajte...",\r
+Done                           : "Gotovo",\r
+PasteWordConfirm       : "Tekst koji želite zalijepiti èini se da je kopiran iz Worda. Da li želite da se prvo oèisti?",\r
+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?",\r
+UnknownToolbarItem     : "Nepoznata stavka sa trake sa alatima \"%1\"",\r
+UnknownCommand         : "Nepoznata komanda \"%1\"",\r
+NotImplemented         : "Komanda nije implementirana",\r
+UnknownToolbarSet      : "Traka sa alatima \"%1\" ne postoji",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Odustani",\r
+DlgBtnClose                    : "Zatvori",\r
+DlgBtnBrowseServer     : "Browse Server",      //MISSING\r
+DlgAdvancedTag         : "Naprednije",\r
+DlgOpOther                     : "<Other>",    //MISSING\r
+DlgInfoTab                     : "Info",       //MISSING\r
+DlgAlertUrl                    : "Please insert the URL",      //MISSING\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nije podešeno>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Smjer pisanja",\r
+DlgGenLangDirLtr       : "S lijeva na desno (LTR)",\r
+DlgGenLangDirRtl       : "S desna na lijevo (RTL)",\r
+DlgGenLangCode         : "Jezièni kôd",\r
+DlgGenAccessKey                : "Pristupna tipka",\r
+DlgGenName                     : "Naziv",\r
+DlgGenTabIndex         : "Tab indeks",\r
+DlgGenLongDescr                : "Dugaèki opis URL-a",\r
+DlgGenClass                    : "Klase CSS stilova",\r
+DlgGenTitle                    : "Advisory title",\r
+DlgGenContType         : "Advisory vrsta sadržaja",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Stil",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Svojstva slike",\r
+DlgImgInfoTab          : "Info slike",\r
+DlgImgBtnUpload                : "Šalji na server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Šalji",\r
+DlgImgAlt                      : "Tekst na slici",\r
+DlgImgWidth                    : "Širina",\r
+DlgImgHeight           : "Visina",\r
+DlgImgLockRatio                : "Zakljuèaj odnos",\r
+DlgBtnResetSize                : "Resetuj dimenzije",\r
+DlgImgBorder           : "Okvir",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Poravnanje",\r
+DlgImgAlignLeft                : "Lijevo",\r
+DlgImgAlignAbsBottom: "Abs dole",\r
+DlgImgAlignAbsMiddle: "Abs sredina",\r
+DlgImgAlignBaseline    : "Bazno",\r
+DlgImgAlignBottom      : "Dno",\r
+DlgImgAlignMiddle      : "Sredina",\r
+DlgImgAlignRight       : "Desno",\r
+DlgImgAlignTextTop     : "Vrh teksta",\r
+DlgImgAlignTop         : "Vrh",\r
+DlgImgPreview          : "Prikaz",\r
+DlgImgAlertUrl         : "Molimo ukucajte URL od slike.",\r
+DlgImgLinkTab          : "Link",       //MISSING\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Properties",   //MISSING\r
+DlgFlashChkPlay                : "Auto Play",  //MISSING\r
+DlgFlashChkLoop                : "Loop",       //MISSING\r
+DlgFlashChkMenu                : "Enable Flash Menu",  //MISSING\r
+DlgFlashScale          : "Scale",      //MISSING\r
+DlgFlashScaleAll       : "Show all",   //MISSING\r
+DlgFlashScaleNoBorder  : "No Border",  //MISSING\r
+DlgFlashScaleFit       : "Exact Fit",  //MISSING\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Link info",\r
+DlgLnkTargetTab                : "Prozor",\r
+\r
+DlgLnkType                     : "Tip linka",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Sidro na ovoj stranici",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<drugi>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Izaberi sidro",\r
+DlgLnkAnchorByName     : "Po nazivu sidra",\r
+DlgLnkAnchorById       : "Po Id-u elementa",\r
+DlgLnkNoAnchors                : "<Nema dostupnih sidra na stranici>",         //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail Adresa",\r
+DlgLnkEMailSubject     : "Subjekt poruke",\r
+DlgLnkEMailBody                : "Poruka",\r
+DlgLnkUpload           : "Šalji",\r
+DlgLnkBtnUpload                : "Šalji na server",\r
+\r
+DlgLnkTarget           : "Prozor",\r
+DlgLnkTargetFrame      : "<frejm>",\r
+DlgLnkTargetPopup      : "<popup prozor>",\r
+DlgLnkTargetBlank      : "Novi prozor (_blank)",\r
+DlgLnkTargetParent     : "Glavni prozor (_parent)",\r
+DlgLnkTargetSelf       : "Isti prozor (_self)",\r
+DlgLnkTargetTop                : "Najgornji prozor (_top)",\r
+DlgLnkTargetFrameName  : "Target Frame Name",  //MISSING\r
+DlgLnkPopWinName       : "Naziv popup prozora",\r
+DlgLnkPopWinFeat       : "Moguænosti popup prozora",\r
+DlgLnkPopResize                : "Promjenljive velièine",\r
+DlgLnkPopLocation      : "Traka za lokaciju",\r
+DlgLnkPopMenu          : "Izborna traka",\r
+DlgLnkPopScroll                : "Scroll traka",\r
+DlgLnkPopStatus                : "Statusna traka",\r
+DlgLnkPopToolbar       : "Traka sa alatima",\r
+DlgLnkPopFullScrn      : "Cijeli ekran (IE)",\r
+DlgLnkPopDependent     : "Ovisno (Netscape)",\r
+DlgLnkPopWidth         : "Širina",\r
+DlgLnkPopHeight                : "Visina",\r
+DlgLnkPopLeft          : "Lijeva pozicija",\r
+DlgLnkPopTop           : "Gornja pozicija",\r
+\r
+DlnLnkMsgNoUrl         : "Molimo ukucajte URL link",\r
+DlnLnkMsgNoEMail       : "Molimo ukucajte e-mail adresu",\r
+DlnLnkMsgNoAnchor      : "Molimo izaberite sidro",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Izaberi boju",\r
+DlgColorBtnClear       : "Oèisti",\r
+DlgColorHighlight      : "Igled",\r
+DlgColorSelected       : "Selektovana",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Ubaci smješka",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Izaberi specijalni karakter",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Svojstva tabele",\r
+DlgTableRows           : "Redova",\r
+DlgTableColumns                : "Kolona",\r
+DlgTableBorder         : "Okvir",\r
+DlgTableAlign          : "Poravnanje",\r
+DlgTableAlignNotSet    : "<Nije podešeno>",\r
+DlgTableAlignLeft      : "Lijevo",\r
+DlgTableAlignCenter    : "Centar",\r
+DlgTableAlignRight     : "Desno",\r
+DlgTableWidth          : "Širina",\r
+DlgTableWidthPx                : "piksela",\r
+DlgTableWidthPc                : "posto",\r
+DlgTableHeight         : "Visina",\r
+DlgTableCellSpace      : "Razmak æelija",\r
+DlgTableCellPad                : "Uvod æelija",\r
+DlgTableCaption                : "Naslov",\r
+DlgTableSummary                : "Summary",    //MISSING\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Svojstva æelije",\r
+DlgCellWidth           : "Širina",\r
+DlgCellWidthPx         : "piksela",\r
+DlgCellWidthPc         : "posto",\r
+DlgCellHeight          : "Visina",\r
+DlgCellWordWrap                : "Vrapuj tekst",\r
+DlgCellWordWrapNotSet  : "<Nije podešeno>",\r
+DlgCellWordWrapYes     : "Da",\r
+DlgCellWordWrapNo      : "Ne",\r
+DlgCellHorAlign                : "Horizontalno poravnanje",\r
+DlgCellHorAlignNotSet  : "<Nije podešeno>",\r
+DlgCellHorAlignLeft    : "Lijevo",\r
+DlgCellHorAlignCenter  : "Centar",\r
+DlgCellHorAlignRight: "Desno",\r
+DlgCellVerAlign                : "Vertikalno poravnanje",\r
+DlgCellVerAlignNotSet  : "<Nije podešeno>",\r
+DlgCellVerAlignTop     : "Gore",\r
+DlgCellVerAlignMiddle  : "Sredina",\r
+DlgCellVerAlignBottom  : "Dno",\r
+DlgCellVerAlignBaseline        : "Bazno",\r
+DlgCellRowSpan         : "Spajanje æelija",\r
+DlgCellCollSpan                : "Spajanje kolona",\r
+DlgCellBackColor       : "Boja pozadine",\r
+DlgCellBorderColor     : "Boja okvira",\r
+DlgCellBtnSelect       : "Selektuj...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Naði",\r
+DlgFindFindBtn         : "Naði",\r
+DlgFindNotFoundMsg     : "Traženi tekst nije pronaðen.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Zamjeni",\r
+DlgReplaceFindLbl              : "Naði šta:",\r
+DlgReplaceReplaceLbl   : "Zamjeni sa:",\r
+DlgReplaceCaseChk              : "Uporeðuj velika/mala slova",\r
+DlgReplaceReplaceBtn   : "Zamjeni",\r
+DlgReplaceReplAllBtn   : "Zamjeni sve",\r
+DlgReplaceWordChk              : "Uporeðuj samo cijelu rijeè",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Sigurnosne postavke vašeg pretraživaèa ne dozvoljavaju operacije automatskog rezanja. Molimo koristite kraticu na tastaturi (Ctrl+X).",\r
+PasteErrorCopy : "Sigurnosne postavke Vašeg pretraživaèa ne dozvoljavaju operacije automatskog kopiranja. Molimo koristite kraticu na tastaturi (Ctrl+C).",\r
+\r
+PasteAsText            : "Zalijepi kao obièan tekst",\r
+PasteFromWord  : "Zalijepi iz Word-a",\r
+\r
+DlgPasteMsg2   : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",    //MISSING\r
+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\r
+DlgPasteIgnoreFont             : "Ignore Font Face definitions",       //MISSING\r
+DlgPasteRemoveStyles   : "Remove Styles definitions",  //MISSING\r
+DlgPasteCleanBox               : "Clean Up Box",       //MISSING\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatska",\r
+ColorMoreColors        : "Više boja...",\r
+\r
+// Document Properties\r
+DocProps               : "Document Properties",        //MISSING\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Anchor Properties",  //MISSING\r
+DlgAnchorName          : "Anchor Name",        //MISSING\r
+DlgAnchorErrorName     : "Please type the anchor name",        //MISSING\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Not in dictionary",  //MISSING\r
+DlgSpellChangeTo               : "Change to",  //MISSING\r
+DlgSpellBtnIgnore              : "Ignore",     //MISSING\r
+DlgSpellBtnIgnoreAll   : "Ignore All", //MISSING\r
+DlgSpellBtnReplace             : "Replace",    //MISSING\r
+DlgSpellBtnReplaceAll  : "Replace All",        //MISSING\r
+DlgSpellBtnUndo                        : "Undo",       //MISSING\r
+DlgSpellNoSuggestions  : "- No suggestions -", //MISSING\r
+DlgSpellProgress               : "Spell check in progress...", //MISSING\r
+DlgSpellNoMispell              : "Spell check complete: No misspellings found",        //MISSING\r
+DlgSpellNoChanges              : "Spell check complete: No words changed",     //MISSING\r
+DlgSpellOneChange              : "Spell check complete: One word changed",     //MISSING\r
+DlgSpellManyChanges            : "Spell check complete: %1 words changed",     //MISSING\r
+\r
+IeSpellDownload                        : "Spell checker not installed. Do you want to download it now?",       //MISSING\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Value)",       //MISSING\r
+DlgButtonType          : "Type",       //MISSING\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Name",       //MISSING\r
+DlgCheckboxValue       : "Value",      //MISSING\r
+DlgCheckboxSelected    : "Selected",   //MISSING\r
+\r
+// Form Dialog\r
+DlgFormName            : "Name",       //MISSING\r
+DlgFormAction  : "Action",     //MISSING\r
+DlgFormMethod  : "Method",     //MISSING\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Name",       //MISSING\r
+DlgSelectValue         : "Value",      //MISSING\r
+DlgSelectSize          : "Size",       //MISSING\r
+DlgSelectLines         : "lines",      //MISSING\r
+DlgSelectChkMulti      : "Allow multiple selections",  //MISSING\r
+DlgSelectOpAvail       : "Available Options",  //MISSING\r
+DlgSelectOpText                : "Text",       //MISSING\r
+DlgSelectOpValue       : "Value",      //MISSING\r
+DlgSelectBtnAdd                : "Add",        //MISSING\r
+DlgSelectBtnModify     : "Modify",     //MISSING\r
+DlgSelectBtnUp         : "Up", //MISSING\r
+DlgSelectBtnDown       : "Down",       //MISSING\r
+DlgSelectBtnSetValue : "Set as selected value",        //MISSING\r
+DlgSelectBtnDelete     : "Delete",     //MISSING\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Name",       //MISSING\r
+DlgTextareaCols        : "Columns",    //MISSING\r
+DlgTextareaRows        : "Rows",       //MISSING\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Name",       //MISSING\r
+DlgTextValue           : "Value",      //MISSING\r
+DlgTextCharWidth       : "Character Width",    //MISSING\r
+DlgTextMaxChars                : "Maximum Characters", //MISSING\r
+DlgTextType                    : "Type",       //MISSING\r
+DlgTextTypeText                : "Text",       //MISSING\r
+DlgTextTypePass                : "Password",   //MISSING\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Name",       //MISSING\r
+DlgHiddenValue : "Value",      //MISSING\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Bulleted List Properties",   //MISSING\r
+NumberedListProp       : "Numbered List Properties",   //MISSING\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Type",       //MISSING\r
+DlgLstTypeCircle       : "Circle",     //MISSING\r
+DlgLstTypeDisc         : "Disc",       //MISSING\r
+DlgLstTypeSquare       : "Square",     //MISSING\r
+DlgLstTypeNumbers      : "Numbers (1, 2, 3)",  //MISSING\r
+DlgLstTypeLCase                : "Lowercase Letters (a, b, c)",        //MISSING\r
+DlgLstTypeUCase                : "Uppercase Letters (A, B, C)",        //MISSING\r
+DlgLstTypeSRoman       : "Small Roman Numerals (i, ii, iii)",  //MISSING\r
+DlgLstTypeLRoman       : "Large Roman Numerals (I, II, III)",  //MISSING\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",    //MISSING\r
+DlgDocBackTab          : "Background", //MISSING\r
+DlgDocColorsTab                : "Colors and Margins", //MISSING\r
+DlgDocMetaTab          : "Meta Data",  //MISSING\r
+\r
+DlgDocPageTitle                : "Page Title", //MISSING\r
+DlgDocLangDir          : "Language Direction", //MISSING\r
+DlgDocLangDirLTR       : "Left to Right (LTR)",        //MISSING\r
+DlgDocLangDirRTL       : "Right to Left (RTL)",        //MISSING\r
+DlgDocLangCode         : "Language Code",      //MISSING\r
+DlgDocCharSet          : "Character Set Encoding",     //MISSING\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Other Character Set Encoding",       //MISSING\r
+\r
+DlgDocDocType          : "Document Type Heading",      //MISSING\r
+DlgDocDocTypeOther     : "Other Document Type Heading",        //MISSING\r
+DlgDocIncXHTML         : "Include XHTML Declarations", //MISSING\r
+DlgDocBgColor          : "Background Color",   //MISSING\r
+DlgDocBgImage          : "Background Image URL",       //MISSING\r
+DlgDocBgNoScroll       : "Nonscrolling Background",    //MISSING\r
+DlgDocCText                    : "Text",       //MISSING\r
+DlgDocCLink                    : "Link",       //MISSING\r
+DlgDocCVisited         : "Visited Link",       //MISSING\r
+DlgDocCActive          : "Active Link",        //MISSING\r
+DlgDocMargins          : "Page Margins",       //MISSING\r
+DlgDocMaTop                    : "Top",        //MISSING\r
+DlgDocMaLeft           : "Left",       //MISSING\r
+DlgDocMaRight          : "Right",      //MISSING\r
+DlgDocMaBottom         : "Bottom",     //MISSING\r
+DlgDocMeIndex          : "Document Indexing Keywords (comma separated)",       //MISSING\r
+DlgDocMeDescr          : "Document Description",       //MISSING\r
+DlgDocMeAuthor         : "Author",     //MISSING\r
+DlgDocMeCopy           : "Copyright",  //MISSING\r
+DlgDocPreview          : "Preview",    //MISSING\r
+\r
+// Templates Dialog\r
+Templates                      : "Templates",  //MISSING\r
+DlgTemplatesTitle      : "Content Templates",  //MISSING\r
+DlgTemplatesSelMsg     : "Please select the template to open in the editor<br />(the actual contents will be lost):",  //MISSING\r
+DlgTemplatesLoading    : "Loading templates list. Please wait...",     //MISSING\r
+DlgTemplatesNoTpl      : "(No templates defined)",     //MISSING\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "About",      //MISSING\r
+DlgAboutBrowserInfoTab : "Browser Info",       //MISSING\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "verzija",\r
+DlgAboutInfo           : "Za više informacija posjetite"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/ca.js b/httemplate/elements/fckeditor/editor/lang/ca.js
new file mode 100644 (file)
index 0000000..c3859cd
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Catalan language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Col·lapsa la barra",\r
+ToolbarExpand          : "Amplia la barra",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Desa",\r
+NewPage                                : "Nova Pàgina",\r
+Preview                                : "Vista Prèvia",\r
+Cut                                    : "Retalla",\r
+Copy                           : "Copia",\r
+Paste                          : "Enganxa",\r
+PasteText                      : "Enganxa com a text no formatat",\r
+PasteWord                      : "Enganxa des del Word",\r
+Print                          : "Imprimeix",\r
+SelectAll                      : "Selecciona-ho tot",\r
+RemoveFormat           : "Elimina Format",\r
+InsertLinkLbl          : "Enllaç",\r
+InsertLink                     : "Insereix/Edita enllaç",\r
+RemoveLink                     : "Elimina enllaç",\r
+Anchor                         : "Insereix/Edita àncora",\r
+InsertImageLbl         : "Imatge",\r
+InsertImage                    : "Insereix/Edita imatge",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Insereix/Edita Flash",\r
+InsertTableLbl         : "Taula",\r
+InsertTable                    : "Insereix/Edita taula",\r
+InsertLineLbl          : "Línia",\r
+InsertLine                     : "Insereix línia horitzontal",\r
+InsertSpecialCharLbl: "Caràcter Especial",\r
+InsertSpecialChar      : "Insereix caràcter especial",\r
+InsertSmileyLbl                : "Icona",\r
+InsertSmiley           : "Insereix icona",\r
+About                          : "Quant a FCKeditor",\r
+Bold                           : "Negreta",\r
+Italic                         : "Cursiva",\r
+Underline                      : "Subratllat",\r
+StrikeThrough          : "Barrat",\r
+Subscript                      : "Subíndex",\r
+Superscript                    : "Superíndex",\r
+LeftJustify                    : "Aliniament esquerra",\r
+CenterJustify          : "Aliniament centrat",\r
+RightJustify           : "Aliniament dreta",\r
+BlockJustify           : "Justifica",\r
+DecreaseIndent         : "Sagna el text",\r
+IncreaseIndent         : "Treu el sagnat del text",\r
+Undo                           : "Desfés",\r
+Redo                           : "Refés",\r
+NumberedListLbl                : "Llista numerada",\r
+NumberedList           : "Aplica o elimina la llista numerada",\r
+BulletedListLbl                : "Llista de pics",\r
+BulletedList           : "Aplica o elimina la llista de pics",\r
+ShowTableBorders       : "Mostra les vores de les taules",\r
+ShowDetails                    : "Mostra detalls",\r
+Style                          : "Estil",\r
+FontFormat                     : "Format",\r
+Font                           : "Tipus de lletra",\r
+FontSize                       : "Mida",\r
+TextColor                      : "Color de Text",\r
+BGColor                                : "Color de Fons",\r
+Source                         : "Codi font",\r
+Find                           : "Cerca",\r
+Replace                                : "Reemplaça",\r
+SpellCheck                     : "Revisa l'ortografia",\r
+UniversalKeyboard      : "Teclat universal",\r
+PageBreakLbl           : "Salt de pàgina",\r
+PageBreak                      : "Insereix salt de pàgina",\r
+\r
+Form                   : "Formulari",\r
+Checkbox               : "Casella de verificació",\r
+RadioButton            : "Botó d'opció",\r
+TextField              : "Camp de text",\r
+Textarea               : "Àrea de text",\r
+HiddenField            : "Camp ocult",\r
+Button                 : "Botó",\r
+SelectionField : "Camp de selecció",\r
+ImageButton            : "Botó d'imatge",\r
+\r
+FitWindow              : "Maximiza la mida de l'editor",\r
+\r
+// Context Menu\r
+EditLink                       : "Edita l'enllaç",\r
+CellCM                         : "Cel·la",\r
+RowCM                          : "Fila",\r
+ColumnCM                       : "Columna",\r
+InsertRow                      : "Insereix una fila",\r
+DeleteRows                     : "Suprimeix una fila",\r
+InsertColumn           : "Afegeix una columna",\r
+DeleteColumns          : "Suprimeix una columna",\r
+InsertCell                     : "Insereix una cel·la",\r
+DeleteCells                    : "Suprimeix les cel·les",\r
+MergeCells                     : "Fusiona les cel·les",\r
+SplitCell                      : "Separa les cel·les",\r
+TableDelete                    : "Suprimeix la taula",\r
+CellProperties         : "Propietats de la cel·la",\r
+TableProperties                : "Propietats de la taula",\r
+ImageProperties                : "Propietats de la imatge",\r
+FlashProperties                : "Propietats del Flash",\r
+\r
+AnchorProp                     : "Propietats de l'àncora",\r
+ButtonProp                     : "Propietats del botó",\r
+CheckboxProp           : "Propietats de la casella de verificació",\r
+HiddenFieldProp                : "Propietats del camp ocult",\r
+RadioButtonProp                : "Propietats del botó d'opció",\r
+ImageButtonProp                : "Propietats del botó d'imatge",\r
+TextFieldProp          : "Propietats del camp de text",\r
+SelectionFieldProp     : "Propietats del camp de selecció",\r
+TextareaProp           : "Propietats de l'àrea de text",\r
+FormProp                       : "Propietats del formulari",\r
+\r
+FontFormats                    : "Normal;Formatejat;Adreça;Encapçalament 1;Encapçalament 2;Encapçalament 3;Encapçalament 4;Encapçalament 5;Encapçalament 6",            //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Processant XHTML. Si us plau esperi...",\r
+Done                           : "Fet",\r
+PasteWordConfirm       : "El text que voleu enganxar sembla provenir de Word. Voleu netejar aquest text abans que sigui enganxat?",\r
+NotCompatiblePaste     : "Aquesta funció és disponible per a Internet Explorer versió 5.5 o superior. Voleu enganxar sense netejar?",\r
+UnknownToolbarItem     : "Element de la barra d'eines desconegut \"%1\"",\r
+UnknownCommand         : "Nom de comanda desconegut \"%1\"",\r
+NotImplemented         : "Mètode no implementat",\r
+UnknownToolbarSet      : "Conjunt de barra d'eines \"%1\" inexistent",\r
+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.",\r
+BrowseServerBlocked : "El visualitzador de recursos no s'ha pogut obrir. Assegura't de que els bloquejos de finestres emergents estan desactivats.",\r
+DialogBlocked          : "No ha estat possible obrir una finestra de diàleg. Assegura't de que els bloquejos de finestres emergents estan desactivats.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "D'acord",\r
+DlgBtnCancel           : "Cancel·la",\r
+DlgBtnClose                    : "Tanca",\r
+DlgBtnBrowseServer     : "Veure servidor",\r
+DlgAdvancedTag         : "Avançat",\r
+DlgOpOther                     : "Altres",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Si us plau, afegiu la URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<no definit>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Direcció de l'idioma",\r
+DlgGenLangDirLtr       : "D'esquerra a dreta (LTR)",\r
+DlgGenLangDirRtl       : "De dreta a esquerra (RTL)",\r
+DlgGenLangCode         : "Codi d'idioma",\r
+DlgGenAccessKey                : "Clau d'accés",\r
+DlgGenName                     : "Nom",\r
+DlgGenTabIndex         : "Index de Tab",\r
+DlgGenLongDescr                : "Descripció llarga de la URL",\r
+DlgGenClass                    : "Classes del full d'estil",\r
+DlgGenTitle                    : "Títol consultiu",\r
+DlgGenContType         : "Tipus de contingut consultiu",\r
+DlgGenLinkCharset      : "Conjunt de caràcters font enllaçat",\r
+DlgGenStyle                    : "Estil",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Propietats de la imatge",\r
+DlgImgInfoTab          : "Informació de la imatge",\r
+DlgImgBtnUpload                : "Envia-la al servidor",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Puja",\r
+DlgImgAlt                      : "Text alternatiu",\r
+DlgImgWidth                    : "Amplada",\r
+DlgImgHeight           : "Alçada",\r
+DlgImgLockRatio                : "Bloqueja les proporcions",\r
+DlgBtnResetSize                : "Restaura la mida",\r
+DlgImgBorder           : "Vora",\r
+DlgImgHSpace           : "Espaiat horit.",\r
+DlgImgVSpace           : "Espaiat vert.",\r
+DlgImgAlign                    : "Alineació",\r
+DlgImgAlignLeft                : "Ajusta a l'esquerra",\r
+DlgImgAlignAbsBottom: "Abs Bottom",\r
+DlgImgAlignAbsMiddle: "Abs Middle",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Bottom",\r
+DlgImgAlignMiddle      : "Middle",\r
+DlgImgAlignRight       : "Ajusta a la dreta",\r
+DlgImgAlignTextTop     : "Text Top",\r
+DlgImgAlignTop         : "Top",\r
+DlgImgPreview          : "Vista prèvia",\r
+DlgImgAlertUrl         : "Si us plau, escriviu la URL de la imatge",\r
+DlgImgLinkTab          : "Enllaç",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Propietats del Flash",\r
+DlgFlashChkPlay                : "Reprodució automàtica",\r
+DlgFlashChkLoop                : "Bucle",\r
+DlgFlashChkMenu                : "Habilita menú Flash",\r
+DlgFlashScale          : "Escala",\r
+DlgFlashScaleAll       : "Mostra-ho tot",\r
+DlgFlashScaleNoBorder  : "Sense vores",\r
+DlgFlashScaleFit       : "Mida exacta",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Enllaç",\r
+DlgLnkInfoTab          : "Informació de l'enllaç",\r
+DlgLnkTargetTab                : "Destí",\r
+\r
+DlgLnkType                     : "Tipus d'enllaç",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Àncora en aquesta pàgina",\r
+DlgLnkTypeEMail                : "Correu electrònic",\r
+DlgLnkProto                    : "Protocol",\r
+DlgLnkProtoOther       : "<altra>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Selecciona una àncora",\r
+DlgLnkAnchorByName     : "Per nom d'àncora",\r
+DlgLnkAnchorById       : "Per Id d'element",\r
+DlgLnkNoAnchors                : "(No hi ha àncores disponibles en aquest document)",         //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Adreça de correu electrònic",\r
+DlgLnkEMailSubject     : "Assumpte del missatge",\r
+DlgLnkEMailBody                : "Cos del missatge",\r
+DlgLnkUpload           : "Puja",\r
+DlgLnkBtnUpload                : "Envia al servidor",\r
+\r
+DlgLnkTarget           : "Destí",\r
+DlgLnkTargetFrame      : "<marc>",\r
+DlgLnkTargetPopup      : "<finestra emergent>",\r
+DlgLnkTargetBlank      : "Nova finestra (_blank)",\r
+DlgLnkTargetParent     : "Finestra pare (_parent)",\r
+DlgLnkTargetSelf       : "Mateixa finestra (_self)",\r
+DlgLnkTargetTop                : "Finestra Major (_top)",\r
+DlgLnkTargetFrameName  : "Nom del marc de destí",\r
+DlgLnkPopWinName       : "Nom finestra popup",\r
+DlgLnkPopWinFeat       : "Característiques finestra popup",\r
+DlgLnkPopResize                : "Redimensionable",\r
+DlgLnkPopLocation      : "Barra d'adreça",\r
+DlgLnkPopMenu          : "Barra de menú",\r
+DlgLnkPopScroll                : "Barres d'scroll",\r
+DlgLnkPopStatus                : "Barra d'estat",\r
+DlgLnkPopToolbar       : "Barra d'eines",\r
+DlgLnkPopFullScrn      : "Pantalla completa (IE)",\r
+DlgLnkPopDependent     : "Depenent (Netscape)",\r
+DlgLnkPopWidth         : "Amplada",\r
+DlgLnkPopHeight                : "Alçada",\r
+DlgLnkPopLeft          : "Posició esquerra",\r
+DlgLnkPopTop           : "Posició dalt",\r
+\r
+DlnLnkMsgNoUrl         : "Si us plau, escrigui l'enllaç URL",\r
+DlnLnkMsgNoEMail       : "Si us plau, escrigui l'adreça correu electrònic",\r
+DlnLnkMsgNoAnchor      : "Si us plau, escrigui l'àncora",\r
+DlnLnkMsgInvPopName    : "El nom de la finestra emergent ha de començar amb una lletra i no pot tenir espais",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Selecciona el color",\r
+DlgColorBtnClear       : "Neteja",\r
+DlgColorHighlight      : "Realça",\r
+DlgColorSelected       : "Selecciona",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Insereix una icona",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Selecciona el caràcter especial",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Propietats de la taula",\r
+DlgTableRows           : "Files",\r
+DlgTableColumns                : "Columnes",\r
+DlgTableBorder         : "Mida vora",\r
+DlgTableAlign          : "Alineació",\r
+DlgTableAlignNotSet    : "<No Definit>",\r
+DlgTableAlignLeft      : "Esquerra",\r
+DlgTableAlignCenter    : "Centre",\r
+DlgTableAlignRight     : "Dreta",\r
+DlgTableWidth          : "Amplada",\r
+DlgTableWidthPx                : "píxels",\r
+DlgTableWidthPc                : "percentatge",\r
+DlgTableHeight         : "Alçada",\r
+DlgTableCellSpace      : "Espaiat de cel·les",\r
+DlgTableCellPad                : "Encoixinament de cel·les",\r
+DlgTableCaption                : "Títol",\r
+DlgTableSummary                : "Resum",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Propietats de la cel·la",\r
+DlgCellWidth           : "Amplada",\r
+DlgCellWidthPx         : "píxels",\r
+DlgCellWidthPc         : "percentatge",\r
+DlgCellHeight          : "Alçada",\r
+DlgCellWordWrap                : "Ajust de paraula",\r
+DlgCellWordWrapNotSet  : "<No Definit>",\r
+DlgCellWordWrapYes     : "Si",\r
+DlgCellWordWrapNo      : "No",\r
+DlgCellHorAlign                : "Alineació horitzontal",\r
+DlgCellHorAlignNotSet  : "<No Definit>",\r
+DlgCellHorAlignLeft    : "Esquerra",\r
+DlgCellHorAlignCenter  : "Centre",\r
+DlgCellHorAlignRight: "Dreta",\r
+DlgCellVerAlign                : "Alineació vertical",\r
+DlgCellVerAlignNotSet  : "<No definit>",\r
+DlgCellVerAlignTop     : "Top",\r
+DlgCellVerAlignMiddle  : "Middle",\r
+DlgCellVerAlignBottom  : "Bottom",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Rows Span",\r
+DlgCellCollSpan                : "Columns Span",\r
+DlgCellBackColor       : "Color de fons",\r
+DlgCellBorderColor     : "Color de la vora",\r
+DlgCellBtnSelect       : "Seleccioneu...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Cerca",\r
+DlgFindFindBtn         : "Cerca",\r
+DlgFindNotFoundMsg     : "El text especificat no s'ha trobat.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Reemplaça",\r
+DlgReplaceFindLbl              : "Cerca:",\r
+DlgReplaceReplaceLbl   : "Remplaça amb:",\r
+DlgReplaceCaseChk              : "Distingeix majúscules/minúscules",\r
+DlgReplaceReplaceBtn   : "Reemplaça",\r
+DlgReplaceReplAllBtn   : "Reemplaça-ho tot",\r
+DlgReplaceWordChk              : "Només paraules completes",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "La seguretat del vostre navegador no permet executar automàticament les operacions de retallar. Si us plau, utilitzeu el teclat (Ctrl+X).",\r
+PasteErrorCopy : "La seguretat del vostre navegador no permet executar automàticament les operacions de copiar. Si us plau, utilitzeu el teclat (Ctrl+C).",\r
+\r
+PasteAsText            : "Enganxa com a text no formatat",\r
+PasteFromWord  : "Enganxa com a Word",\r
+\r
+DlgPasteMsg2   : "Si us plau, enganxeu dins del següent camp utilitzant el teclat (<STRONG>Ctrl+V</STRONG>) i premeu <STRONG>OK</STRONG>.",\r
+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.",\r
+DlgPasteIgnoreFont             : "Ignora definicions de font",\r
+DlgPasteRemoveStyles   : "Elimina definicions d'estil",\r
+DlgPasteCleanBox               : "Neteja camp",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automàtic",\r
+ColorMoreColors        : "Més colors...",\r
+\r
+// Document Properties\r
+DocProps               : "Propietats del document",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Propietats de l'àncora",\r
+DlgAnchorName          : "Nom de l'àncora",\r
+DlgAnchorErrorName     : "Si us plau, escriviu el nom de l'ancora",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "No és al diccionari",\r
+DlgSpellChangeTo               : "Canvia a",\r
+DlgSpellBtnIgnore              : "Ignora",\r
+DlgSpellBtnIgnoreAll   : "Ignora-les totes",\r
+DlgSpellBtnReplace             : "Canvia",\r
+DlgSpellBtnReplaceAll  : "Canvia-les totes",\r
+DlgSpellBtnUndo                        : "Desfés",\r
+DlgSpellNoSuggestions  : "Cap sugerència",\r
+DlgSpellProgress               : "Comprovació ortogràfica en progrés",\r
+DlgSpellNoMispell              : "Comprovació ortogràfica completada",\r
+DlgSpellNoChanges              : "Comprovació ortogràfica: cap paraulada canviada",\r
+DlgSpellOneChange              : "Comprovació ortogràfica: una paraula canviada",\r
+DlgSpellManyChanges            : "Comprovació ortogràfica %1 paraules canviades",\r
+\r
+IeSpellDownload                        : "Comprovació ortogràfica no instal·lada. Voleu descarregar-ho ara?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Valor)",\r
+DlgButtonType          : "Tipus",\r
+DlgButtonTypeBtn       : "Botó",\r
+DlgButtonTypeSbm       : "Transmet formulari",\r
+DlgButtonTypeRst       : "Reinicia formulari",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nom",\r
+DlgCheckboxValue       : "Valor",\r
+DlgCheckboxSelected    : "Seleccionat",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nom",\r
+DlgFormAction  : "Acció",\r
+DlgFormMethod  : "Mètode",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nom",\r
+DlgSelectValue         : "Valor",\r
+DlgSelectSize          : "Mida",\r
+DlgSelectLines         : "Línies",\r
+DlgSelectChkMulti      : "Permet múltiples seleccions",\r
+DlgSelectOpAvail       : "Opcions disponibles",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Valor",\r
+DlgSelectBtnAdd                : "Afegeix",\r
+DlgSelectBtnModify     : "Modifica",\r
+DlgSelectBtnUp         : "Amunt",\r
+DlgSelectBtnDown       : "Avall",\r
+DlgSelectBtnSetValue : "Selecciona per defecte",\r
+DlgSelectBtnDelete     : "Elimina",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nom",\r
+DlgTextareaCols        : "Columnes",\r
+DlgTextareaRows        : "Files",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nom",\r
+DlgTextValue           : "Valor",\r
+DlgTextCharWidth       : "Amplada de caràcter",\r
+DlgTextMaxChars                : "Màxim de caràcters",\r
+DlgTextType                    : "Tipus",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Contrasenya",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nom",\r
+DlgHiddenValue : "Valor",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Propietats de la llista de pics",\r
+NumberedListProp       : "Propietats de llista numerada",\r
+DlgLstStart                    : "Inici",\r
+DlgLstType                     : "Tipus",\r
+DlgLstTypeCircle       : "Cercle",\r
+DlgLstTypeDisc         : "Disc",\r
+DlgLstTypeSquare       : "Quadrat",\r
+DlgLstTypeNumbers      : "Números (1, 2, 3)",\r
+DlgLstTypeLCase                : "Lletres minúscules (a, b, c)",\r
+DlgLstTypeUCase                : "Lletres majúscules (A, B, C)",\r
+DlgLstTypeSRoman       : "Números romans minúscules (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Números romans majúscules (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",\r
+DlgDocBackTab          : "Fons",\r
+DlgDocColorsTab                : "Colors i marges",\r
+DlgDocMetaTab          : "Dades Meta",\r
+\r
+DlgDocPageTitle                : "Títol de la pàgina",\r
+DlgDocLangDir          : "Direcció idioma",\r
+DlgDocLangDirLTR       : "Esquerra a dreta (LTR)",\r
+DlgDocLangDirRTL       : "Dreta a esquerra (RTL)",\r
+DlgDocLangCode         : "Codi d'idioma",\r
+DlgDocCharSet          : "Codificació de conjunt de caràcters",\r
+DlgDocCharSetCE                : "Centreeuropeu",\r
+DlgDocCharSetCT                : "Xinès tradicional (Big5)",\r
+DlgDocCharSetCR                : "Ciríl·lic",\r
+DlgDocCharSetGR                : "Grec",\r
+DlgDocCharSetJP                : "Japonès",\r
+DlgDocCharSetKR                : "Coreà",\r
+DlgDocCharSetTR                : "Turc",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Europeu occidental",\r
+DlgDocCharSetOther     : "Una altra codificació de caràcters",\r
+\r
+DlgDocDocType          : "Capçalera de tipus de document",\r
+DlgDocDocTypeOther     : "Un altra capçalera de tipus de document",\r
+DlgDocIncXHTML         : "Incloure declaracions XHTML",\r
+DlgDocBgColor          : "Color de fons",\r
+DlgDocBgImage          : "URL de la imatge de fons",\r
+DlgDocBgNoScroll       : "Fons fixe",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Enllaç",\r
+DlgDocCVisited         : "Enllaç visitat",\r
+DlgDocCActive          : "Enllaç actiu",\r
+DlgDocMargins          : "Marges de pàgina",\r
+DlgDocMaTop                    : "Cap",\r
+DlgDocMaLeft           : "Esquerra",\r
+DlgDocMaRight          : "Dreta",\r
+DlgDocMaBottom         : "Peu",\r
+DlgDocMeIndex          : "Mots clau per a indexació (separats per coma)",\r
+DlgDocMeDescr          : "Descripció del document",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Vista prèvia",\r
+\r
+// Templates Dialog\r
+Templates                      : "Plantilles",\r
+DlgTemplatesTitle      : "Contingut plantilles",\r
+DlgTemplatesSelMsg     : "Si us plau, seleccioneu la plantilla per obrir en l'editor<br>(el contingut actual no serà enregistrat):",\r
+DlgTemplatesLoading    : "Carregant la llista de plantilles. Si us plau, espereu...",\r
+DlgTemplatesNoTpl      : "(No hi ha plantilles definides)",\r
+DlgTemplatesReplace    : "Reemplaça el contingut actual",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Quant a",\r
+DlgAboutBrowserInfoTab : "Informació del navegador",\r
+DlgAboutLicenseTab     : "Llicència",\r
+DlgAboutVersion                : "versió",\r
+DlgAboutInfo           : "Per a més informació aneu a"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/cs.js b/httemplate/elements/fckeditor/editor/lang/cs.js
new file mode 100644 (file)
index 0000000..49b5f87
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Czech language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Skrýt panel nástrojů",\r
+ToolbarExpand          : "Zobrazit panel nástrojů",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Uložit",\r
+NewPage                                : "Nová stránka",\r
+Preview                                : "Náhled",\r
+Cut                                    : "Vyjmout",\r
+Copy                           : "Kopírovat",\r
+Paste                          : "Vložit",\r
+PasteText                      : "Vložit jako čistý text",\r
+PasteWord                      : "Vložit z Wordu",\r
+Print                          : "Tisk",\r
+SelectAll                      : "Vybrat vše",\r
+RemoveFormat           : "Odstranit formátování",\r
+InsertLinkLbl          : "Odkaz",\r
+InsertLink                     : "Vložit/změnit odkaz",\r
+RemoveLink                     : "Odstranit odkaz",\r
+Anchor                         : "Vložít/změnit záložku",\r
+InsertImageLbl         : "Obrázek",\r
+InsertImage                    : "Vložit/změnit obrázek",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Vložit/Upravit Flash",\r
+InsertTableLbl         : "Tabulka",\r
+InsertTable                    : "Vložit/změnit tabulku",\r
+InsertLineLbl          : "Linka",\r
+InsertLine                     : "Vložit vodorovnou linku",\r
+InsertSpecialCharLbl: "Speciální znaky",\r
+InsertSpecialChar      : "Vložit speciální znaky",\r
+InsertSmileyLbl                : "Smajlíky",\r
+InsertSmiley           : "Vložit smajlík",\r
+About                          : "O aplikaci FCKeditor",\r
+Bold                           : "Tučné",\r
+Italic                         : "Kurzíva",\r
+Underline                      : "Podtržené",\r
+StrikeThrough          : "Přeškrtnuté",\r
+Subscript                      : "Dolní index",\r
+Superscript                    : "Horní index",\r
+LeftJustify                    : "Zarovnat vlevo",\r
+CenterJustify          : "Zarovnat na střed",\r
+RightJustify           : "Zarovnat vpravo",\r
+BlockJustify           : "Zarovnat do bloku",\r
+DecreaseIndent         : "Zmenšit odsazení",\r
+IncreaseIndent         : "Zvětšit odsazení",\r
+Undo                           : "Zpět",\r
+Redo                           : "Znovu",\r
+NumberedListLbl                : "Číslování",\r
+NumberedList           : "Vložit/odstranit číslovaný seznam",\r
+BulletedListLbl                : "Odrážky",\r
+BulletedList           : "Vložit/odstranit odrážky",\r
+ShowTableBorders       : "Zobrazit okraje tabulek",\r
+ShowDetails                    : "Zobrazit podrobnosti",\r
+Style                          : "Styl",\r
+FontFormat                     : "Formát",\r
+Font                           : "Písmo",\r
+FontSize                       : "Velikost",\r
+TextColor                      : "Barva textu",\r
+BGColor                                : "Barva pozadí",\r
+Source                         : "Zdroj",\r
+Find                           : "Hledat",\r
+Replace                                : "Nahradit",\r
+SpellCheck                     : "Zkontrolovat pravopis",\r
+UniversalKeyboard      : "Univerzální klávesnice",\r
+PageBreakLbl           : "Konec stránky",\r
+PageBreak                      : "Vložit konec stránky",\r
+\r
+Form                   : "Formulář",\r
+Checkbox               : "Zaškrtávací políčko",\r
+RadioButton            : "Přepínač",\r
+TextField              : "Textové pole",\r
+Textarea               : "Textová oblast",\r
+HiddenField            : "Skryté pole",\r
+Button                 : "Tlačítko",\r
+SelectionField : "Seznam",\r
+ImageButton            : "Obrázkové tlačítko",\r
+\r
+FitWindow              : "Maximalizovat velikost editoru",\r
+\r
+// Context Menu\r
+EditLink                       : "Změnit odkaz",\r
+CellCM                         : "Buňka",\r
+RowCM                          : "Řádek",\r
+ColumnCM                       : "Sloupec",\r
+InsertRow                      : "Vložit řádek",\r
+DeleteRows                     : "Smazat řádek",\r
+InsertColumn           : "Vložit sloupec",\r
+DeleteColumns          : "Smazat sloupec",\r
+InsertCell                     : "Vložit buňku",\r
+DeleteCells                    : "Smazat buňky",\r
+MergeCells                     : "Sloučit buňky",\r
+SplitCell                      : "Rozdělit buňku",\r
+TableDelete                    : "Smazat tabulku",\r
+CellProperties         : "Vlastnosti buňky",\r
+TableProperties                : "Vlastnosti tabulky",\r
+ImageProperties                : "Vlastnosti obrázku",\r
+FlashProperties                : "Vlastnosti Flashe",\r
+\r
+AnchorProp                     : "Vlastnosti záložky",\r
+ButtonProp                     : "Vlastnosti tlačítka",\r
+CheckboxProp           : "Vlastnosti zaškrtávacího políčka",\r
+HiddenFieldProp                : "Vlastnosti skrytého pole",\r
+RadioButtonProp                : "Vlastnosti přepínače",\r
+ImageButtonProp                : "Vlastností obrázkového tlačítka",\r
+TextFieldProp          : "Vlastnosti textového pole",\r
+SelectionFieldProp     : "Vlastnosti seznamu",\r
+TextareaProp           : "Vlastnosti textové oblasti",\r
+FormProp                       : "Vlastnosti formuláře",\r
+\r
+FontFormats                    : "Normální;Formátovaný;Adresa;Nadpis 1;Nadpis 2;Nadpis 3;Nadpis 4;Nadpis 5;Nadpis 6",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Probíhá zpracování XHTML. Prosím čekejte...",\r
+Done                           : "Hotovo",\r
+PasteWordConfirm       : "Jak je vidět, vkládaný text je kopírován z Wordu. Chcete jej před vložením vyčistit?",\r
+NotCompatiblePaste     : "Tento příkaz je dostupný pouze v Internet Exploreru verze 5.5 nebo vyšší. Chcete vložit text bez vyčištění?",\r
+UnknownToolbarItem     : "Neznámá položka panelu nástrojů \"%1\"",\r
+UnknownCommand         : "Neznámý příkaz \"%1\"",\r
+NotImplemented         : "Příkaz není implementován",\r
+UnknownToolbarSet      : "Panel nástrojů \"%1\" neexistuje",\r
+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.",\r
+BrowseServerBlocked : "Průzkumník zdrojů nelze otevřít. Prověřte, zda nemáte aktivováno blokování popup oken.",\r
+DialogBlocked          : "Nelze otevřít dialogové okno. Prověřte, zda nemáte aktivováno blokování popup oken.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Storno",\r
+DlgBtnClose                    : "Zavřít",\r
+DlgBtnBrowseServer     : "Vybrat na serveru",\r
+DlgAdvancedTag         : "Rozšířené",\r
+DlgOpOther                     : "<Ostatní>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Prosím vložte URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nenastaveno>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Orientace jazyka",\r
+DlgGenLangDirLtr       : "Zleva do prava (LTR)",\r
+DlgGenLangDirRtl       : "Zprava do leva (RTL)",\r
+DlgGenLangCode         : "Kód jazyka",\r
+DlgGenAccessKey                : "Přístupový klíč",\r
+DlgGenName                     : "Jméno",\r
+DlgGenTabIndex         : "Pořadí prvku",\r
+DlgGenLongDescr                : "Dlouhý popis URL",\r
+DlgGenClass                    : "Třída stylu",\r
+DlgGenTitle                    : "Pomocný titulek",\r
+DlgGenContType         : "Pomocný typ obsahu",\r
+DlgGenLinkCharset      : "Přiřazená znaková sada",\r
+DlgGenStyle                    : "Styl",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Vlastnosti obrázku",\r
+DlgImgInfoTab          : "Informace o obrázku",\r
+DlgImgBtnUpload                : "Odeslat na server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Odeslat",\r
+DlgImgAlt                      : "Alternativní text",\r
+DlgImgWidth                    : "Šířka",\r
+DlgImgHeight           : "Výška",\r
+DlgImgLockRatio                : "Zámek",\r
+DlgBtnResetSize                : "Původní velikost",\r
+DlgImgBorder           : "Okraje",\r
+DlgImgHSpace           : "H-mezera",\r
+DlgImgVSpace           : "V-mezera",\r
+DlgImgAlign                    : "Zarovnání",\r
+DlgImgAlignLeft                : "Vlevo",\r
+DlgImgAlignAbsBottom: "Zcela dolů",\r
+DlgImgAlignAbsMiddle: "Doprostřed",\r
+DlgImgAlignBaseline    : "Na účaří",\r
+DlgImgAlignBottom      : "Dolů",\r
+DlgImgAlignMiddle      : "Na střed",\r
+DlgImgAlignRight       : "Vpravo",\r
+DlgImgAlignTextTop     : "Na horní okraj textu",\r
+DlgImgAlignTop         : "Nahoru",\r
+DlgImgPreview          : "Náhled",\r
+DlgImgAlertUrl         : "Zadejte prosím URL obrázku",\r
+DlgImgLinkTab          : "Odkaz",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Vlastnosti Flashe",\r
+DlgFlashChkPlay                : "Automatické spuštění",\r
+DlgFlashChkLoop                : "Opakování",\r
+DlgFlashChkMenu                : "Nabídka Flash",\r
+DlgFlashScale          : "Zobrazit",\r
+DlgFlashScaleAll       : "Zobrazit vše",\r
+DlgFlashScaleNoBorder  : "Bez okraje",\r
+DlgFlashScaleFit       : "Přizpůsobit",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Odkaz",\r
+DlgLnkInfoTab          : "Informace o odkazu",\r
+DlgLnkTargetTab                : "Cíl",\r
+\r
+DlgLnkType                     : "Typ odkazu",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Kotva v této stránce",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<jiný>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Vybrat kotvu",\r
+DlgLnkAnchorByName     : "Podle jména kotvy",\r
+DlgLnkAnchorById       : "Podle Id objektu",\r
+DlgLnkNoAnchors                : "<Ve stránce žádná kotva není definována>",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mailová adresa",\r
+DlgLnkEMailSubject     : "Předmět zprávy",\r
+DlgLnkEMailBody                : "Tělo zprávy",\r
+DlgLnkUpload           : "Odeslat",\r
+DlgLnkBtnUpload                : "Odeslat na Server",\r
+\r
+DlgLnkTarget           : "Cíl",\r
+DlgLnkTargetFrame      : "<rámec>",\r
+DlgLnkTargetPopup      : "<vyskakovací okno>",\r
+DlgLnkTargetBlank      : "Nové okno (_blank)",\r
+DlgLnkTargetParent     : "Rodičovské okno (_parent)",\r
+DlgLnkTargetSelf       : "Stejné okno (_self)",\r
+DlgLnkTargetTop                : "Hlavní okno (_top)",\r
+DlgLnkTargetFrameName  : "Název cílového rámu",\r
+DlgLnkPopWinName       : "Název vyskakovacího okna",\r
+DlgLnkPopWinFeat       : "Vlastnosti vyskakovacího okna",\r
+DlgLnkPopResize                : "Měnitelná velikost",\r
+DlgLnkPopLocation      : "Panel umístění",\r
+DlgLnkPopMenu          : "Panel nabídky",\r
+DlgLnkPopScroll                : "Posuvníky",\r
+DlgLnkPopStatus                : "Stavový řádek",\r
+DlgLnkPopToolbar       : "Panel nástrojů",\r
+DlgLnkPopFullScrn      : "Celá obrazovka (IE)",\r
+DlgLnkPopDependent     : "Závislost (Netscape)",\r
+DlgLnkPopWidth         : "Šířka",\r
+DlgLnkPopHeight                : "Výška",\r
+DlgLnkPopLeft          : "Levý okraj",\r
+DlgLnkPopTop           : "Horní okraj",\r
+\r
+DlnLnkMsgNoUrl         : "Zadejte prosím URL odkazu",\r
+DlnLnkMsgNoEMail       : "Zadejte prosím e-mailovou adresu",\r
+DlnLnkMsgNoAnchor      : "Vyberte prosím kotvu",\r
+DlnLnkMsgInvPopName    : "Název vyskakovacího okna musí začínat písmenem a nesmí obsahovat mezery",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Výběr barvy",\r
+DlgColorBtnClear       : "Vymazat",\r
+DlgColorHighlight      : "Zvýrazněná",\r
+DlgColorSelected       : "Vybraná",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Vkládání smajlíků",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Výběr speciálního znaku",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Vlastnosti tabulky",\r
+DlgTableRows           : "Řádky",\r
+DlgTableColumns                : "Sloupce",\r
+DlgTableBorder         : "Ohraničení",\r
+DlgTableAlign          : "Zarovnání",\r
+DlgTableAlignNotSet    : "<nenastaveno>",\r
+DlgTableAlignLeft      : "Vlevo",\r
+DlgTableAlignCenter    : "Na střed",\r
+DlgTableAlignRight     : "Vpravo",\r
+DlgTableWidth          : "Šířka",\r
+DlgTableWidthPx                : "bodů",\r
+DlgTableWidthPc                : "procent",\r
+DlgTableHeight         : "Výška",\r
+DlgTableCellSpace      : "Vzdálenost buněk",\r
+DlgTableCellPad                : "Odsazení obsahu",\r
+DlgTableCaption                : "Popis",\r
+DlgTableSummary                : "Souhrn",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Vlastnosti buňky",\r
+DlgCellWidth           : "Šířka",\r
+DlgCellWidthPx         : "bodů",\r
+DlgCellWidthPc         : "procent",\r
+DlgCellHeight          : "Výška",\r
+DlgCellWordWrap                : "Zalamování",\r
+DlgCellWordWrapNotSet  : "<nenanstaveno>",\r
+DlgCellWordWrapYes     : "Ano",\r
+DlgCellWordWrapNo      : "Ne",\r
+DlgCellHorAlign                : "Vodorovné zarovnání",\r
+DlgCellHorAlignNotSet  : "<nenastaveno>",\r
+DlgCellHorAlignLeft    : "Vlevo",\r
+DlgCellHorAlignCenter  : "Na střed",\r
+DlgCellHorAlignRight: "Vpravo",\r
+DlgCellVerAlign                : "Svislé zarovnání",\r
+DlgCellVerAlignNotSet  : "<nenastaveno>",\r
+DlgCellVerAlignTop     : "Nahoru",\r
+DlgCellVerAlignMiddle  : "Doprostřed",\r
+DlgCellVerAlignBottom  : "Dolů",\r
+DlgCellVerAlignBaseline        : "Na účaří",\r
+DlgCellRowSpan         : "Sloučené řádky",\r
+DlgCellCollSpan                : "Sloučené sloupce",\r
+DlgCellBackColor       : "Barva pozadí",\r
+DlgCellBorderColor     : "Barva ohraničení",\r
+DlgCellBtnSelect       : "Výběr...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Hledat",\r
+DlgFindFindBtn         : "Hledat",\r
+DlgFindNotFoundMsg     : "Hledaný text nebyl nalezen.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Nahradit",\r
+DlgReplaceFindLbl              : "Co hledat:",\r
+DlgReplaceReplaceLbl   : "Čím nahradit:",\r
+DlgReplaceCaseChk              : "Rozlišovat velikost písma",\r
+DlgReplaceReplaceBtn   : "Nahradit",\r
+DlgReplaceReplAllBtn   : "Nahradit vše",\r
+DlgReplaceWordChk              : "Pouze celá slova",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Vložit jako čistý text",\r
+PasteFromWord  : "Vložit text z Wordu",\r
+\r
+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>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorovat písmo",\r
+DlgPasteRemoveStyles   : "Odstranit styly",\r
+DlgPasteCleanBox               : "Vyčistit",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automaticky",\r
+ColorMoreColors        : "Více barev...",\r
+\r
+// Document Properties\r
+DocProps               : "Vlastnosti dokumentu",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Vlastnosti záložky",\r
+DlgAnchorName          : "Název záložky",\r
+DlgAnchorErrorName     : "Zadejte prosím název záložky",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Není ve slovníku",\r
+DlgSpellChangeTo               : "Změnit na",\r
+DlgSpellBtnIgnore              : "Přeskočit",\r
+DlgSpellBtnIgnoreAll   : "Přeskakovat vše",\r
+DlgSpellBtnReplace             : "Zaměnit",\r
+DlgSpellBtnReplaceAll  : "Zaměňovat vše",\r
+DlgSpellBtnUndo                        : "Zpět",\r
+DlgSpellNoSuggestions  : "- žádné návrhy -",\r
+DlgSpellProgress               : "Probíhá kontrola pravopisu...",\r
+DlgSpellNoMispell              : "Kontrola pravopisu dokončena: Žádné pravopisné chyby nenalezeny",\r
+DlgSpellNoChanges              : "Kontrola pravopisu dokončena: Beze změn",\r
+DlgSpellOneChange              : "Kontrola pravopisu dokončena: Jedno slovo změněno",\r
+DlgSpellManyChanges            : "Kontrola pravopisu dokončena: %1 slov změněno",\r
+\r
+IeSpellDownload                        : "Kontrola pravopisu není nainstalována. Chcete ji nyní stáhnout?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Popisek",\r
+DlgButtonType          : "Typ",\r
+DlgButtonTypeBtn       : "Tlačítko",\r
+DlgButtonTypeSbm       : "Odeslat",\r
+DlgButtonTypeRst       : "Obnovit",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Název",\r
+DlgCheckboxValue       : "Hodnota",\r
+DlgCheckboxSelected    : "Zaškrtnuto",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Název",\r
+DlgFormAction  : "Akce",\r
+DlgFormMethod  : "Metoda",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Název",\r
+DlgSelectValue         : "Hodnota",\r
+DlgSelectSize          : "Velikost",\r
+DlgSelectLines         : "Řádků",\r
+DlgSelectChkMulti      : "Povolit mnohonásobné výběry",\r
+DlgSelectOpAvail       : "Dostupná nastavení",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Hodnota",\r
+DlgSelectBtnAdd                : "Přidat",\r
+DlgSelectBtnModify     : "Změnit",\r
+DlgSelectBtnUp         : "Nahoru",\r
+DlgSelectBtnDown       : "Dolů",\r
+DlgSelectBtnSetValue : "Nastavit jako vybranou hodnotu",\r
+DlgSelectBtnDelete     : "Smazat",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Název",\r
+DlgTextareaCols        : "Sloupců",\r
+DlgTextareaRows        : "Řádků",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Název",\r
+DlgTextValue           : "Hodnota",\r
+DlgTextCharWidth       : "Šířka ve znacích",\r
+DlgTextMaxChars                : "Maximální počet znaků",\r
+DlgTextType                    : "Typ",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Heslo",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Název",\r
+DlgHiddenValue : "Hodnota",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Vlastnosti odrážek",\r
+NumberedListProp       : "Vlastnosti číslovaného seznamu",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Typ",\r
+DlgLstTypeCircle       : "Kružnice",\r
+DlgLstTypeDisc         : "Kruh",\r
+DlgLstTypeSquare       : "Čtverec",\r
+DlgLstTypeNumbers      : "Čísla (1, 2, 3)",\r
+DlgLstTypeLCase                : "Malá písmena (a, b, c)",\r
+DlgLstTypeUCase                : "Velká písmena (A, B, C)",\r
+DlgLstTypeSRoman       : "Malé římská číslice (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Velké římské číslice (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Obecné",\r
+DlgDocBackTab          : "Pozadí",\r
+DlgDocColorsTab                : "Barvy a okraje",\r
+DlgDocMetaTab          : "Metadata",\r
+\r
+DlgDocPageTitle                : "Titulek stránky",\r
+DlgDocLangDir          : "Směr jazyku",\r
+DlgDocLangDirLTR       : "Zleva do prava ",\r
+DlgDocLangDirRTL       : "Zprava doleva",\r
+DlgDocLangCode         : "Kód jazyku",\r
+DlgDocCharSet          : "Znaková sada",\r
+DlgDocCharSetCE                : "Středoevropské jazyky",\r
+DlgDocCharSetCT                : "Tradiční čínština (Big5)",\r
+DlgDocCharSetCR                : "Cyrilice",\r
+DlgDocCharSetGR                : "Řečtina",\r
+DlgDocCharSetJP                : "Japonština",\r
+DlgDocCharSetKR                : "Korejština",\r
+DlgDocCharSetTR                : "Turečtina",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Západoevropské jazyky",\r
+DlgDocCharSetOther     : "Další znaková sada",\r
+\r
+DlgDocDocType          : "Typ dokumentu",\r
+DlgDocDocTypeOther     : "Jiný typ dokumetu",\r
+DlgDocIncXHTML         : "Zahrnou deklarace XHTML",\r
+DlgDocBgColor          : "Barva pozadí",\r
+DlgDocBgImage          : "URL obrázku na pozadí",\r
+DlgDocBgNoScroll       : "Nerolovatelné pozadí",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Odkaz",\r
+DlgDocCVisited         : "Navštívený odkaz",\r
+DlgDocCActive          : "Vybraný odkaz",\r
+DlgDocMargins          : "Okraje stránky",\r
+DlgDocMaTop                    : "Horní",\r
+DlgDocMaLeft           : "Levý",\r
+DlgDocMaRight          : "Pravý",\r
+DlgDocMaBottom         : "Dolní",\r
+DlgDocMeIndex          : "Klíčová slova (oddělená čárkou)",\r
+DlgDocMeDescr          : "Popis dokumentu",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Autorská práva",\r
+DlgDocPreview          : "Náhled",\r
+\r
+// Templates Dialog\r
+Templates                      : "Šablony",\r
+DlgTemplatesTitle      : "Šablony obsahu",\r
+DlgTemplatesSelMsg     : "Prosím zvolte šablonu pro otevření v editoru<br>(aktuální obsah editoru bude ztracen):",\r
+DlgTemplatesLoading    : "Nahrávám přeheld šablon. Prosím čekejte...",\r
+DlgTemplatesNoTpl      : "(Není definována žádná šablona)",\r
+DlgTemplatesReplace    : "Nahradit aktuální obsah",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "O aplikaci",\r
+DlgAboutBrowserInfoTab : "Informace o prohlížeči",\r
+DlgAboutLicenseTab     : "Licence",\r
+DlgAboutVersion                : "verze",\r
+DlgAboutInfo           : "Více informací získáte na"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/da.js b/httemplate/elements/fckeditor/editor/lang/da.js
new file mode 100644 (file)
index 0000000..8143241
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Danish language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Skjul værktøjslinier",\r
+ToolbarExpand          : "Vis værktøjslinier",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Gem",\r
+NewPage                                : "Ny side",\r
+Preview                                : "Vis eksempel",\r
+Cut                                    : "Klip",\r
+Copy                           : "Kopier",\r
+Paste                          : "Indsæt",\r
+PasteText                      : "Indsæt som ikke-formateret tekst",\r
+PasteWord                      : "Indsæt fra Word",\r
+Print                          : "Udskriv",\r
+SelectAll                      : "Vælg alt",\r
+RemoveFormat           : "Fjern formatering",\r
+InsertLinkLbl          : "Hyperlink",\r
+InsertLink                     : "Indsæt/rediger hyperlink",\r
+RemoveLink                     : "Fjern hyperlink",\r
+Anchor                         : "Indsæt/rediger bogmærke",\r
+InsertImageLbl         : "Indsæt billede",\r
+InsertImage                    : "Indsæt/rediger billede",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Indsæt/rediger Flash",\r
+InsertTableLbl         : "Table",\r
+InsertTable                    : "Indsæt/rediger tabel",\r
+InsertLineLbl          : "Linie",\r
+InsertLine                     : "Indsæt vandret linie",\r
+InsertSpecialCharLbl: "Symbol",\r
+InsertSpecialChar      : "Indsæt symbol",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Indsæt smiley",\r
+About                          : "Om FCKeditor",\r
+Bold                           : "Fed",\r
+Italic                         : "Kursiv",\r
+Underline                      : "Understreget",\r
+StrikeThrough          : "Overstreget",\r
+Subscript                      : "Sænket skrift",\r
+Superscript                    : "Hævet skrift",\r
+LeftJustify                    : "Venstrestillet",\r
+CenterJustify          : "Centreret",\r
+RightJustify           : "Højrestillet",\r
+BlockJustify           : "Lige margener",\r
+DecreaseIndent         : "Formindsk indrykning",\r
+IncreaseIndent         : "Forøg indrykning",\r
+Undo                           : "Fortryd",\r
+Redo                           : "Annuller fortryd",\r
+NumberedListLbl                : "Talopstilling",\r
+NumberedList           : "Indsæt/fjern talopstilling",\r
+BulletedListLbl                : "Punktopstilling",\r
+BulletedList           : "Indsæt/fjern punktopstilling",\r
+ShowTableBorders       : "Vis tabelkanter",\r
+ShowDetails                    : "Vis detaljer",\r
+Style                          : "Typografi",\r
+FontFormat                     : "Formatering",\r
+Font                           : "Skrifttype",\r
+FontSize                       : "Skriftstørrelse",\r
+TextColor                      : "Tekstfarve",\r
+BGColor                                : "Baggrundsfarve",\r
+Source                         : "Kilde",\r
+Find                           : "Søg",\r
+Replace                                : "Erstat",\r
+SpellCheck                     : "Stavekontrol",\r
+UniversalKeyboard      : "Universaltastatur",\r
+PageBreakLbl           : "Sidskift",\r
+PageBreak                      : "Indsæt sideskift",\r
+\r
+Form                   : "Indsæt formular",\r
+Checkbox               : "Indsæt afkrydsningsfelt",\r
+RadioButton            : "Indsæt alternativknap",\r
+TextField              : "Indsæt tekstfelt",\r
+Textarea               : "Indsæt tekstboks",\r
+HiddenField            : "Indsæt skjult felt",\r
+Button                 : "Indsæt knap",\r
+SelectionField : "Indsæt liste",\r
+ImageButton            : "Indsæt billedknap",\r
+\r
+FitWindow              : "Maksimer editor vinduet",\r
+\r
+// Context Menu\r
+EditLink                       : "Rediger hyperlink",\r
+CellCM                         : "Celle",\r
+RowCM                          : "Række",\r
+ColumnCM                       : "Kolonne",\r
+InsertRow                      : "Indsæt række",\r
+DeleteRows                     : "Slet række",\r
+InsertColumn           : "Indsæt kolonne",\r
+DeleteColumns          : "Slet kolonne",\r
+InsertCell                     : "Indsæt celle",\r
+DeleteCells                    : "Slet celle",\r
+MergeCells                     : "Flet celler",\r
+SplitCell                      : "Opdel celle",\r
+TableDelete                    : "Slet tabel",\r
+CellProperties         : "Egenskaber for celle",\r
+TableProperties                : "Egenskaber for tabel",\r
+ImageProperties                : "Egenskaber for billede",\r
+FlashProperties                : "Egenskaber for Flash",\r
+\r
+AnchorProp                     : "Egenskaber for bogmærke",\r
+ButtonProp                     : "Egenskaber for knap",\r
+CheckboxProp           : "Egenskaber for afkrydsningsfelt",\r
+HiddenFieldProp                : "Egenskaber for skjult felt",\r
+RadioButtonProp                : "Egenskaber for alternativknap",\r
+ImageButtonProp                : "Egenskaber for billedknap",\r
+TextFieldProp          : "Egenskaber for tekstfelt",\r
+SelectionFieldProp     : "Egenskaber for liste",\r
+TextareaProp           : "Egenskaber for tekstboks",\r
+FormProp                       : "Egenskaber for formular",\r
+\r
+FontFormats                    : "Normal;Formateret;Adresse;Overskrift 1;Overskrift 2;Overskrift 3;Overskrift 4;Overskrift 5;Overskrift 6;Normal (DIV)",               //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Behandler XHTML...",\r
+Done                           : "Færdig",\r
+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?",\r
+NotCompatiblePaste     : "Denne kommando er tilgændelig i Internet Explorer 5.5 eller senere.<br>Vil du indsætte teksten uden at rense den ?",\r
+UnknownToolbarItem     : "Ukendt værktøjslinjeobjekt \"%1\"!",\r
+UnknownCommand         : "Ukendt kommandonavn \"%1\"!",\r
+NotImplemented         : "Kommandoen er ikke implementeret!",\r
+UnknownToolbarSet      : "Værktøjslinjen \"%1\" eksisterer ikke!",\r
+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.",\r
+BrowseServerBlocked : "Browseren kunne ikke åbne de nødvendige ressourcer!<br>Slå pop-up blokering fra.",\r
+DialogBlocked          : "Dialogvinduet kunne ikke åbnes!<br>Slå pop-up blokering fra.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Annuller",\r
+DlgBtnClose                    : "Luk",\r
+DlgBtnBrowseServer     : "Gennemse...",\r
+DlgAdvancedTag         : "Avanceret",\r
+DlgOpOther                     : "<Andet>",\r
+DlgInfoTab                     : "Generelt",\r
+DlgAlertUrl                    : "Indtast URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<intet valgt>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Tekstretning",\r
+DlgGenLangDirLtr       : "Fra venstre mod højre (LTR)",\r
+DlgGenLangDirRtl       : "Fra højre mod venstre (RTL)",\r
+DlgGenLangCode         : "Sprogkode",\r
+DlgGenAccessKey                : "Genvejstast",\r
+DlgGenName                     : "Navn",\r
+DlgGenTabIndex         : "Tabulator indeks",\r
+DlgGenLongDescr                : "Udvidet beskrivelse",\r
+DlgGenClass                    : "Typografiark",\r
+DlgGenTitle                    : "Titel",\r
+DlgGenContType         : "Indholdstype",\r
+DlgGenLinkCharset      : "Tegnsæt",\r
+DlgGenStyle                    : "Typografi",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Egenskaber for billede",\r
+DlgImgInfoTab          : "Generelt",\r
+DlgImgBtnUpload                : "Upload",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Upload",\r
+DlgImgAlt                      : "Alternativ tekst",\r
+DlgImgWidth                    : "Bredde",\r
+DlgImgHeight           : "Højde",\r
+DlgImgLockRatio                : "Lås størrelsesforhold",\r
+DlgBtnResetSize                : "Nulstil størrelse",\r
+DlgImgBorder           : "Ramme",\r
+DlgImgHSpace           : "HMargen",\r
+DlgImgVSpace           : "VMargen",\r
+DlgImgAlign                    : "Justering",\r
+DlgImgAlignLeft                : "Venstre",\r
+DlgImgAlignAbsBottom: "Absolut nederst",\r
+DlgImgAlignAbsMiddle: "Absolut centreret",\r
+DlgImgAlignBaseline    : "Grundlinje",\r
+DlgImgAlignBottom      : "Nederst",\r
+DlgImgAlignMiddle      : "Centreret",\r
+DlgImgAlignRight       : "Højre",\r
+DlgImgAlignTextTop     : "Toppen af teksten",\r
+DlgImgAlignTop         : "Øverst",\r
+DlgImgPreview          : "Vis eksempel",\r
+DlgImgAlertUrl         : "Indtast stien til billedet",\r
+DlgImgLinkTab          : "Hyperlink",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Egenskaber for Flash",\r
+DlgFlashChkPlay                : "Automatisk afspilning",\r
+DlgFlashChkLoop                : "Gentagelse",\r
+DlgFlashChkMenu                : "Vis Flash menu",\r
+DlgFlashScale          : "Skalér",\r
+DlgFlashScaleAll       : "Vis alt",\r
+DlgFlashScaleNoBorder  : "Ingen ramme",\r
+DlgFlashScaleFit       : "Tilpas størrelse",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Egenskaber for hyperlink",\r
+DlgLnkInfoTab          : "Generelt",\r
+DlgLnkTargetTab                : "Mål",\r
+\r
+DlgLnkType                     : "Hyperlink type",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Bogmærke på denne side",\r
+DlgLnkTypeEMail                : "E-mail",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<anden>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Vælg et anker",\r
+DlgLnkAnchorByName     : "Efter anker navn",\r
+DlgLnkAnchorById       : "Efter element Id",\r
+DlgLnkNoAnchors                : "<Ingen bogmærker dokumentet>",              //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-mailadresse",\r
+DlgLnkEMailSubject     : "Emne",\r
+DlgLnkEMailBody                : "Brødtekst",\r
+DlgLnkUpload           : "Upload",\r
+DlgLnkBtnUpload                : "Upload",\r
+\r
+DlgLnkTarget           : "Mål",\r
+DlgLnkTargetFrame      : "<ramme>",\r
+DlgLnkTargetPopup      : "<popup vindue>",\r
+DlgLnkTargetBlank      : "Nyt vindue (_blank)",\r
+DlgLnkTargetParent     : "Overordnet ramme (_parent)",\r
+DlgLnkTargetSelf       : "Samme vindue (_self)",\r
+DlgLnkTargetTop                : "Hele vinduet (_top)",\r
+DlgLnkTargetFrameName  : "Destinationsvinduets navn",\r
+DlgLnkPopWinName       : "Pop-up vinduets navn",\r
+DlgLnkPopWinFeat       : "Egenskaber for pop-up",\r
+DlgLnkPopResize                : "Skalering",\r
+DlgLnkPopLocation      : "Adresselinje",\r
+DlgLnkPopMenu          : "Menulinje",\r
+DlgLnkPopScroll                : "Scrollbars",\r
+DlgLnkPopStatus                : "Statuslinje",\r
+DlgLnkPopToolbar       : "Værktøjslinje",\r
+DlgLnkPopFullScrn      : "Fuld skærm (IE)",\r
+DlgLnkPopDependent     : "Koblet/dependent (Netscape)",\r
+DlgLnkPopWidth         : "Bredde",\r
+DlgLnkPopHeight                : "Højde",\r
+DlgLnkPopLeft          : "Position fra venstre",\r
+DlgLnkPopTop           : "Position fra toppen",\r
+\r
+DlnLnkMsgNoUrl         : "Indtast hyperlink URL!",\r
+DlnLnkMsgNoEMail       : "Indtast e-mailaddresse!",\r
+DlnLnkMsgNoAnchor      : "Vælg bogmærke!",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Vælg farve",\r
+DlgColorBtnClear       : "Nulstil",\r
+DlgColorHighlight      : "Markeret",\r
+DlgColorSelected       : "Valgt",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Vælg smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Vælg symbol",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Egenskaber for tabel",\r
+DlgTableRows           : "Rækker",\r
+DlgTableColumns                : "Kolonner",\r
+DlgTableBorder         : "Rammebredde",\r
+DlgTableAlign          : "Justering",\r
+DlgTableAlignNotSet    : "<intet valgt>",\r
+DlgTableAlignLeft      : "Venstrestillet",\r
+DlgTableAlignCenter    : "Centreret",\r
+DlgTableAlignRight     : "Højrestillet",\r
+DlgTableWidth          : "Bredde",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "procent",\r
+DlgTableHeight         : "Højde",\r
+DlgTableCellSpace      : "Celleafstand",\r
+DlgTableCellPad                : "Cellemargen",\r
+DlgTableCaption                : "Titel",\r
+DlgTableSummary                : "Resume",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Egenskaber for celle",\r
+DlgCellWidth           : "Bredde",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "procent",\r
+DlgCellHeight          : "Højde",\r
+DlgCellWordWrap                : "Orddeling",\r
+DlgCellWordWrapNotSet  : "<intet valgt>",\r
+DlgCellWordWrapYes     : "Ja",\r
+DlgCellWordWrapNo      : "Nej",\r
+DlgCellHorAlign                : "Vandret justering",\r
+DlgCellHorAlignNotSet  : "<intet valgt>",\r
+DlgCellHorAlignLeft    : "Venstrestillet",\r
+DlgCellHorAlignCenter  : "Centreret",\r
+DlgCellHorAlignRight: "Højrestillet",\r
+DlgCellVerAlign                : "Lodret justering",\r
+DlgCellVerAlignNotSet  : "<intet valgt>",\r
+DlgCellVerAlignTop     : "Øverst",\r
+DlgCellVerAlignMiddle  : "Centreret",\r
+DlgCellVerAlignBottom  : "Nederst",\r
+DlgCellVerAlignBaseline        : "Grundlinje",\r
+DlgCellRowSpan         : "Højde i antal rækker",\r
+DlgCellCollSpan                : "Bredde i antal kolonner",\r
+DlgCellBackColor       : "Baggrundsfarve",\r
+DlgCellBorderColor     : "Rammefarve",\r
+DlgCellBtnSelect       : "Vælg...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Find",\r
+DlgFindFindBtn         : "Find",\r
+DlgFindNotFoundMsg     : "Søgeteksten blev ikke fundet!",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Erstat",\r
+DlgReplaceFindLbl              : "Søg efter:",\r
+DlgReplaceReplaceLbl   : "Erstat med:",\r
+DlgReplaceCaseChk              : "Forskel på store og små bogstaver",\r
+DlgReplaceReplaceBtn   : "Erstat",\r
+DlgReplaceReplAllBtn   : "Erstat alle",\r
+DlgReplaceWordChk              : "Kun hele ord",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Din browsers sikkerhedsindstillinger tillader ikke editoren at klippe tekst automatisk!<br>Brug i stedet tastaturet til at klippe teksten (Ctrl+X).",\r
+PasteErrorCopy : "Din browsers sikkerhedsindstillinger tillader ikke editoren at kopiere tekst automatisk!<br>Brug i stedet tastaturet til at kopiere teksten (Ctrl+C).",\r
+\r
+PasteAsText            : "Indsæt som ikke-formateret tekst",\r
+PasteFromWord  : "Indsæt fra Word",\r
+\r
+DlgPasteMsg2   : "Indsæt i feltet herunder (<STRONG>Ctrl+V</STRONG>) og klik <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorer font definitioner",\r
+DlgPasteRemoveStyles   : "Ignorer typografi",\r
+DlgPasteCleanBox               : "Slet indhold",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatisk",\r
+ColorMoreColors        : "Flere farver...",\r
+\r
+// Document Properties\r
+DocProps               : "Egenskaber for dokument",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Egenskaber for bogmærke",\r
+DlgAnchorName          : "Bogmærke navn",\r
+DlgAnchorErrorName     : "Indtast bogmærke navn!",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Ikke i ordbogen",\r
+DlgSpellChangeTo               : "Forslag",\r
+DlgSpellBtnIgnore              : "Ignorer",\r
+DlgSpellBtnIgnoreAll   : "Ignorer alle",\r
+DlgSpellBtnReplace             : "Erstat",\r
+DlgSpellBtnReplaceAll  : "Erstat alle",\r
+DlgSpellBtnUndo                        : "Tilbage",\r
+DlgSpellNoSuggestions  : "- ingen forslag -",\r
+DlgSpellProgress               : "Stavekontrolen arbejder...",\r
+DlgSpellNoMispell              : "Stavekontrol færdig: Ingen fejl fundet",\r
+DlgSpellNoChanges              : "Stavekontrol færdig: Ingen ord ændret",\r
+DlgSpellOneChange              : "Stavekontrol færdig: Et ord ændret",\r
+DlgSpellManyChanges            : "Stavekontrol færdig: %1 ord ændret",\r
+\r
+IeSpellDownload                        : "Stavekontrol ikke installeret.<br>Vil du hente den nu?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekst",\r
+DlgButtonType          : "Type",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Navn",\r
+DlgCheckboxValue       : "Værdi",\r
+DlgCheckboxSelected    : "Valgt",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Navn",\r
+DlgFormAction  : "Handling",\r
+DlgFormMethod  : "Metod",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Navn",\r
+DlgSelectValue         : "Værdi",\r
+DlgSelectSize          : "Størrelse",\r
+DlgSelectLines         : "linier",\r
+DlgSelectChkMulti      : "Tillad flere valg",\r
+DlgSelectOpAvail       : "Valgmuligheder",\r
+DlgSelectOpText                : "Tekst",\r
+DlgSelectOpValue       : "Værdi",\r
+DlgSelectBtnAdd                : "Tilføj",\r
+DlgSelectBtnModify     : "Rediger",\r
+DlgSelectBtnUp         : "Op",\r
+DlgSelectBtnDown       : "Ned",\r
+DlgSelectBtnSetValue : "Sæt som valgt",\r
+DlgSelectBtnDelete     : "Slet",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Navn",\r
+DlgTextareaCols        : "Kolonner",\r
+DlgTextareaRows        : "Rækker",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Navn",\r
+DlgTextValue           : "Værdi",\r
+DlgTextCharWidth       : "Bredde (tegn)",\r
+DlgTextMaxChars                : "Max antal tegn",\r
+DlgTextType                    : "Type",\r
+DlgTextTypeText                : "Tekst",\r
+DlgTextTypePass                : "Adgangskode",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Navn",\r
+DlgHiddenValue : "Værdi",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Egenskaber for punktopstilling",\r
+NumberedListProp       : "Egenskaber for talopstilling",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Type",\r
+DlgLstTypeCircle       : "Cirkel",\r
+DlgLstTypeDisc         : "Udfyldt cirkel",\r
+DlgLstTypeSquare       : "Firkant",\r
+DlgLstTypeNumbers      : "Nummereret (1, 2, 3)",\r
+DlgLstTypeLCase                : "Små bogstaver (a, b, c)",\r
+DlgLstTypeUCase                : "Store bogstaver (A, B, C)",\r
+DlgLstTypeSRoman       : "Små romertal (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Store romertal (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Generelt",\r
+DlgDocBackTab          : "Baggrund",\r
+DlgDocColorsTab                : "Farver og margen",\r
+DlgDocMetaTab          : "Metadata",\r
+\r
+DlgDocPageTitle                : "Sidetitel",\r
+DlgDocLangDir          : "Sprog",\r
+DlgDocLangDirLTR       : "Fra venstre mod højre (LTR)",\r
+DlgDocLangDirRTL       : "Fra højre mod venstre (RTL)",\r
+DlgDocLangCode         : "Landekode",\r
+DlgDocCharSet          : "Tegnsæt kode",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Anden tegnsæt kode",\r
+\r
+DlgDocDocType          : "Dokumenttype kategori",\r
+DlgDocDocTypeOther     : "Anden dokumenttype kategori",\r
+DlgDocIncXHTML         : "Inkludere XHTML deklartion",\r
+DlgDocBgColor          : "Baggrundsfarve",\r
+DlgDocBgImage          : "Baggrundsbillede URL",\r
+DlgDocBgNoScroll       : "Fastlåst baggrund",\r
+DlgDocCText                    : "Tekst",\r
+DlgDocCLink                    : "Hyperlink",\r
+DlgDocCVisited         : "Besøgt hyperlink",\r
+DlgDocCActive          : "Aktivt hyperlink",\r
+DlgDocMargins          : "Sidemargen",\r
+DlgDocMaTop                    : "Øverst",\r
+DlgDocMaLeft           : "Venstre",\r
+DlgDocMaRight          : "Højre",\r
+DlgDocMaBottom         : "Nederst",\r
+DlgDocMeIndex          : "Dokument index nøgleord (kommasepareret)",\r
+DlgDocMeDescr          : "Dokument beskrivelse",\r
+DlgDocMeAuthor         : "Forfatter",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Vis",\r
+\r
+// Templates Dialog\r
+Templates                      : "Skabeloner",\r
+DlgTemplatesTitle      : "Indholdsskabeloner",\r
+DlgTemplatesSelMsg     : "Vælg den skabelon, som skal åbnes i editoren.<br>(Nuværende indhold vil blive overskrevet!):",\r
+DlgTemplatesLoading    : "Henter liste over skabeloner...",\r
+DlgTemplatesNoTpl      : "(Der er ikke defineret nogen skabelon!)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Om",\r
+DlgAboutBrowserInfoTab : "Generelt",\r
+DlgAboutLicenseTab     : "Licens",\r
+DlgAboutVersion                : "version",\r
+DlgAboutInfo           : "For yderlig information gå til"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/de.js b/httemplate/elements/fckeditor/editor/lang/de.js
new file mode 100644 (file)
index 0000000..2848d34
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * German language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Symbolleiste einklappen",\r
+ToolbarExpand          : "Symbolleiste ausklappen",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Speichern",\r
+NewPage                                : "Neue Seite",\r
+Preview                                : "Vorschau",\r
+Cut                                    : "Ausschneiden",\r
+Copy                           : "Kopieren",\r
+Paste                          : "Einfügen",\r
+PasteText                      : "aus Textdatei einfügen",\r
+PasteWord                      : "aus MS-Word einfügen",\r
+Print                          : "Drucken",\r
+SelectAll                      : "Alles auswählen",\r
+RemoveFormat           : "Formatierungen entfernen",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Link einfügen/editieren",\r
+RemoveLink                     : "Link entfernen",\r
+Anchor                         : "Anker einfügen/editieren",\r
+InsertImageLbl         : "Bild",\r
+InsertImage                    : "Bild einfügen/editieren",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Flash einfügen/editieren",\r
+InsertTableLbl         : "Tabelle",\r
+InsertTable                    : "Tabelle einfügen/editieren",\r
+InsertLineLbl          : "Linie",\r
+InsertLine                     : "Horizontale Linie einfügen",\r
+InsertSpecialCharLbl: "Sonderzeichen",\r
+InsertSpecialChar      : "Sonderzeichen einfügen/editieren",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Smiley einfügen",\r
+About                          : "Über FCKeditor",\r
+Bold                           : "Fett",\r
+Italic                         : "Kursiv",\r
+Underline                      : "Unterstrichen",\r
+StrikeThrough          : "Durchgestrichen",\r
+Subscript                      : "Tiefgestellt",\r
+Superscript                    : "Hochgestellt",\r
+LeftJustify                    : "Linksbündig",\r
+CenterJustify          : "Zentriert",\r
+RightJustify           : "Rechtsbündig",\r
+BlockJustify           : "Blocksatz",\r
+DecreaseIndent         : "Einzug verringern",\r
+IncreaseIndent         : "Einzug erhöhen",\r
+Undo                           : "Rückgängig",\r
+Redo                           : "Wiederherstellen",\r
+NumberedListLbl                : "Nummerierte Liste",\r
+NumberedList           : "Nummerierte Liste einfügen/entfernen",\r
+BulletedListLbl                : "Liste",\r
+BulletedList           : "Liste einfügen/entfernen",\r
+ShowTableBorders       : "Zeige Tabellenrahmen",\r
+ShowDetails                    : "Zeige Details",\r
+Style                          : "Stil",\r
+FontFormat                     : "Format",\r
+Font                           : "Schriftart",\r
+FontSize                       : "Größe",\r
+TextColor                      : "Textfarbe",\r
+BGColor                                : "Hintergrundfarbe",\r
+Source                         : "Quellcode",\r
+Find                           : "Finden",\r
+Replace                                : "Ersetzen",\r
+SpellCheck                     : "Rechtschreibprüfung",\r
+UniversalKeyboard      : "Universal-Tastatur",\r
+PageBreakLbl           : "Seitenumbruch",\r
+PageBreak                      : "Seitenumbruch einfügen",\r
+\r
+Form                   : "Formular",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Radiobutton",\r
+TextField              : "Textfeld einzeilig",\r
+Textarea               : "Textfeld mehrzeilig",\r
+HiddenField            : "verstecktes Feld",\r
+Button                 : "Klickbutton",\r
+SelectionField : "Auswahlfeld",\r
+ImageButton            : "Bildbutton",\r
+\r
+FitWindow              : "Editor maximieren",\r
+\r
+// Context Menu\r
+EditLink                       : "Link editieren",\r
+CellCM                         : "Zelle",\r
+RowCM                          : "Zeile",\r
+ColumnCM                       : "Spalte",\r
+InsertRow                      : "Zeile einfügen",\r
+DeleteRows                     : "Zeile entfernen",\r
+InsertColumn           : "Spalte einfügen",\r
+DeleteColumns          : "Spalte löschen",\r
+InsertCell                     : "Zelle einfügen",\r
+DeleteCells                    : "Zelle löschen",\r
+MergeCells                     : "Zellen vereinen",\r
+SplitCell                      : "Zelle teilen",\r
+TableDelete                    : "Tabelle löschen",\r
+CellProperties         : "Zellen Eigenschaften",\r
+TableProperties                : "Tabellen Eigenschaften",\r
+ImageProperties                : "Bild Eigenschaften",\r
+FlashProperties                : "Flash Eigenschaften",\r
+\r
+AnchorProp                     : "Anker Eigenschaften",\r
+ButtonProp                     : "Button Eigenschaften",\r
+CheckboxProp           : "Checkbox Eigenschaften",\r
+HiddenFieldProp                : "Verstecktes Feld Eigenschaften",\r
+RadioButtonProp                : "Optionsfeld Eigenschaften",\r
+ImageButtonProp                : "Bildbutton Eigenschaften",\r
+TextFieldProp          : "Textfeld (einzeilig) Eigenschaften",\r
+SelectionFieldProp     : "Auswahlfeld Eigenschaften",\r
+TextareaProp           : "Textfeld (mehrzeilig) Eigenschaften",\r
+FormProp                       : "Formular Eigenschaften",\r
+\r
+FontFormats                    : "Normal;Formatiert;Addresse;Überschrift 1;Überschrift 2;Überschrift 3;Überschrift 4;Überschrift 5;Überschrift 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Bearbeite XHTML. Bitte warten...",\r
+Done                           : "Fertig",\r
+PasteWordConfirm       : "Der Text, den Sie einfügen möchten, scheint aus MS-Word kopiert zu sein. Möchten Sie ihn zuvor bereinigen lassen?",\r
+NotCompatiblePaste     : "Diese Funktion steht nur im Internet Explorer ab Version 5.5 zur Verfügung. Möchten Sie den Text unbereinigt einfügen?",\r
+UnknownToolbarItem     : "Unbekanntes Menüleisten-Objekt \"%1\"",\r
+UnknownCommand         : "Unbekannter Befehl \"%1\"",\r
+NotImplemented         : "Befehl nicht implementiert",\r
+UnknownToolbarSet      : "Menüleiste \"%1\" existiert nicht",\r
+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",\r
+BrowseServerBlocked : "Ein Auswahlfenster konnte nicht geöffnet werden. Stellen Sie sicher, das alle Popup-Blocker ausgeschaltet sind.",\r
+DialogBlocked          : "Das Dialog-Fenster konnte nicht geöffnet werden. Stellen Sie sicher, das alle Popup-Blocker ausgeschaltet sind.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Abbrechen",\r
+DlgBtnClose                    : "Schließen",\r
+DlgBtnBrowseServer     : "Server durchsuchen",\r
+DlgAdvancedTag         : "Erweitert",\r
+DlgOpOther                     : "<andere>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Bitte tragen Sie die URL ein",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "< nichts >",\r
+DlgGenId                       : "ID",\r
+DlgGenLangDir          : "Schreibrichtung",\r
+DlgGenLangDirLtr       : "Links nach Rechts (LTR)",\r
+DlgGenLangDirRtl       : "Rechts nach Links (RTL)",\r
+DlgGenLangCode         : "Sprachenkürzel",\r
+DlgGenAccessKey                : "Schlüssel",\r
+DlgGenName                     : "Name",\r
+DlgGenTabIndex         : "Tab Index",\r
+DlgGenLongDescr                : "Langform URL",\r
+DlgGenClass                    : "Stylesheet Klasse",\r
+DlgGenTitle                    : "Titel Beschreibung",\r
+DlgGenContType         : "Content Beschreibung",\r
+DlgGenLinkCharset      : "Ziel-Zeichensatz",\r
+DlgGenStyle                    : "Style",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Bild Eigenschaften",\r
+DlgImgInfoTab          : "Bild-Info",\r
+DlgImgBtnUpload                : "Zum Server senden",\r
+DlgImgURL                      : "Bildauswahl",\r
+DlgImgUpload           : "Upload",\r
+DlgImgAlt                      : "Alternativer Text",\r
+DlgImgWidth                    : "Breite",\r
+DlgImgHeight           : "Höhe",\r
+DlgImgLockRatio                : "Größenverhältniss beibehalten",\r
+DlgBtnResetSize                : "Größe zurücksetzen",\r
+DlgImgBorder           : "Rahmen",\r
+DlgImgHSpace           : "H-Abstand",\r
+DlgImgVSpace           : "V-Abstand",\r
+DlgImgAlign                    : "Ausrichtung",\r
+DlgImgAlignLeft                : "Links",\r
+DlgImgAlignAbsBottom: "Abs Unten",\r
+DlgImgAlignAbsMiddle: "Abs Mitte",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Unten",\r
+DlgImgAlignMiddle      : "Mitte",\r
+DlgImgAlignRight       : "Rechts",\r
+DlgImgAlignTextTop     : "Text Oben",\r
+DlgImgAlignTop         : "Oben",\r
+DlgImgPreview          : "Vorschau",\r
+DlgImgAlertUrl         : "Bitte geben Sie die Bild-URL an",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Eigenschaften",\r
+DlgFlashChkPlay                : "autom. Abspielen",\r
+DlgFlashChkLoop                : "Endlosschleife",\r
+DlgFlashChkMenu                : "Flash-Menü aktivieren",\r
+DlgFlashScale          : "Skalierung",\r
+DlgFlashScaleAll       : "Alles anzeigen",\r
+DlgFlashScaleNoBorder  : "ohne Rand",\r
+DlgFlashScaleFit       : "Passgenau",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Link Info",\r
+DlgLnkTargetTab                : "Zielseite",\r
+\r
+DlgLnkType                     : "Link-Typ",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Anker in dieser Seite",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokoll",\r
+DlgLnkProtoOther       : "<anderes>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Anker auswählen",\r
+DlgLnkAnchorByName     : "nach Anker Name",\r
+DlgLnkAnchorById       : "nach Element Id",\r
+DlgLnkNoAnchors                : "<keine Anker im Dokument vorhanden>",                //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail Addresse",\r
+DlgLnkEMailSubject     : "Betreffzeile",\r
+DlgLnkEMailBody                : "Nachrichtentext",\r
+DlgLnkUpload           : "Upload",\r
+DlgLnkBtnUpload                : "Zum Server senden",\r
+\r
+DlgLnkTarget           : "Zielseite",\r
+DlgLnkTargetFrame      : "<Frame>",\r
+DlgLnkTargetPopup      : "<Pop-up Fenster>",\r
+DlgLnkTargetBlank      : "Neues Fenster (_blank)",\r
+DlgLnkTargetParent     : "Oberes Fenster (_parent)",\r
+DlgLnkTargetSelf       : "Gleiches Fenster (_self)",\r
+DlgLnkTargetTop                : "Oberstes Fenster (_top)",\r
+DlgLnkTargetFrameName  : "Ziel-Fenster Name",\r
+DlgLnkPopWinName       : "Pop-up Fenster Name",\r
+DlgLnkPopWinFeat       : "Pop-up Fenster Eigenschaften",\r
+DlgLnkPopResize                : "Vergrößerbar",\r
+DlgLnkPopLocation      : "Adress-Leiste",\r
+DlgLnkPopMenu          : "Menü-Leiste",\r
+DlgLnkPopScroll                : "Rollbalken",\r
+DlgLnkPopStatus                : "Statusleiste",\r
+DlgLnkPopToolbar       : "Werkzeugleiste",\r
+DlgLnkPopFullScrn      : "Vollbild (IE)",\r
+DlgLnkPopDependent     : "Abhängig (Netscape)",\r
+DlgLnkPopWidth         : "Breite",\r
+DlgLnkPopHeight                : "Höhe",\r
+DlgLnkPopLeft          : "Linke Position",\r
+DlgLnkPopTop           : "Obere Position",\r
+\r
+DlnLnkMsgNoUrl         : "Bitte geben Sie die Link-URL an",\r
+DlnLnkMsgNoEMail       : "Bitte geben Sie e-Mail Adresse an",\r
+DlnLnkMsgNoAnchor      : "Bitte wählen Sie einen Anker aus",\r
+DlnLnkMsgInvPopName    : "Der Name des Popups muss mit einem Buchstaben beginnen und darf keine Leerzeichen enthalten",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Farbauswahl",\r
+DlgColorBtnClear       : "Keine Farbe",\r
+DlgColorHighlight      : "Vorschau",\r
+DlgColorSelected       : "Ausgewählt",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Smiley auswählen",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Sonderzeichen auswählen",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Tabellen Eigenschaften",\r
+DlgTableRows           : "Zeile",\r
+DlgTableColumns                : "Spalte",\r
+DlgTableBorder         : "Rahmen",\r
+DlgTableAlign          : "Ausrichtung",\r
+DlgTableAlignNotSet    : "<nichts>",\r
+DlgTableAlignLeft      : "Links",\r
+DlgTableAlignCenter    : "Zentriert",\r
+DlgTableAlignRight     : "Rechts",\r
+DlgTableWidth          : "Breite",\r
+DlgTableWidthPx                : "Pixel",\r
+DlgTableWidthPc                : "%",\r
+DlgTableHeight         : "Höhe",\r
+DlgTableCellSpace      : "Zellenabstand außen",\r
+DlgTableCellPad                : "Zellenabstand innen",\r
+DlgTableCaption                : "Überschrift",\r
+DlgTableSummary                : "Inhaltsübersicht",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Zellen-Eigenschaften",\r
+DlgCellWidth           : "Breite",\r
+DlgCellWidthPx         : "Pixel",\r
+DlgCellWidthPc         : "%",\r
+DlgCellHeight          : "Höhe",\r
+DlgCellWordWrap                : "Umbruch",\r
+DlgCellWordWrapNotSet  : "<nichts>",\r
+DlgCellWordWrapYes     : "Ja",\r
+DlgCellWordWrapNo      : "Nein",\r
+DlgCellHorAlign                : "Horizontale Ausrichtung",\r
+DlgCellHorAlignNotSet  : "<nichts>",\r
+DlgCellHorAlignLeft    : "Links",\r
+DlgCellHorAlignCenter  : "Zentriert",\r
+DlgCellHorAlignRight: "Rechts",\r
+DlgCellVerAlign                : "Vertikale Ausrichtung",\r
+DlgCellVerAlignNotSet  : "<nichts>",\r
+DlgCellVerAlignTop     : "Oben",\r
+DlgCellVerAlignMiddle  : "Mitte",\r
+DlgCellVerAlignBottom  : "Unten",\r
+DlgCellVerAlignBaseline        : "Grundlinie",\r
+DlgCellRowSpan         : "Zeilen zusammenfassen",\r
+DlgCellCollSpan                : "Spalten zusammenfassen",\r
+DlgCellBackColor       : "Hintergrundfarbe",\r
+DlgCellBorderColor     : "Rahmenfarbe",\r
+DlgCellBtnSelect       : "Auswahl...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Finden",\r
+DlgFindFindBtn         : "Finden",\r
+DlgFindNotFoundMsg     : "Der gesuchte Text wurde nicht gefunden.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Ersetzen",\r
+DlgReplaceFindLbl              : "Suche nach:",\r
+DlgReplaceReplaceLbl   : "Ersetze mit:",\r
+DlgReplaceCaseChk              : "Groß-Kleinschreibung beachten",\r
+DlgReplaceReplaceBtn   : "Ersetzen",\r
+DlgReplaceReplAllBtn   : "Alle Ersetzen",\r
+DlgReplaceWordChk              : "Nur ganze Worte suchen",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+PasteErrorCopy : "Die Sicherheitseinstellungen Ihres Browsers lassen es nicht zu, den Text automatisch kopieren. Bitte benutzen Sie die System-Zwischenablage über STRG-C (kopieren).",\r
+\r
+PasteAsText            : "Als Text einfügen",\r
+PasteFromWord  : "Aus Word einfügen",\r
+\r
+DlgPasteMsg2   : "Bitte fügen Sie den Text in der folgenden Box über die Tastatur (mit <STRONG>Ctrl+V</STRONG>) ein und bestätigen Sie mit <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignoriere Schriftart-Definitionen",\r
+DlgPasteRemoveStyles   : "Entferne Style-Definitionen",\r
+DlgPasteCleanBox               : "Inhalt aufräumen",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatisch",\r
+ColorMoreColors        : "Weitere Farben...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokument Eigenschaften",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Anker Eigenschaften",\r
+DlgAnchorName          : "Anker Name",\r
+DlgAnchorErrorName     : "Bitte geben Sie den Namen des Ankers ein",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Nicht im Wörterbuch",\r
+DlgSpellChangeTo               : "Ändern in",\r
+DlgSpellBtnIgnore              : "Ignorieren",\r
+DlgSpellBtnIgnoreAll   : "Alle Ignorieren",\r
+DlgSpellBtnReplace             : "Ersetzen",\r
+DlgSpellBtnReplaceAll  : "Alle Ersetzen",\r
+DlgSpellBtnUndo                        : "Rückgängig",\r
+DlgSpellNoSuggestions  : " - keine Vorschläge - ",\r
+DlgSpellProgress               : "Rechtschreibprüfung läuft...",\r
+DlgSpellNoMispell              : "Rechtschreibprüfung abgeschlossen - keine Fehler gefunden",\r
+DlgSpellNoChanges              : "Rechtschreibprüfung abgeschlossen - keine Worte geändert",\r
+DlgSpellOneChange              : "Rechtschreibprüfung abgeschlossen - ein Wort geändert",\r
+DlgSpellManyChanges            : "Rechtschreibprüfung abgeschlossen - %1 Wörter geändert",\r
+\r
+IeSpellDownload                        : "Rechtschreibprüfung nicht installiert. Möchten Sie sie jetzt herunterladen?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Wert)",\r
+DlgButtonType          : "Typ",\r
+DlgButtonTypeBtn       : "Button",\r
+DlgButtonTypeSbm       : "Absenden",\r
+DlgButtonTypeRst       : "Zurücksetzen",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Name",\r
+DlgCheckboxValue       : "Wert",\r
+DlgCheckboxSelected    : "ausgewählt",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Name",\r
+DlgFormAction  : "Action",\r
+DlgFormMethod  : "Method",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Name",\r
+DlgSelectValue         : "Wert",\r
+DlgSelectSize          : "Größe",\r
+DlgSelectLines         : "Linien",\r
+DlgSelectChkMulti      : "Erlaube Mehrfachauswahl",\r
+DlgSelectOpAvail       : "Mögliche Optionen",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Wert",\r
+DlgSelectBtnAdd                : "Hinzufügen",\r
+DlgSelectBtnModify     : "Ändern",\r
+DlgSelectBtnUp         : "Hoch",\r
+DlgSelectBtnDown       : "Runter",\r
+DlgSelectBtnSetValue : "Setze als Standardwert",\r
+DlgSelectBtnDelete     : "Entfernen",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Name",\r
+DlgTextareaCols        : "Spalten",\r
+DlgTextareaRows        : "Reihen",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Name",\r
+DlgTextValue           : "Wert",\r
+DlgTextCharWidth       : "Zeichenbreite",\r
+DlgTextMaxChars                : "Max. Zeichen",\r
+DlgTextType                    : "Typ",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Passwort",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Name",\r
+DlgHiddenValue : "Wert",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Listen-Eigenschaften",\r
+NumberedListProp       : "Nummerierte Listen-Eigenschaften",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Typ",\r
+DlgLstTypeCircle       : "Ring",\r
+DlgLstTypeDisc         : "Kreis",\r
+DlgLstTypeSquare       : "Quadrat",\r
+DlgLstTypeNumbers      : "Nummern (1, 2, 3)",\r
+DlgLstTypeLCase                : "Kleinbuchstaben (a, b, c)",\r
+DlgLstTypeUCase                : "Großbuchstaben (A, B, C)",\r
+DlgLstTypeSRoman       : "Kleine römische Zahlen (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Große römische Zahlen (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Allgemein",\r
+DlgDocBackTab          : "Hintergrund",\r
+DlgDocColorsTab                : "Farben und Abstände",\r
+DlgDocMetaTab          : "Metadaten",\r
+\r
+DlgDocPageTitle                : "Seitentitel",\r
+DlgDocLangDir          : "Schriftrichtung",\r
+DlgDocLangDirLTR       : "Links nach Rechts",\r
+DlgDocLangDirRTL       : "Rechts nach Links",\r
+DlgDocLangCode         : "Sprachkürzel",\r
+DlgDocCharSet          : "Zeichenkodierung",\r
+DlgDocCharSetCE                : "Zentraleuropäisch",\r
+DlgDocCharSetCT                : "traditionell Chinesisch (Big5)",\r
+DlgDocCharSetCR                : "Kyrillisch",\r
+DlgDocCharSetGR                : "Griechisch",\r
+DlgDocCharSetJP                : "Japanisch",\r
+DlgDocCharSetKR                : "Koreanisch",\r
+DlgDocCharSetTR                : "Türkisch",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Westeuropäisch",\r
+DlgDocCharSetOther     : "Andere Zeichenkodierung",\r
+\r
+DlgDocDocType          : "Dokumententyp",\r
+DlgDocDocTypeOther     : "Anderer Dokumententyp",\r
+DlgDocIncXHTML         : "Beziehe XHTML Deklarationen ein",\r
+DlgDocBgColor          : "Hintergrundfarbe",\r
+DlgDocBgImage          : "Hintergrundbild URL",\r
+DlgDocBgNoScroll       : "feststehender Hintergrund",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Besuchter Link",\r
+DlgDocCActive          : "Aktiver Link",\r
+DlgDocMargins          : "Seitenränder",\r
+DlgDocMaTop                    : "Oben",\r
+DlgDocMaLeft           : "Links",\r
+DlgDocMaRight          : "Rechts",\r
+DlgDocMaBottom         : "Unten",\r
+DlgDocMeIndex          : "Schlüsselwörter (durch Komma getrennt)",\r
+DlgDocMeDescr          : "Dokument-Beschreibung",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Vorschau",\r
+\r
+// Templates Dialog\r
+Templates                      : "Vorlagen",\r
+DlgTemplatesTitle      : "Vorlagen",\r
+DlgTemplatesSelMsg     : "Klicken Sie auf eine Vorlage, um sie im Editor zu öffnen (der aktuelle Inhalt wird dabei gelöscht!):",\r
+DlgTemplatesLoading    : "Liste der Vorlagen wird geladen. Bitte warten...",\r
+DlgTemplatesNoTpl      : "(keine Vorlagen definiert)",\r
+DlgTemplatesReplace    : "Aktuellen Inhalt ersetzen",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Über",\r
+DlgAboutBrowserInfoTab : "Browser-Info",\r
+DlgAboutLicenseTab     : "Lizenz",\r
+DlgAboutVersion                : "Version",\r
+DlgAboutInfo           : "Für weitere Informationen siehe"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/el.js b/httemplate/elements/fckeditor/editor/lang/el.js
new file mode 100644 (file)
index 0000000..90fefc4
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Greek language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Απόκρυψη Μπάρας Εργαλείων",\r
+ToolbarExpand          : "Εμφάνιση Μπάρας Εργαλείων",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Αποθήκευση",\r
+NewPage                                : "Νέα Σελίδα",\r
+Preview                                : "Προεπισκόπιση",\r
+Cut                                    : "Αποκοπή",\r
+Copy                           : "Αντιγραφή",\r
+Paste                          : "Επικόλληση",\r
+PasteText                      : "Επικόλληση (απλό κείμενο)",\r
+PasteWord                      : "Επικόλληση από το Word",\r
+Print                          : "Εκτύπωση",\r
+SelectAll                      : "Επιλογή όλων",\r
+RemoveFormat           : "Αφαίρεση Μορφοποίησης",\r
+InsertLinkLbl          : "Σύνδεσμος (Link)",\r
+InsertLink                     : "Εισαγωγή/Μεταβολή Συνδέσμου (Link)",\r
+RemoveLink                     : "Αφαίρεση Συνδέσμου (Link)",\r
+Anchor                         : "Εισαγωγή/επεξεργασία Anchor",\r
+InsertImageLbl         : "Εικόνα",\r
+InsertImage                    : "Εισαγωγή/Μεταβολή Εικόνας",\r
+InsertFlashLbl         : "Εισαγωγή Flash",\r
+InsertFlash                    : "Εισαγωγή/επεξεργασία Flash",\r
+InsertTableLbl         : "Πίνακας",\r
+InsertTable                    : "Εισαγωγή/Μεταβολή Πίνακα",\r
+InsertLineLbl          : "Γραμμή",\r
+InsertLine                     : "Εισαγωγή Οριζόντιας Γραμμής",\r
+InsertSpecialCharLbl: "Ειδικό Σύμβολο",\r
+InsertSpecialChar      : "Εισαγωγή Ειδικού Συμβόλου",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Εισαγωγή Smiley",\r
+About                          : "Περί του FCKeditor",\r
+Bold                           : "Έντονα",\r
+Italic                         : "Πλάγια",\r
+Underline                      : "Υπογράμμιση",\r
+StrikeThrough          : "Διαγράμμιση",\r
+Subscript                      : "Δείκτης",\r
+Superscript                    : "Εκθέτης",\r
+LeftJustify                    : "Στοίχιση Αριστερά",\r
+CenterJustify          : "Στοίχιση στο Κέντρο",\r
+RightJustify           : "Στοίχιση Δεξιά",\r
+BlockJustify           : "Πλήρης Στοίχιση (Block)",\r
+DecreaseIndent         : "Μείωση Εσοχής",\r
+IncreaseIndent         : "Αύξηση Εσοχής",\r
+Undo                           : "Αναίρεση",\r
+Redo                           : "Επαναφορά",\r
+NumberedListLbl                : "Λίστα με Αριθμούς",\r
+NumberedList           : "Εισαγωγή/Διαγραφή Λίστας με Αριθμούς",\r
+BulletedListLbl                : "Λίστα με Bullets",\r
+BulletedList           : "Εισαγωγή/Διαγραφή Λίστας με Bullets",\r
+ShowTableBorders       : "Προβολή Ορίων Πίνακα",\r
+ShowDetails                    : "Προβολή Λεπτομερειών",\r
+Style                          : "Στυλ",\r
+FontFormat                     : "Μορφή Γραμματοσειράς",\r
+Font                           : "Γραμματοσειρά",\r
+FontSize                       : "Μέγεθος",\r
+TextColor                      : "Χρώμα Γραμμάτων",\r
+BGColor                                : "Χρώμα Υποβάθρου",\r
+Source                         : "HTML κώδικας",\r
+Find                           : "Αναζήτηση",\r
+Replace                                : "Αντικατάσταση",\r
+SpellCheck                     : "Ορθογραφικός έλεγχος",\r
+UniversalKeyboard      : "Διεθνής πληκτρολόγιο",\r
+PageBreakLbl           : "Τέλος σελίδας",\r
+PageBreak                      : "Εισαγωγή τέλους σελίδας",\r
+\r
+Form                   : "Φόρμα",\r
+Checkbox               : "Κουτί επιλογής",\r
+RadioButton            : "Κουμπί Radio",\r
+TextField              : "Πεδίο κειμένου",\r
+Textarea               : "Περιοχή κειμένου",\r
+HiddenField            : "Κρυφό πεδίο",\r
+Button                 : "Κουμπί",\r
+SelectionField : "Πεδίο επιλογής",\r
+ImageButton            : "Κουμπί εικόνας",\r
+\r
+FitWindow              : "Μεγιστοποίηση προγράμματος",\r
+\r
+// Context Menu\r
+EditLink                       : "Μεταβολή Συνδέσμου (Link)",\r
+CellCM                         : "Κελί",\r
+RowCM                          : "Σειρά",\r
+ColumnCM                       : "Στήλη",\r
+InsertRow                      : "Εισαγωγή Γραμμής",\r
+DeleteRows                     : "Διαγραφή Γραμμών",\r
+InsertColumn           : "Εισαγωγή Κολώνας",\r
+DeleteColumns          : "Διαγραφή Κολωνών",\r
+InsertCell                     : "Εισαγωγή Κελιού",\r
+DeleteCells                    : "Διαγραφή Κελιών",\r
+MergeCells                     : "Ενοποίηση Κελιών",\r
+SplitCell                      : "Διαχωρισμός Κελιού",\r
+TableDelete                    : "Διαγραφή πίνακα",\r
+CellProperties         : "Ιδιότητες Κελιού",\r
+TableProperties                : "Ιδιότητες Πίνακα",\r
+ImageProperties                : "Ιδιότητες Εικόνας",\r
+FlashProperties                : "Ιδιότητες Flash",\r
+\r
+AnchorProp                     : "Ιδιότητες άγκυρας",\r
+ButtonProp                     : "Ιδιότητες κουμπιού",\r
+CheckboxProp           : "Ιδιότητες κουμπιού επιλογής",\r
+HiddenFieldProp                : "Ιδιότητες κρυφού πεδίου",\r
+RadioButtonProp                : "Ιδιότητες κουμπιού radio",\r
+ImageButtonProp                : "Ιδιότητες κουμπιού εικόνας",\r
+TextFieldProp          : "Ιδιότητες πεδίου κειμένου",\r
+SelectionFieldProp     : "Ιδιότητες πεδίου επιλογής",\r
+TextareaProp           : "Ιδιότητες περιοχής κειμένου",\r
+FormProp                       : "Ιδιότητες φόρμας",\r
+\r
+FontFormats                    : "Κανονικό;Μορφοποιημένο;Διεύθυνση;Επικεφαλίδα 1;Επικεφαλίδα 2;Επικεφαλίδα 3;Επικεφαλίδα 4;Επικεφαλίδα 5;Επικεφαλίδα 6",               //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Επεξεργασία XHTML. Παρακαλώ περιμένετε...",\r
+Done                           : "Έτοιμο",\r
+PasteWordConfirm       : "Το κείμενο που θέλετε να επικολήσετε, φαίνεται πως προέρχεται από το Word. Θέλετε να καθαριστεί πριν επικοληθεί;",\r
+NotCompatiblePaste     : "Αυτή η επιλογή είναι διαθέσιμη στον Internet Explorer έκδοση 5.5+. Θέλετε να γίνει η επικόλληση χωρίς καθαρισμό;",\r
+UnknownToolbarItem     : "Άγνωστο αντικείμενο της μπάρας εργαλείων \"%1\"",\r
+UnknownCommand         : "Άγνωστή εντολή \"%1\"",\r
+NotImplemented         : "Η εντολή δεν έχει ενεργοποιηθεί",\r
+UnknownToolbarSet      : "Η μπάρα εργαλείων \"%1\" δεν υπάρχει",\r
+NoActiveX                      : "Οι ρυθμίσεις ασφαλείας του browser σας μπορεί να περιορίσουν κάποιες ρυθμίσεις του προγράμματος. Χρειάζεται να ενεργοποιήσετε την επιλογή \"Run ActiveX controls and plug-ins\". Ίσως παρουσιαστούν λάθη και παρατηρήσετε ελειπείς λειτουργίες.",\r
+BrowseServerBlocked : "Οι πόροι του browser σας δεν είναι προσπελάσιμοι. Σιγουρευτείτε ότι δεν υπάρχουν ενεργοί popup blockers.",\r
+DialogBlocked          : "Δεν ήταν δυνατό να ανοίξει το παράθυρο διαλόγου. Σιγουρευτείτε ότι δεν υπάρχουν ενεργοί popup blockers.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Ακύρωση",\r
+DlgBtnClose                    : "Κλείσιμο",\r
+DlgBtnBrowseServer     : "Εξερεύνηση διακομιστή",\r
+DlgAdvancedTag         : "Για προχωρημένους",\r
+DlgOpOther                     : "<Άλλα>",\r
+DlgInfoTab                     : "Πληροφορίες",\r
+DlgAlertUrl                    : "Παρακαλώ εισάγετε URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<χωρίς>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Κατεύθυνση κειμένου",\r
+DlgGenLangDirLtr       : "Αριστερά προς Δεξιά (LTR)",\r
+DlgGenLangDirRtl       : "Δεξιά προς Αριστερά (RTL)",\r
+DlgGenLangCode         : "Κωδικός Γλώσσας",\r
+DlgGenAccessKey                : "Συντόμευση (Access Key)",\r
+DlgGenName                     : "Όνομα",\r
+DlgGenTabIndex         : "Tab Index",\r
+DlgGenLongDescr                : "Αναλυτική περιγραφή URL",\r
+DlgGenClass                    : "Stylesheet Classes",\r
+DlgGenTitle                    : "Συμβουλευτικός τίτλος",\r
+DlgGenContType         : "Συμβουλευτικός τίτλος περιεχομένου",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Στύλ",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Ιδιότητες Εικόνας",\r
+DlgImgInfoTab          : "Πληροφορίες Εικόνας",\r
+DlgImgBtnUpload                : "Αποστολή στον Διακομιστή",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Αποστολή",\r
+DlgImgAlt                      : "Εναλλακτικό Κείμενο (ALT)",\r
+DlgImgWidth                    : "Πλάτος",\r
+DlgImgHeight           : "Ύψος",\r
+DlgImgLockRatio                : "Κλείδωμα Αναλογίας",\r
+DlgBtnResetSize                : "Επαναφορά Αρχικού Μεγέθους",\r
+DlgImgBorder           : "Περιθώριο",\r
+DlgImgHSpace           : "Οριζόντιος Χώρος (HSpace)",\r
+DlgImgVSpace           : "Κάθετος Χώρος (VSpace)",\r
+DlgImgAlign                    : "Ευθυγράμμιση (Align)",\r
+DlgImgAlignLeft                : "Αριστερά",\r
+DlgImgAlignAbsBottom: "Απόλυτα Κάτω (Abs Bottom)",\r
+DlgImgAlignAbsMiddle: "Απόλυτα στη Μέση (Abs Middle)",\r
+DlgImgAlignBaseline    : "Γραμμή Βάσης (Baseline)",\r
+DlgImgAlignBottom      : "Κάτω (Bottom)",\r
+DlgImgAlignMiddle      : "Μέση (Middle)",\r
+DlgImgAlignRight       : "Δεξιά (Right)",\r
+DlgImgAlignTextTop     : "Κορυφή Κειμένου (Text Top)",\r
+DlgImgAlignTop         : "Πάνω (Top)",\r
+DlgImgPreview          : "Προεπισκόπιση",\r
+DlgImgAlertUrl         : "Εισάγετε την τοποθεσία (URL) της εικόνας",\r
+DlgImgLinkTab          : "Σύνδεσμος",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Ιδιότητες flash",\r
+DlgFlashChkPlay                : "Αυτόματη έναρξη",\r
+DlgFlashChkLoop                : "Επανάληψη",\r
+DlgFlashChkMenu                : "Ενεργοποίηση Flash Menu",\r
+DlgFlashScale          : "Κλίμακα",\r
+DlgFlashScaleAll       : "Εμφάνιση όλων",\r
+DlgFlashScaleNoBorder  : "Χωρίς όρια",\r
+DlgFlashScaleFit       : "Ακριβής εφαρμογή",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Σύνδεσμος (Link)",\r
+DlgLnkInfoTab          : "Link",\r
+DlgLnkTargetTab                : "Παράθυρο Στόχος (Target)",\r
+\r
+DlgLnkType                     : "Τύπος συνδέσμου (Link)",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Άγκυρα σε αυτή τη σελίδα",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Προτόκολο",\r
+DlgLnkProtoOther       : "<άλλο>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Επιλέξτε μια άγκυρα",\r
+DlgLnkAnchorByName     : "Βάσει του Ονόματος (Name) της άγκυρας",\r
+DlgLnkAnchorById       : "Βάσει του Element Id",\r
+DlgLnkNoAnchors                : "<Δεν υπάρχουν άγκυρες στο κείμενο>",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Διεύθυνση Ηλεκτρονικού Ταχυδρομείου",\r
+DlgLnkEMailSubject     : "Θέμα Μηνύματος",\r
+DlgLnkEMailBody                : "Κείμενο Μηνύματος",\r
+DlgLnkUpload           : "Αποστολή",\r
+DlgLnkBtnUpload                : "Αποστολή στον Διακομιστή",\r
+\r
+DlgLnkTarget           : "Παράθυρο Στόχος (Target)",\r
+DlgLnkTargetFrame      : "<πλαίσιο>",\r
+DlgLnkTargetPopup      : "<παράθυρο popup>",\r
+DlgLnkTargetBlank      : "Νέο Παράθυρο (_blank)",\r
+DlgLnkTargetParent     : "Γονικό Παράθυρο (_parent)",\r
+DlgLnkTargetSelf       : "Ίδιο Παράθυρο (_self)",\r
+DlgLnkTargetTop                : "Ανώτατο Παράθυρο (_top)",\r
+DlgLnkTargetFrameName  : "Όνομα πλαισίου στόχου",\r
+DlgLnkPopWinName       : "Όνομα Popup Window",\r
+DlgLnkPopWinFeat       : "Επιλογές Popup Window",\r
+DlgLnkPopResize                : "Με αλλαγή Μεγέθους",\r
+DlgLnkPopLocation      : "Μπάρα Τοποθεσίας",\r
+DlgLnkPopMenu          : "Μπάρα Menu",\r
+DlgLnkPopScroll                : "Μπάρες Κύλισης",\r
+DlgLnkPopStatus                : "Μπάρα Status",\r
+DlgLnkPopToolbar       : "Μπάρα Εργαλείων",\r
+DlgLnkPopFullScrn      : "Ολόκληρη η Οθόνη (IE)",\r
+DlgLnkPopDependent     : "Dependent (Netscape)",\r
+DlgLnkPopWidth         : "Πλάτος",\r
+DlgLnkPopHeight                : "Ύψος",\r
+DlgLnkPopLeft          : "Τοποθεσία Αριστερής Άκρης",\r
+DlgLnkPopTop           : "Τοποθεσία Πάνω Άκρης",\r
+\r
+DlnLnkMsgNoUrl         : "Εισάγετε την τοποθεσία (URL) του υπερσυνδέσμου (Link)",\r
+DlnLnkMsgNoEMail       : "Εισάγετε την διεύθυνση ηλεκτρονικού ταχυδρομείου",\r
+DlnLnkMsgNoAnchor      : "Επιλέξτε ένα Anchor",\r
+DlnLnkMsgInvPopName    : "Το όνομα του popup πρέπει να αρχίζει με χαρακτήρα της αλφαβήτου και να μην περιέχει κενά",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Επιλογή χρώματος",\r
+DlgColorBtnClear       : "Καθαρισμός",\r
+DlgColorHighlight      : "Προεπισκόπιση",\r
+DlgColorSelected       : "Επιλεγμένο",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Επιλέξτε ένα Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Επιλέξτε ένα Ειδικό Σύμβολο",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Ιδιότητες Πίνακα",\r
+DlgTableRows           : "Γραμμές",\r
+DlgTableColumns                : "Κολώνες",\r
+DlgTableBorder         : "Μέγεθος Περιθωρίου",\r
+DlgTableAlign          : "Στοίχιση",\r
+DlgTableAlignNotSet    : "<χωρίς>",\r
+DlgTableAlignLeft      : "Αριστερά",\r
+DlgTableAlignCenter    : "Κέντρο",\r
+DlgTableAlignRight     : "Δεξιά",\r
+DlgTableWidth          : "Πλάτος",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "\%",\r
+DlgTableHeight         : "Ύψος",\r
+DlgTableCellSpace      : "Απόσταση κελιών",\r
+DlgTableCellPad                : "Γέμισμα κελιών",\r
+DlgTableCaption                : "Υπέρτιτλος",\r
+DlgTableSummary                : "Περίληψη",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Ιδιότητες Κελιού",\r
+DlgCellWidth           : "Πλάτος",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "\%",\r
+DlgCellHeight          : "Ύψος",\r
+DlgCellWordWrap                : "Με αλλαγή γραμμής",\r
+DlgCellWordWrapNotSet  : "<χωρίς>",\r
+DlgCellWordWrapYes     : "Ναι",\r
+DlgCellWordWrapNo      : "Όχι",\r
+DlgCellHorAlign                : "Οριζόντια Στοίχιση",\r
+DlgCellHorAlignNotSet  : "<χωρίς>",\r
+DlgCellHorAlignLeft    : "Αριστερά",\r
+DlgCellHorAlignCenter  : "Κέντρο",\r
+DlgCellHorAlignRight: "Δεξιά",\r
+DlgCellVerAlign                : "Κάθετη Στοίχιση",\r
+DlgCellVerAlignNotSet  : "<χωρίς>",\r
+DlgCellVerAlignTop     : "Πάνω (Top)",\r
+DlgCellVerAlignMiddle  : "Μέση (Middle)",\r
+DlgCellVerAlignBottom  : "Κάτω (Bottom)",\r
+DlgCellVerAlignBaseline        : "Γραμμή Βάσης (Baseline)",\r
+DlgCellRowSpan         : "Αριθμός Γραμμών (Rows Span)",\r
+DlgCellCollSpan                : "Αριθμός Κολωνών (Columns Span)",\r
+DlgCellBackColor       : "Χρώμα Υποβάθρου",\r
+DlgCellBorderColor     : "Χρώμα Περιθωρίου",\r
+DlgCellBtnSelect       : "Επιλογή...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Αναζήτηση",\r
+DlgFindFindBtn         : "Αναζήτηση",\r
+DlgFindNotFoundMsg     : "Το κείμενο δεν βρέθηκε.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Αντικατάσταση",\r
+DlgReplaceFindLbl              : "Αναζήτηση:",\r
+DlgReplaceReplaceLbl   : "Αντικατάσταση με:",\r
+DlgReplaceCaseChk              : "Έλεγχος πεζών/κεφαλαίων",\r
+DlgReplaceReplaceBtn   : "Αντικατάσταση",\r
+DlgReplaceReplAllBtn   : "Αντικατάσταση Όλων",\r
+DlgReplaceWordChk              : "Εύρεση πλήρους λέξης",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Οι ρυθμίσεις ασφαλείας του φυλλομετρητή σας δεν επιτρέπουν την επιλεγμένη εργασία αποκοπής. Χρησιμοποιείστε το πληκτρολόγιο (Ctrl+X).",\r
+PasteErrorCopy : "Οι ρυθμίσεις ασφαλείας του φυλλομετρητή σας δεν επιτρέπουν την επιλεγμένη εργασία αντιγραφής. Χρησιμοποιείστε το πληκτρολόγιο (Ctrl+C).",\r
+\r
+PasteAsText            : "Επικόλληση ως Απλό Κείμενο",\r
+PasteFromWord  : "Επικόλληση από το Word",\r
+\r
+DlgPasteMsg2   : "Παρακαλώ επικολήστε στο ακόλουθο κουτί χρησιμοποιόντας το πληκτρολόγιο (<STRONG>Ctrl+V</STRONG>) και πατήστε <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Αγνόηση προδιαγραφών γραμματοσειράς",\r
+DlgPasteRemoveStyles   : "Αφαίρεση προδιαγραφών στύλ",\r
+DlgPasteCleanBox               : "Κουτί εκαθάρισης",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Αυτόματο",\r
+ColorMoreColors        : "Περισσότερα χρώματα...",\r
+\r
+// Document Properties\r
+DocProps               : "Ιδιότητες εγγράφου",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ιδιότητες άγκυρας",\r
+DlgAnchorName          : "Όνομα άγκυρας",\r
+DlgAnchorErrorName     : "Παρακαλούμε εισάγετε όνομα άγκυρας",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Δεν υπάρχει στο λεξικό",\r
+DlgSpellChangeTo               : "Αλλαγή σε",\r
+DlgSpellBtnIgnore              : "Αγνόηση",\r
+DlgSpellBtnIgnoreAll   : "Αγνόηση όλων",\r
+DlgSpellBtnReplace             : "Αντικατάσταση",\r
+DlgSpellBtnReplaceAll  : "Αντικατάσταση όλων",\r
+DlgSpellBtnUndo                        : "Αναίρεση",\r
+DlgSpellNoSuggestions  : "- Δεν υπάρχουν προτάσεις -",\r
+DlgSpellProgress               : "Ορθογραφικός έλεγχος σε εξέλιξη...",\r
+DlgSpellNoMispell              : "Ο ορθογραφικός έλεγχος ολοκληρώθηκε: Δεν βρέθηκαν λάθη",\r
+DlgSpellNoChanges              : "Ο ορθογραφικός έλεγχος ολοκληρώθηκε: Δεν άλλαξαν λέξεις",\r
+DlgSpellOneChange              : "Ο ορθογραφικός έλεγχος ολοκληρώθηκε: Μια λέξη άλλαξε",\r
+DlgSpellManyChanges            : "Ο ορθογραφικός έλεγχος ολοκληρώθηκε: %1 λέξεις άλλαξαν",\r
+\r
+IeSpellDownload                        : "Δεν υπάρχει εγκατεστημένος ορθογράφος. Θέλετε να τον κατεβάσετε τώρα;",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Κείμενο (Τιμή)",\r
+DlgButtonType          : "Τύπος",\r
+DlgButtonTypeBtn       : "Κουμπί",\r
+DlgButtonTypeSbm       : "Καταχώρηση",\r
+DlgButtonTypeRst       : "Επαναφορά",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Όνομα",\r
+DlgCheckboxValue       : "Τιμή",\r
+DlgCheckboxSelected    : "Επιλεγμένο",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Όνομα",\r
+DlgFormAction  : "Δράση",\r
+DlgFormMethod  : "Μάθοδος",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Όνομα",\r
+DlgSelectValue         : "Τιμή",\r
+DlgSelectSize          : "Μέγεθος",\r
+DlgSelectLines         : "γραμμές",\r
+DlgSelectChkMulti      : "Πολλαπλές επιλογές",\r
+DlgSelectOpAvail       : "Διαθέσιμες επιλογές",\r
+DlgSelectOpText                : "Κείμενο",\r
+DlgSelectOpValue       : "Τιμή",\r
+DlgSelectBtnAdd                : "Προσθήκη",\r
+DlgSelectBtnModify     : "Αλλαγή",\r
+DlgSelectBtnUp         : "Πάνω",\r
+DlgSelectBtnDown       : "Κάτω",\r
+DlgSelectBtnSetValue : "Προεπιλεγμένη επιλογή",\r
+DlgSelectBtnDelete     : "Διαγραφή",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Όνομα",\r
+DlgTextareaCols        : "Στήλες",\r
+DlgTextareaRows        : "Σειρές",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Όνομα",\r
+DlgTextValue           : "Τιμή",\r
+DlgTextCharWidth       : "Μήκος χαρακτήρων",\r
+DlgTextMaxChars                : "Μέγιστοι χαρακτήρες",\r
+DlgTextType                    : "Τύπος",\r
+DlgTextTypeText                : "Κείμενο",\r
+DlgTextTypePass                : "Κωδικός",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Όνομα",\r
+DlgHiddenValue : "Τιμή",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Ιδιότητες λίστας Bulleted",\r
+NumberedListProp       : "Ιδιότητες αριθμημένης λίστας ",\r
+DlgLstStart                    : "Αρχή",\r
+DlgLstType                     : "Τύπος",\r
+DlgLstTypeCircle       : "Κύκλος",\r
+DlgLstTypeDisc         : "Δίσκος",\r
+DlgLstTypeSquare       : "Τετράγωνο",\r
+DlgLstTypeNumbers      : "Αριθμοί (1, 2, 3)",\r
+DlgLstTypeLCase                : "Πεζά γράμματα (a, b, c)",\r
+DlgLstTypeUCase                : "Κεφαλαία γράμματα (A, B, C)",\r
+DlgLstTypeSRoman       : "Μικρά λατινικά αριθμητικά (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Μεγάλα λατινικά αριθμητικά (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Γενικά",\r
+DlgDocBackTab          : "Φόντο",\r
+DlgDocColorsTab                : "Χρώματα και περιθώρια",\r
+DlgDocMetaTab          : "Δεδομένα Meta",\r
+\r
+DlgDocPageTitle                : "Τίτλος σελίδας",\r
+DlgDocLangDir          : "Κατεύθυνση γραφής",\r
+DlgDocLangDirLTR       : "αριστερά προς δεξιά (LTR)",\r
+DlgDocLangDirRTL       : "δεξιά προς αριστερά (RTL)",\r
+DlgDocLangCode         : "Κωδικός γλώσσας",\r
+DlgDocCharSet          : "Κωδικοποίηση χαρακτήρων",\r
+DlgDocCharSetCE                : "Κεντρικής Ευρώπης",\r
+DlgDocCharSetCT                : "Παραδοσιακά κινέζικα (Big5)",\r
+DlgDocCharSetCR                : "Κυριλλική",\r
+DlgDocCharSetGR                : "Ελληνική",\r
+DlgDocCharSetJP                : "Ιαπωνική",\r
+DlgDocCharSetKR                : "Κορεάτικη",\r
+DlgDocCharSetTR                : "Τουρκική",\r
+DlgDocCharSetUN                : "Διεθνής (UTF-8)",\r
+DlgDocCharSetWE                : "Δυτικής Ευρώπης",\r
+DlgDocCharSetOther     : "Άλλη κωδικοποίηση χαρακτήρων",\r
+\r
+DlgDocDocType          : "Επικεφαλίδα τύπου εγγράφου",\r
+DlgDocDocTypeOther     : "Άλλη επικεφαλίδα τύπου εγγράφου",\r
+DlgDocIncXHTML         : "Να συμπεριληφθούν οι δηλώσεις XHTML",\r
+DlgDocBgColor          : "Χρώμα φόντου",\r
+DlgDocBgImage          : "Διεύθυνση εικόνας φόντου",\r
+DlgDocBgNoScroll       : "Φόντο χωρίς κύλιση",\r
+DlgDocCText                    : "Κείμενο",\r
+DlgDocCLink                    : "Σύνδεσμος",\r
+DlgDocCVisited         : "Σύνδεσμος που έχει επισκευθεί",\r
+DlgDocCActive          : "Ενεργός σύνδεσμος",\r
+DlgDocMargins          : "Περιθώρια σελίδας",\r
+DlgDocMaTop                    : "Κορυφή",\r
+DlgDocMaLeft           : "Αριστερά",\r
+DlgDocMaRight          : "Δεξιά",\r
+DlgDocMaBottom         : "Κάτω",\r
+DlgDocMeIndex          : "Λέξεις κλειδιά δείκτες εγγράφου (διαχωρισμός με κόμμα)",\r
+DlgDocMeDescr          : "Περιγραφή εγγράφου",\r
+DlgDocMeAuthor         : "Συγγραφέας",\r
+DlgDocMeCopy           : "Πνευματικά δικαιώματα",\r
+DlgDocPreview          : "Προεπισκόπηση",\r
+\r
+// Templates Dialog\r
+Templates                      : "Πρότυπα",\r
+DlgTemplatesTitle      : "Πρότυπα περιεχομένου",\r
+DlgTemplatesSelMsg     : "Παρακαλώ επιλέξτε πρότυπο για εισαγωγή στο πρόγραμμα<br>(τα υπάρχοντα περιεχόμενα θα χαθούν):",\r
+DlgTemplatesLoading    : "Φόρτωση καταλόγου προτύπων. Παρακαλώ περιμένετε...",\r
+DlgTemplatesNoTpl      : "(Δεν έχουν καθοριστεί πρότυπα)",\r
+DlgTemplatesReplace    : "Αντικατάσταση υπάρχοντων περιεχομένων",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Σχετικά",\r
+DlgAboutBrowserInfoTab : "Πληροφορίες Browser",\r
+DlgAboutLicenseTab     : "Άδεια",\r
+DlgAboutVersion                : "έκδοση",\r
+DlgAboutInfo           : "Για περισσότερες πληροφορίες"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/en-au.js b/httemplate/elements/fckeditor/editor/lang/en-au.js
new file mode 100644 (file)
index 0000000..b6960b6
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * English (Australia) language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Collapse Toolbar",\r
+ToolbarExpand          : "Expand Toolbar",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Save",\r
+NewPage                                : "New Page",\r
+Preview                                : "Preview",\r
+Cut                                    : "Cut",\r
+Copy                           : "Copy",\r
+Paste                          : "Paste",\r
+PasteText                      : "Paste as plain text",\r
+PasteWord                      : "Paste from Word",\r
+Print                          : "Print",\r
+SelectAll                      : "Select All",\r
+RemoveFormat           : "Remove Format",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Insert/Edit Link",\r
+RemoveLink                     : "Remove Link",\r
+Anchor                         : "Insert/Edit Anchor",\r
+InsertImageLbl         : "Image",\r
+InsertImage                    : "Insert/Edit Image",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Insert/Edit Flash",\r
+InsertTableLbl         : "Table",\r
+InsertTable                    : "Insert/Edit Table",\r
+InsertLineLbl          : "Line",\r
+InsertLine                     : "Insert Horizontal Line",\r
+InsertSpecialCharLbl: "Special Character",\r
+InsertSpecialChar      : "Insert Special Character",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Insert Smiley",\r
+About                          : "About FCKeditor",\r
+Bold                           : "Bold",\r
+Italic                         : "Italic",\r
+Underline                      : "Underline",\r
+StrikeThrough          : "Strike Through",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Left Justify",\r
+CenterJustify          : "Centre Justify",\r
+RightJustify           : "Right Justify",\r
+BlockJustify           : "Block Justify",\r
+DecreaseIndent         : "Decrease Indent",\r
+IncreaseIndent         : "Increase Indent",\r
+Undo                           : "Undo",\r
+Redo                           : "Redo",\r
+NumberedListLbl                : "Numbered List",\r
+NumberedList           : "Insert/Remove Numbered List",\r
+BulletedListLbl                : "Bulleted List",\r
+BulletedList           : "Insert/Remove Bulleted List",\r
+ShowTableBorders       : "Show Table Borders",\r
+ShowDetails                    : "Show Details",\r
+Style                          : "Style",\r
+FontFormat                     : "Format",\r
+Font                           : "Font",\r
+FontSize                       : "Size",\r
+TextColor                      : "Text Colour",\r
+BGColor                                : "Background Colour",\r
+Source                         : "Source",\r
+Find                           : "Find",\r
+Replace                                : "Replace",\r
+SpellCheck                     : "Check Spelling",\r
+UniversalKeyboard      : "Universal Keyboard",\r
+PageBreakLbl           : "Page Break",\r
+PageBreak                      : "Insert Page Break",\r
+\r
+Form                   : "Form",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Radio Button",\r
+TextField              : "Text Field",\r
+Textarea               : "Textarea",\r
+HiddenField            : "Hidden Field",\r
+Button                 : "Button",\r
+SelectionField : "Selection Field",\r
+ImageButton            : "Image Button",\r
+\r
+FitWindow              : "Maximize the editor size",\r
+\r
+// Context Menu\r
+EditLink                       : "Edit Link",\r
+CellCM                         : "Cell",\r
+RowCM                          : "Row",\r
+ColumnCM                       : "Column",\r
+InsertRow                      : "Insert Row",\r
+DeleteRows                     : "Delete Rows",\r
+InsertColumn           : "Insert Column",\r
+DeleteColumns          : "Delete Columns",\r
+InsertCell                     : "Insert Cell",\r
+DeleteCells                    : "Delete Cells",\r
+MergeCells                     : "Merge Cells",\r
+SplitCell                      : "Split Cell",\r
+TableDelete                    : "Delete Table",\r
+CellProperties         : "Cell Properties",\r
+TableProperties                : "Table Properties",\r
+ImageProperties                : "Image Properties",\r
+FlashProperties                : "Flash Properties",\r
+\r
+AnchorProp                     : "Anchor Properties",\r
+ButtonProp                     : "Button Properties",\r
+CheckboxProp           : "Checkbox Properties",\r
+HiddenFieldProp                : "Hidden Field Properties",\r
+RadioButtonProp                : "Radio Button Properties",\r
+ImageButtonProp                : "Image Button Properties",\r
+TextFieldProp          : "Text Field Properties",\r
+SelectionFieldProp     : "Selection Field Properties",\r
+TextareaProp           : "Textarea Properties",\r
+FormProp                       : "Form Properties",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Processing XHTML. Please wait...",\r
+Done                           : "Done",\r
+PasteWordConfirm       : "The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?",\r
+NotCompatiblePaste     : "This command is available for Internet Explorer version 5.5 or more. Do you want to paste without cleaning?",\r
+UnknownToolbarItem     : "Unknown toolbar item \"%1\"",\r
+UnknownCommand         : "Unknown command name \"%1\"",\r
+NotImplemented         : "Command not implemented",\r
+UnknownToolbarSet      : "Toolbar set \"%1\" doesn't exist",\r
+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.",\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Cancel",\r
+DlgBtnClose                    : "Close",\r
+DlgBtnBrowseServer     : "Browse Server",\r
+DlgAdvancedTag         : "Advanced",\r
+DlgOpOther                     : "<Other>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Please insert the URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<not set>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Language Direction",\r
+DlgGenLangDirLtr       : "Left to Right (LTR)",\r
+DlgGenLangDirRtl       : "Right to Left (RTL)",\r
+DlgGenLangCode         : "Language Code",\r
+DlgGenAccessKey                : "Access Key",\r
+DlgGenName                     : "Name",\r
+DlgGenTabIndex         : "Tab Index",\r
+DlgGenLongDescr                : "Long Description URL",\r
+DlgGenClass                    : "Stylesheet Classes",\r
+DlgGenTitle                    : "Advisory Title",\r
+DlgGenContType         : "Advisory Content Type",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Style",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Image Properties",\r
+DlgImgInfoTab          : "Image Info",\r
+DlgImgBtnUpload                : "Send it to the Server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Upload",\r
+DlgImgAlt                      : "Alternative Text",\r
+DlgImgWidth                    : "Width",\r
+DlgImgHeight           : "Height",\r
+DlgImgLockRatio                : "Lock Ratio",\r
+DlgBtnResetSize                : "Reset Size",\r
+DlgImgBorder           : "Border",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Align",\r
+DlgImgAlignLeft                : "Left",\r
+DlgImgAlignAbsBottom: "Abs Bottom",\r
+DlgImgAlignAbsMiddle: "Abs Middle",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Bottom",\r
+DlgImgAlignMiddle      : "Middle",\r
+DlgImgAlignRight       : "Right",\r
+DlgImgAlignTextTop     : "Text Top",\r
+DlgImgAlignTop         : "Top",\r
+DlgImgPreview          : "Preview",\r
+DlgImgAlertUrl         : "Please type the image URL",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Properties",\r
+DlgFlashChkPlay                : "Auto Play",\r
+DlgFlashChkLoop                : "Loop",\r
+DlgFlashChkMenu                : "Enable Flash Menu",\r
+DlgFlashScale          : "Scale",\r
+DlgFlashScaleAll       : "Show all",\r
+DlgFlashScaleNoBorder  : "No Border",\r
+DlgFlashScaleFit       : "Exact Fit",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Link Info",\r
+DlgLnkTargetTab                : "Target",\r
+\r
+DlgLnkType                     : "Link Type",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Link to anchor in the text",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocol",\r
+DlgLnkProtoOther       : "<other>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Select an Anchor",\r
+DlgLnkAnchorByName     : "By Anchor Name",\r
+DlgLnkAnchorById       : "By Element Id",\r
+DlgLnkNoAnchors                : "(No anchors available in the document)",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail Address",\r
+DlgLnkEMailSubject     : "Message Subject",\r
+DlgLnkEMailBody                : "Message Body",\r
+DlgLnkUpload           : "Upload",\r
+DlgLnkBtnUpload                : "Send it to the Server",\r
+\r
+DlgLnkTarget           : "Target",\r
+DlgLnkTargetFrame      : "<frame>",\r
+DlgLnkTargetPopup      : "<popup window>",\r
+DlgLnkTargetBlank      : "New Window (_blank)",\r
+DlgLnkTargetParent     : "Parent Window (_parent)",\r
+DlgLnkTargetSelf       : "Same Window (_self)",\r
+DlgLnkTargetTop                : "Topmost Window (_top)",\r
+DlgLnkTargetFrameName  : "Target Frame Name",\r
+DlgLnkPopWinName       : "Popup Window Name",\r
+DlgLnkPopWinFeat       : "Popup Window Features",\r
+DlgLnkPopResize                : "Resizable",\r
+DlgLnkPopLocation      : "Location Bar",\r
+DlgLnkPopMenu          : "Menu Bar",\r
+DlgLnkPopScroll                : "Scroll Bars",\r
+DlgLnkPopStatus                : "Status Bar",\r
+DlgLnkPopToolbar       : "Toolbar",\r
+DlgLnkPopFullScrn      : "Full Screen (IE)",\r
+DlgLnkPopDependent     : "Dependent (Netscape)",\r
+DlgLnkPopWidth         : "Width",\r
+DlgLnkPopHeight                : "Height",\r
+DlgLnkPopLeft          : "Left Position",\r
+DlgLnkPopTop           : "Top Position",\r
+\r
+DlnLnkMsgNoUrl         : "Please type the link URL",\r
+DlnLnkMsgNoEMail       : "Please type the e-mail address",\r
+DlnLnkMsgNoAnchor      : "Please select an anchor",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Select Colour",\r
+DlgColorBtnClear       : "Clear",\r
+DlgColorHighlight      : "Highlight",\r
+DlgColorSelected       : "Selected",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Insert a Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Select Special Character",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Table Properties",\r
+DlgTableRows           : "Rows",\r
+DlgTableColumns                : "Columns",\r
+DlgTableBorder         : "Border size",\r
+DlgTableAlign          : "Alignment",\r
+DlgTableAlignNotSet    : "<Not set>",\r
+DlgTableAlignLeft      : "Left",\r
+DlgTableAlignCenter    : "Centre",\r
+DlgTableAlignRight     : "Right",\r
+DlgTableWidth          : "Width",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "percent",\r
+DlgTableHeight         : "Height",\r
+DlgTableCellSpace      : "Cell spacing",\r
+DlgTableCellPad                : "Cell padding",\r
+DlgTableCaption                : "Caption",\r
+DlgTableSummary                : "Summary",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Cell Properties",\r
+DlgCellWidth           : "Width",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "percent",\r
+DlgCellHeight          : "Height",\r
+DlgCellWordWrap                : "Word Wrap",\r
+DlgCellWordWrapNotSet  : "<Not set>",\r
+DlgCellWordWrapYes     : "Yes",\r
+DlgCellWordWrapNo      : "No",\r
+DlgCellHorAlign                : "Horizontal Alignment",\r
+DlgCellHorAlignNotSet  : "<Not set>",\r
+DlgCellHorAlignLeft    : "Left",\r
+DlgCellHorAlignCenter  : "Centre",\r
+DlgCellHorAlignRight: "Right",\r
+DlgCellVerAlign                : "Vertical Alignment",\r
+DlgCellVerAlignNotSet  : "<Not set>",\r
+DlgCellVerAlignTop     : "Top",\r
+DlgCellVerAlignMiddle  : "Middle",\r
+DlgCellVerAlignBottom  : "Bottom",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Rows Span",\r
+DlgCellCollSpan                : "Columns Span",\r
+DlgCellBackColor       : "Background Colour",\r
+DlgCellBorderColor     : "Border Colour",\r
+DlgCellBtnSelect       : "Select...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Find",\r
+DlgFindFindBtn         : "Find",\r
+DlgFindNotFoundMsg     : "The specified text was not found.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Replace",\r
+DlgReplaceFindLbl              : "Find what:",\r
+DlgReplaceReplaceLbl   : "Replace with:",\r
+DlgReplaceCaseChk              : "Match case",\r
+DlgReplaceReplaceBtn   : "Replace",\r
+DlgReplaceReplAllBtn   : "Replace All",\r
+DlgReplaceWordChk              : "Match whole word",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl+X).",\r
+PasteErrorCopy : "Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl+C).",\r
+\r
+PasteAsText            : "Paste as Plain Text",\r
+PasteFromWord  : "Paste from Word",\r
+\r
+DlgPasteMsg2   : "Please paste inside the following box using the keyboard (<STRONG>Ctrl+V</STRONG>) and hit <STRONG>OK</STRONG>.",\r
+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.",\r
+DlgPasteIgnoreFont             : "Ignore Font Face definitions",\r
+DlgPasteRemoveStyles   : "Remove Styles definitions",\r
+DlgPasteCleanBox               : "Clean Up Box",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatic",\r
+ColorMoreColors        : "More Colours...",\r
+\r
+// Document Properties\r
+DocProps               : "Document Properties",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Anchor Properties",\r
+DlgAnchorName          : "Anchor Name",\r
+DlgAnchorErrorName     : "Please type the anchor name",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Not in dictionary",\r
+DlgSpellChangeTo               : "Change to",\r
+DlgSpellBtnIgnore              : "Ignore",\r
+DlgSpellBtnIgnoreAll   : "Ignore All",\r
+DlgSpellBtnReplace             : "Replace",\r
+DlgSpellBtnReplaceAll  : "Replace All",\r
+DlgSpellBtnUndo                        : "Undo",\r
+DlgSpellNoSuggestions  : "- No suggestions -",\r
+DlgSpellProgress               : "Spell check in progress...",\r
+DlgSpellNoMispell              : "Spell check complete: No misspellings found",\r
+DlgSpellNoChanges              : "Spell check complete: No words changed",\r
+DlgSpellOneChange              : "Spell check complete: One word changed",\r
+DlgSpellManyChanges            : "Spell check complete: %1 words changed",\r
+\r
+IeSpellDownload                        : "Spell checker not installed. Do you want to download it now?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Value)",\r
+DlgButtonType          : "Type",\r
+DlgButtonTypeBtn       : "Button",\r
+DlgButtonTypeSbm       : "Submit",\r
+DlgButtonTypeRst       : "Reset",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Name",\r
+DlgCheckboxValue       : "Value",\r
+DlgCheckboxSelected    : "Selected",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Name",\r
+DlgFormAction  : "Action",\r
+DlgFormMethod  : "Method",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Name",\r
+DlgSelectValue         : "Value",\r
+DlgSelectSize          : "Size",\r
+DlgSelectLines         : "lines",\r
+DlgSelectChkMulti      : "Allow multiple selections",\r
+DlgSelectOpAvail       : "Available Options",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Value",\r
+DlgSelectBtnAdd                : "Add",\r
+DlgSelectBtnModify     : "Modify",\r
+DlgSelectBtnUp         : "Up",\r
+DlgSelectBtnDown       : "Down",\r
+DlgSelectBtnSetValue : "Set as selected value",\r
+DlgSelectBtnDelete     : "Delete",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Name",\r
+DlgTextareaCols        : "Columns",\r
+DlgTextareaRows        : "Rows",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Name",\r
+DlgTextValue           : "Value",\r
+DlgTextCharWidth       : "Character Width",\r
+DlgTextMaxChars                : "Maximum Characters",\r
+DlgTextType                    : "Type",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Password",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Name",\r
+DlgHiddenValue : "Value",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Bulleted List Properties",\r
+NumberedListProp       : "Numbered List Properties",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Type",\r
+DlgLstTypeCircle       : "Circle",\r
+DlgLstTypeDisc         : "Disc",\r
+DlgLstTypeSquare       : "Square",\r
+DlgLstTypeNumbers      : "Numbers (1, 2, 3)",\r
+DlgLstTypeLCase                : "Lowercase Letters (a, b, c)",\r
+DlgLstTypeUCase                : "Uppercase Letters (A, B, C)",\r
+DlgLstTypeSRoman       : "Small Roman Numerals (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Large Roman Numerals (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",\r
+DlgDocBackTab          : "Background",\r
+DlgDocColorsTab                : "Colours and Margins",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Page Title",\r
+DlgDocLangDir          : "Language Direction",\r
+DlgDocLangDirLTR       : "Left to Right (LTR)",\r
+DlgDocLangDirRTL       : "Right to Left (RTL)",\r
+DlgDocLangCode         : "Language Code",\r
+DlgDocCharSet          : "Character Set Encoding",\r
+DlgDocCharSetCE                : "Central European",\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Greek",\r
+DlgDocCharSetJP                : "Japanese",\r
+DlgDocCharSetKR                : "Korean",\r
+DlgDocCharSetTR                : "Turkish",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Western European",\r
+DlgDocCharSetOther     : "Other Character Set Encoding",\r
+\r
+DlgDocDocType          : "Document Type Heading",\r
+DlgDocDocTypeOther     : "Other Document Type Heading",\r
+DlgDocIncXHTML         : "Include XHTML Declarations",\r
+DlgDocBgColor          : "Background Colour",\r
+DlgDocBgImage          : "Background Image URL",\r
+DlgDocBgNoScroll       : "Nonscrolling Background",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Visited Link",\r
+DlgDocCActive          : "Active Link",\r
+DlgDocMargins          : "Page Margins",\r
+DlgDocMaTop                    : "Top",\r
+DlgDocMaLeft           : "Left",\r
+DlgDocMaRight          : "Right",\r
+DlgDocMaBottom         : "Bottom",\r
+DlgDocMeIndex          : "Document Indexing Keywords (comma separated)",\r
+DlgDocMeDescr          : "Document Description",\r
+DlgDocMeAuthor         : "Author",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Preview",\r
+\r
+// Templates Dialog\r
+Templates                      : "Templates",\r
+DlgTemplatesTitle      : "Content Templates",\r
+DlgTemplatesSelMsg     : "Please select the template to open in the editor<br>(the actual contents will be lost):",\r
+DlgTemplatesLoading    : "Loading templates list. Please wait...",\r
+DlgTemplatesNoTpl      : "(No templates defined)",\r
+DlgTemplatesReplace    : "Replace actual contents",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "About",\r
+DlgAboutBrowserInfoTab : "Browser Info",\r
+DlgAboutLicenseTab     : "License",\r
+DlgAboutVersion                : "version",\r
+DlgAboutInfo           : "For further information go to"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/en-ca.js b/httemplate/elements/fckeditor/editor/lang/en-ca.js
new file mode 100644 (file)
index 0000000..2900a9d
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * English (Canadian) language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Collapse Toolbar",\r
+ToolbarExpand          : "Expand Toolbar",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Save",\r
+NewPage                                : "New Page",\r
+Preview                                : "Preview",\r
+Cut                                    : "Cut",\r
+Copy                           : "Copy",\r
+Paste                          : "Paste",\r
+PasteText                      : "Paste as plain text",\r
+PasteWord                      : "Paste from Word",\r
+Print                          : "Print",\r
+SelectAll                      : "Select All",\r
+RemoveFormat           : "Remove Format",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Insert/Edit Link",\r
+RemoveLink                     : "Remove Link",\r
+Anchor                         : "Insert/Edit Anchor",\r
+InsertImageLbl         : "Image",\r
+InsertImage                    : "Insert/Edit Image",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Insert/Edit Flash",\r
+InsertTableLbl         : "Table",\r
+InsertTable                    : "Insert/Edit Table",\r
+InsertLineLbl          : "Line",\r
+InsertLine                     : "Insert Horizontal Line",\r
+InsertSpecialCharLbl: "Special Character",\r
+InsertSpecialChar      : "Insert Special Character",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Insert Smiley",\r
+About                          : "About FCKeditor",\r
+Bold                           : "Bold",\r
+Italic                         : "Italic",\r
+Underline                      : "Underline",\r
+StrikeThrough          : "Strike Through",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Left Justify",\r
+CenterJustify          : "Centre Justify",\r
+RightJustify           : "Right Justify",\r
+BlockJustify           : "Block Justify",\r
+DecreaseIndent         : "Decrease Indent",\r
+IncreaseIndent         : "Increase Indent",\r
+Undo                           : "Undo",\r
+Redo                           : "Redo",\r
+NumberedListLbl                : "Numbered List",\r
+NumberedList           : "Insert/Remove Numbered List",\r
+BulletedListLbl                : "Bulleted List",\r
+BulletedList           : "Insert/Remove Bulleted List",\r
+ShowTableBorders       : "Show Table Borders",\r
+ShowDetails                    : "Show Details",\r
+Style                          : "Style",\r
+FontFormat                     : "Format",\r
+Font                           : "Font",\r
+FontSize                       : "Size",\r
+TextColor                      : "Text Colour",\r
+BGColor                                : "Background Colour",\r
+Source                         : "Source",\r
+Find                           : "Find",\r
+Replace                                : "Replace",\r
+SpellCheck                     : "Check Spelling",\r
+UniversalKeyboard      : "Universal Keyboard",\r
+PageBreakLbl           : "Page Break",\r
+PageBreak                      : "Insert Page Break",\r
+\r
+Form                   : "Form",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Radio Button",\r
+TextField              : "Text Field",\r
+Textarea               : "Textarea",\r
+HiddenField            : "Hidden Field",\r
+Button                 : "Button",\r
+SelectionField : "Selection Field",\r
+ImageButton            : "Image Button",\r
+\r
+FitWindow              : "Maximize the editor size",\r
+\r
+// Context Menu\r
+EditLink                       : "Edit Link",\r
+CellCM                         : "Cell",\r
+RowCM                          : "Row",\r
+ColumnCM                       : "Column",\r
+InsertRow                      : "Insert Row",\r
+DeleteRows                     : "Delete Rows",\r
+InsertColumn           : "Insert Column",\r
+DeleteColumns          : "Delete Columns",\r
+InsertCell                     : "Insert Cell",\r
+DeleteCells                    : "Delete Cells",\r
+MergeCells                     : "Merge Cells",\r
+SplitCell                      : "Split Cell",\r
+TableDelete                    : "Delete Table",\r
+CellProperties         : "Cell Properties",\r
+TableProperties                : "Table Properties",\r
+ImageProperties                : "Image Properties",\r
+FlashProperties                : "Flash Properties",\r
+\r
+AnchorProp                     : "Anchor Properties",\r
+ButtonProp                     : "Button Properties",\r
+CheckboxProp           : "Checkbox Properties",\r
+HiddenFieldProp                : "Hidden Field Properties",\r
+RadioButtonProp                : "Radio Button Properties",\r
+ImageButtonProp                : "Image Button Properties",\r
+TextFieldProp          : "Text Field Properties",\r
+SelectionFieldProp     : "Selection Field Properties",\r
+TextareaProp           : "Textarea Properties",\r
+FormProp                       : "Form Properties",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Processing XHTML. Please wait...",\r
+Done                           : "Done",\r
+PasteWordConfirm       : "The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?",\r
+NotCompatiblePaste     : "This command is available for Internet Explorer version 5.5 or more. Do you want to paste without cleaning?",\r
+UnknownToolbarItem     : "Unknown toolbar item \"%1\"",\r
+UnknownCommand         : "Unknown command name \"%1\"",\r
+NotImplemented         : "Command not implemented",\r
+UnknownToolbarSet      : "Toolbar set \"%1\" doesn't exist",\r
+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.",\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Cancel",\r
+DlgBtnClose                    : "Close",\r
+DlgBtnBrowseServer     : "Browse Server",\r
+DlgAdvancedTag         : "Advanced",\r
+DlgOpOther                     : "<Other>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Please insert the URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<not set>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Language Direction",\r
+DlgGenLangDirLtr       : "Left to Right (LTR)",\r
+DlgGenLangDirRtl       : "Right to Left (RTL)",\r
+DlgGenLangCode         : "Language Code",\r
+DlgGenAccessKey                : "Access Key",\r
+DlgGenName                     : "Name",\r
+DlgGenTabIndex         : "Tab Index",\r
+DlgGenLongDescr                : "Long Description URL",\r
+DlgGenClass                    : "Stylesheet Classes",\r
+DlgGenTitle                    : "Advisory Title",\r
+DlgGenContType         : "Advisory Content Type",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Style",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Image Properties",\r
+DlgImgInfoTab          : "Image Info",\r
+DlgImgBtnUpload                : "Send it to the Server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Upload",\r
+DlgImgAlt                      : "Alternative Text",\r
+DlgImgWidth                    : "Width",\r
+DlgImgHeight           : "Height",\r
+DlgImgLockRatio                : "Lock Ratio",\r
+DlgBtnResetSize                : "Reset Size",\r
+DlgImgBorder           : "Border",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Align",\r
+DlgImgAlignLeft                : "Left",\r
+DlgImgAlignAbsBottom: "Abs Bottom",\r
+DlgImgAlignAbsMiddle: "Abs Middle",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Bottom",\r
+DlgImgAlignMiddle      : "Middle",\r
+DlgImgAlignRight       : "Right",\r
+DlgImgAlignTextTop     : "Text Top",\r
+DlgImgAlignTop         : "Top",\r
+DlgImgPreview          : "Preview",\r
+DlgImgAlertUrl         : "Please type the image URL",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Properties",\r
+DlgFlashChkPlay                : "Auto Play",\r
+DlgFlashChkLoop                : "Loop",\r
+DlgFlashChkMenu                : "Enable Flash Menu",\r
+DlgFlashScale          : "Scale",\r
+DlgFlashScaleAll       : "Show all",\r
+DlgFlashScaleNoBorder  : "No Border",\r
+DlgFlashScaleFit       : "Exact Fit",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Link Info",\r
+DlgLnkTargetTab                : "Target",\r
+\r
+DlgLnkType                     : "Link Type",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Link to anchor in the text",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocol",\r
+DlgLnkProtoOther       : "<other>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Select an Anchor",\r
+DlgLnkAnchorByName     : "By Anchor Name",\r
+DlgLnkAnchorById       : "By Element Id",\r
+DlgLnkNoAnchors                : "(No anchors available in the document)",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail Address",\r
+DlgLnkEMailSubject     : "Message Subject",\r
+DlgLnkEMailBody                : "Message Body",\r
+DlgLnkUpload           : "Upload",\r
+DlgLnkBtnUpload                : "Send it to the Server",\r
+\r
+DlgLnkTarget           : "Target",\r
+DlgLnkTargetFrame      : "<frame>",\r
+DlgLnkTargetPopup      : "<popup window>",\r
+DlgLnkTargetBlank      : "New Window (_blank)",\r
+DlgLnkTargetParent     : "Parent Window (_parent)",\r
+DlgLnkTargetSelf       : "Same Window (_self)",\r
+DlgLnkTargetTop                : "Topmost Window (_top)",\r
+DlgLnkTargetFrameName  : "Target Frame Name",\r
+DlgLnkPopWinName       : "Popup Window Name",\r
+DlgLnkPopWinFeat       : "Popup Window Features",\r
+DlgLnkPopResize                : "Resizable",\r
+DlgLnkPopLocation      : "Location Bar",\r
+DlgLnkPopMenu          : "Menu Bar",\r
+DlgLnkPopScroll                : "Scroll Bars",\r
+DlgLnkPopStatus                : "Status Bar",\r
+DlgLnkPopToolbar       : "Toolbar",\r
+DlgLnkPopFullScrn      : "Full Screen (IE)",\r
+DlgLnkPopDependent     : "Dependent (Netscape)",\r
+DlgLnkPopWidth         : "Width",\r
+DlgLnkPopHeight                : "Height",\r
+DlgLnkPopLeft          : "Left Position",\r
+DlgLnkPopTop           : "Top Position",\r
+\r
+DlnLnkMsgNoUrl         : "Please type the link URL",\r
+DlnLnkMsgNoEMail       : "Please type the e-mail address",\r
+DlnLnkMsgNoAnchor      : "Please select an anchor",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Select Colour",\r
+DlgColorBtnClear       : "Clear",\r
+DlgColorHighlight      : "Highlight",\r
+DlgColorSelected       : "Selected",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Insert a Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Select Special Character",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Table Properties",\r
+DlgTableRows           : "Rows",\r
+DlgTableColumns                : "Columns",\r
+DlgTableBorder         : "Border size",\r
+DlgTableAlign          : "Alignment",\r
+DlgTableAlignNotSet    : "<Not set>",\r
+DlgTableAlignLeft      : "Left",\r
+DlgTableAlignCenter    : "Centre",\r
+DlgTableAlignRight     : "Right",\r
+DlgTableWidth          : "Width",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "percent",\r
+DlgTableHeight         : "Height",\r
+DlgTableCellSpace      : "Cell spacing",\r
+DlgTableCellPad                : "Cell padding",\r
+DlgTableCaption                : "Caption",\r
+DlgTableSummary                : "Summary",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Cell Properties",\r
+DlgCellWidth           : "Width",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "percent",\r
+DlgCellHeight          : "Height",\r
+DlgCellWordWrap                : "Word Wrap",\r
+DlgCellWordWrapNotSet  : "<Not set>",\r
+DlgCellWordWrapYes     : "Yes",\r
+DlgCellWordWrapNo      : "No",\r
+DlgCellHorAlign                : "Horizontal Alignment",\r
+DlgCellHorAlignNotSet  : "<Not set>",\r
+DlgCellHorAlignLeft    : "Left",\r
+DlgCellHorAlignCenter  : "Centre",\r
+DlgCellHorAlignRight: "Right",\r
+DlgCellVerAlign                : "Vertical Alignment",\r
+DlgCellVerAlignNotSet  : "<Not set>",\r
+DlgCellVerAlignTop     : "Top",\r
+DlgCellVerAlignMiddle  : "Middle",\r
+DlgCellVerAlignBottom  : "Bottom",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Rows Span",\r
+DlgCellCollSpan                : "Columns Span",\r
+DlgCellBackColor       : "Background Colour",\r
+DlgCellBorderColor     : "Border Colour",\r
+DlgCellBtnSelect       : "Select...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Find",\r
+DlgFindFindBtn         : "Find",\r
+DlgFindNotFoundMsg     : "The specified text was not found.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Replace",\r
+DlgReplaceFindLbl              : "Find what:",\r
+DlgReplaceReplaceLbl   : "Replace with:",\r
+DlgReplaceCaseChk              : "Match case",\r
+DlgReplaceReplaceBtn   : "Replace",\r
+DlgReplaceReplAllBtn   : "Replace All",\r
+DlgReplaceWordChk              : "Match whole word",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl+X).",\r
+PasteErrorCopy : "Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl+C).",\r
+\r
+PasteAsText            : "Paste as Plain Text",\r
+PasteFromWord  : "Paste from Word",\r
+\r
+DlgPasteMsg2   : "Please paste inside the following box using the keyboard (<STRONG>Ctrl+V</STRONG>) and hit <STRONG>OK</STRONG>.",\r
+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.",\r
+DlgPasteIgnoreFont             : "Ignore Font Face definitions",\r
+DlgPasteRemoveStyles   : "Remove Styles definitions",\r
+DlgPasteCleanBox               : "Clean Up Box",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatic",\r
+ColorMoreColors        : "More Colours...",\r
+\r
+// Document Properties\r
+DocProps               : "Document Properties",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Anchor Properties",\r
+DlgAnchorName          : "Anchor Name",\r
+DlgAnchorErrorName     : "Please type the anchor name",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Not in dictionary",\r
+DlgSpellChangeTo               : "Change to",\r
+DlgSpellBtnIgnore              : "Ignore",\r
+DlgSpellBtnIgnoreAll   : "Ignore All",\r
+DlgSpellBtnReplace             : "Replace",\r
+DlgSpellBtnReplaceAll  : "Replace All",\r
+DlgSpellBtnUndo                        : "Undo",\r
+DlgSpellNoSuggestions  : "- No suggestions -",\r
+DlgSpellProgress               : "Spell check in progress...",\r
+DlgSpellNoMispell              : "Spell check complete: No misspellings found",\r
+DlgSpellNoChanges              : "Spell check complete: No words changed",\r
+DlgSpellOneChange              : "Spell check complete: One word changed",\r
+DlgSpellManyChanges            : "Spell check complete: %1 words changed",\r
+\r
+IeSpellDownload                        : "Spell checker not installed. Do you want to download it now?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Value)",\r
+DlgButtonType          : "Type",\r
+DlgButtonTypeBtn       : "Button",\r
+DlgButtonTypeSbm       : "Submit",\r
+DlgButtonTypeRst       : "Reset",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Name",\r
+DlgCheckboxValue       : "Value",\r
+DlgCheckboxSelected    : "Selected",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Name",\r
+DlgFormAction  : "Action",\r
+DlgFormMethod  : "Method",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Name",\r
+DlgSelectValue         : "Value",\r
+DlgSelectSize          : "Size",\r
+DlgSelectLines         : "lines",\r
+DlgSelectChkMulti      : "Allow multiple selections",\r
+DlgSelectOpAvail       : "Available Options",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Value",\r
+DlgSelectBtnAdd                : "Add",\r
+DlgSelectBtnModify     : "Modify",\r
+DlgSelectBtnUp         : "Up",\r
+DlgSelectBtnDown       : "Down",\r
+DlgSelectBtnSetValue : "Set as selected value",\r
+DlgSelectBtnDelete     : "Delete",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Name",\r
+DlgTextareaCols        : "Columns",\r
+DlgTextareaRows        : "Rows",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Name",\r
+DlgTextValue           : "Value",\r
+DlgTextCharWidth       : "Character Width",\r
+DlgTextMaxChars                : "Maximum Characters",\r
+DlgTextType                    : "Type",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Password",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Name",\r
+DlgHiddenValue : "Value",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Bulleted List Properties",\r
+NumberedListProp       : "Numbered List Properties",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Type",\r
+DlgLstTypeCircle       : "Circle",\r
+DlgLstTypeDisc         : "Disc",\r
+DlgLstTypeSquare       : "Square",\r
+DlgLstTypeNumbers      : "Numbers (1, 2, 3)",\r
+DlgLstTypeLCase                : "Lowercase Letters (a, b, c)",\r
+DlgLstTypeUCase                : "Uppercase Letters (A, B, C)",\r
+DlgLstTypeSRoman       : "Small Roman Numerals (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Large Roman Numerals (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",\r
+DlgDocBackTab          : "Background",\r
+DlgDocColorsTab                : "Colours and Margins",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Page Title",\r
+DlgDocLangDir          : "Language Direction",\r
+DlgDocLangDirLTR       : "Left to Right (LTR)",\r
+DlgDocLangDirRTL       : "Right to Left (RTL)",\r
+DlgDocLangCode         : "Language Code",\r
+DlgDocCharSet          : "Character Set Encoding",\r
+DlgDocCharSetCE                : "Central European",\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Greek",\r
+DlgDocCharSetJP                : "Japanese",\r
+DlgDocCharSetKR                : "Korean",\r
+DlgDocCharSetTR                : "Turkish",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Western European",\r
+DlgDocCharSetOther     : "Other Character Set Encoding",\r
+\r
+DlgDocDocType          : "Document Type Heading",\r
+DlgDocDocTypeOther     : "Other Document Type Heading",\r
+DlgDocIncXHTML         : "Include XHTML Declarations",\r
+DlgDocBgColor          : "Background Colour",\r
+DlgDocBgImage          : "Background Image URL",\r
+DlgDocBgNoScroll       : "Nonscrolling Background",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Visited Link",\r
+DlgDocCActive          : "Active Link",\r
+DlgDocMargins          : "Page Margins",\r
+DlgDocMaTop                    : "Top",\r
+DlgDocMaLeft           : "Left",\r
+DlgDocMaRight          : "Right",\r
+DlgDocMaBottom         : "Bottom",\r
+DlgDocMeIndex          : "Document Indexing Keywords (comma separated)",\r
+DlgDocMeDescr          : "Document Description",\r
+DlgDocMeAuthor         : "Author",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Preview",\r
+\r
+// Templates Dialog\r
+Templates                      : "Templates",\r
+DlgTemplatesTitle      : "Content Templates",\r
+DlgTemplatesSelMsg     : "Please select the template to open in the editor<br>(the actual contents will be lost):",\r
+DlgTemplatesLoading    : "Loading templates list. Please wait...",\r
+DlgTemplatesNoTpl      : "(No templates defined)",\r
+DlgTemplatesReplace    : "Replace actual contents",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "About",\r
+DlgAboutBrowserInfoTab : "Browser Info",\r
+DlgAboutLicenseTab     : "License",\r
+DlgAboutVersion                : "version",\r
+DlgAboutInfo           : "For further information go to"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/en-uk.js b/httemplate/elements/fckeditor/editor/lang/en-uk.js
new file mode 100644 (file)
index 0000000..eaaf3e4
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * English (United Kingdom) language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Collapse Toolbar",\r
+ToolbarExpand          : "Expand Toolbar",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Save",\r
+NewPage                                : "New Page",\r
+Preview                                : "Preview",\r
+Cut                                    : "Cut",\r
+Copy                           : "Copy",\r
+Paste                          : "Paste",\r
+PasteText                      : "Paste as plain text",\r
+PasteWord                      : "Paste from Word",\r
+Print                          : "Print",\r
+SelectAll                      : "Select All",\r
+RemoveFormat           : "Remove Format",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Insert/Edit Link",\r
+RemoveLink                     : "Remove Link",\r
+Anchor                         : "Insert/Edit Anchor",\r
+InsertImageLbl         : "Image",\r
+InsertImage                    : "Insert/Edit Image",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Insert/Edit Flash",\r
+InsertTableLbl         : "Table",\r
+InsertTable                    : "Insert/Edit Table",\r
+InsertLineLbl          : "Line",\r
+InsertLine                     : "Insert Horizontal Line",\r
+InsertSpecialCharLbl: "Special Character",\r
+InsertSpecialChar      : "Insert Special Character",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Insert Smiley",\r
+About                          : "About FCKeditor",\r
+Bold                           : "Bold",\r
+Italic                         : "Italic",\r
+Underline                      : "Underline",\r
+StrikeThrough          : "Strike Through",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Left Justify",\r
+CenterJustify          : "Centre Justify",\r
+RightJustify           : "Right Justify",\r
+BlockJustify           : "Block Justify",\r
+DecreaseIndent         : "Decrease Indent",\r
+IncreaseIndent         : "Increase Indent",\r
+Undo                           : "Undo",\r
+Redo                           : "Redo",\r
+NumberedListLbl                : "Numbered List",\r
+NumberedList           : "Insert/Remove Numbered List",\r
+BulletedListLbl                : "Bulleted List",\r
+BulletedList           : "Insert/Remove Bulleted List",\r
+ShowTableBorders       : "Show Table Borders",\r
+ShowDetails                    : "Show Details",\r
+Style                          : "Style",\r
+FontFormat                     : "Format",\r
+Font                           : "Font",\r
+FontSize                       : "Size",\r
+TextColor                      : "Text Colour",\r
+BGColor                                : "Background Colour",\r
+Source                         : "Source",\r
+Find                           : "Find",\r
+Replace                                : "Replace",\r
+SpellCheck                     : "Check Spelling",\r
+UniversalKeyboard      : "Universal Keyboard",\r
+PageBreakLbl           : "Page Break",\r
+PageBreak                      : "Insert Page Break",\r
+\r
+Form                   : "Form",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Radio Button",\r
+TextField              : "Text Field",\r
+Textarea               : "Textarea",\r
+HiddenField            : "Hidden Field",\r
+Button                 : "Button",\r
+SelectionField : "Selection Field",\r
+ImageButton            : "Image Button",\r
+\r
+FitWindow              : "Maximize the editor size",\r
+\r
+// Context Menu\r
+EditLink                       : "Edit Link",\r
+CellCM                         : "Cell",\r
+RowCM                          : "Row",\r
+ColumnCM                       : "Column",\r
+InsertRow                      : "Insert Row",\r
+DeleteRows                     : "Delete Rows",\r
+InsertColumn           : "Insert Column",\r
+DeleteColumns          : "Delete Columns",\r
+InsertCell                     : "Insert Cell",\r
+DeleteCells                    : "Delete Cells",\r
+MergeCells                     : "Merge Cells",\r
+SplitCell                      : "Split Cell",\r
+TableDelete                    : "Delete Table",\r
+CellProperties         : "Cell Properties",\r
+TableProperties                : "Table Properties",\r
+ImageProperties                : "Image Properties",\r
+FlashProperties                : "Flash Properties",\r
+\r
+AnchorProp                     : "Anchor Properties",\r
+ButtonProp                     : "Button Properties",\r
+CheckboxProp           : "Checkbox Properties",\r
+HiddenFieldProp                : "Hidden Field Properties",\r
+RadioButtonProp                : "Radio Button Properties",\r
+ImageButtonProp                : "Image Button Properties",\r
+TextFieldProp          : "Text Field Properties",\r
+SelectionFieldProp     : "Selection Field Properties",\r
+TextareaProp           : "Textarea Properties",\r
+FormProp                       : "Form Properties",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Processing XHTML. Please wait...",\r
+Done                           : "Done",\r
+PasteWordConfirm       : "The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?",\r
+NotCompatiblePaste     : "This command is available for Internet Explorer version 5.5 or more. Do you want to paste without cleaning?",\r
+UnknownToolbarItem     : "Unknown toolbar item \"%1\"",\r
+UnknownCommand         : "Unknown command name \"%1\"",\r
+NotImplemented         : "Command not implemented",\r
+UnknownToolbarSet      : "Toolbar set \"%1\" doesn't exist",\r
+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.",\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Cancel",\r
+DlgBtnClose                    : "Close",\r
+DlgBtnBrowseServer     : "Browse Server",\r
+DlgAdvancedTag         : "Advanced",\r
+DlgOpOther                     : "<Other>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Please insert the URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<not set>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Language Direction",\r
+DlgGenLangDirLtr       : "Left to Right (LTR)",\r
+DlgGenLangDirRtl       : "Right to Left (RTL)",\r
+DlgGenLangCode         : "Language Code",\r
+DlgGenAccessKey                : "Access Key",\r
+DlgGenName                     : "Name",\r
+DlgGenTabIndex         : "Tab Index",\r
+DlgGenLongDescr                : "Long Description URL",\r
+DlgGenClass                    : "Stylesheet Classes",\r
+DlgGenTitle                    : "Advisory Title",\r
+DlgGenContType         : "Advisory Content Type",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Style",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Image Properties",\r
+DlgImgInfoTab          : "Image Info",\r
+DlgImgBtnUpload                : "Send it to the Server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Upload",\r
+DlgImgAlt                      : "Alternative Text",\r
+DlgImgWidth                    : "Width",\r
+DlgImgHeight           : "Height",\r
+DlgImgLockRatio                : "Lock Ratio",\r
+DlgBtnResetSize                : "Reset Size",\r
+DlgImgBorder           : "Border",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Align",\r
+DlgImgAlignLeft                : "Left",\r
+DlgImgAlignAbsBottom: "Abs Bottom",\r
+DlgImgAlignAbsMiddle: "Abs Middle",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Bottom",\r
+DlgImgAlignMiddle      : "Middle",\r
+DlgImgAlignRight       : "Right",\r
+DlgImgAlignTextTop     : "Text Top",\r
+DlgImgAlignTop         : "Top",\r
+DlgImgPreview          : "Preview",\r
+DlgImgAlertUrl         : "Please type the image URL",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Properties",\r
+DlgFlashChkPlay                : "Auto Play",\r
+DlgFlashChkLoop                : "Loop",\r
+DlgFlashChkMenu                : "Enable Flash Menu",\r
+DlgFlashScale          : "Scale",\r
+DlgFlashScaleAll       : "Show all",\r
+DlgFlashScaleNoBorder  : "No Border",\r
+DlgFlashScaleFit       : "Exact Fit",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Link Info",\r
+DlgLnkTargetTab                : "Target",\r
+\r
+DlgLnkType                     : "Link Type",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Link to anchor in the text",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocol",\r
+DlgLnkProtoOther       : "<other>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Select an Anchor",\r
+DlgLnkAnchorByName     : "By Anchor Name",\r
+DlgLnkAnchorById       : "By Element Id",\r
+DlgLnkNoAnchors                : "(No anchors available in the document)",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail Address",\r
+DlgLnkEMailSubject     : "Message Subject",\r
+DlgLnkEMailBody                : "Message Body",\r
+DlgLnkUpload           : "Upload",\r
+DlgLnkBtnUpload                : "Send it to the Server",\r
+\r
+DlgLnkTarget           : "Target",\r
+DlgLnkTargetFrame      : "<frame>",\r
+DlgLnkTargetPopup      : "<popup window>",\r
+DlgLnkTargetBlank      : "New Window (_blank)",\r
+DlgLnkTargetParent     : "Parent Window (_parent)",\r
+DlgLnkTargetSelf       : "Same Window (_self)",\r
+DlgLnkTargetTop                : "Topmost Window (_top)",\r
+DlgLnkTargetFrameName  : "Target Frame Name",\r
+DlgLnkPopWinName       : "Popup Window Name",\r
+DlgLnkPopWinFeat       : "Popup Window Features",\r
+DlgLnkPopResize                : "Resizable",\r
+DlgLnkPopLocation      : "Location Bar",\r
+DlgLnkPopMenu          : "Menu Bar",\r
+DlgLnkPopScroll                : "Scroll Bars",\r
+DlgLnkPopStatus                : "Status Bar",\r
+DlgLnkPopToolbar       : "Toolbar",\r
+DlgLnkPopFullScrn      : "Full Screen (IE)",\r
+DlgLnkPopDependent     : "Dependent (Netscape)",\r
+DlgLnkPopWidth         : "Width",\r
+DlgLnkPopHeight                : "Height",\r
+DlgLnkPopLeft          : "Left Position",\r
+DlgLnkPopTop           : "Top Position",\r
+\r
+DlnLnkMsgNoUrl         : "Please type the link URL",\r
+DlnLnkMsgNoEMail       : "Please type the e-mail address",\r
+DlnLnkMsgNoAnchor      : "Please select an anchor",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Select Colour",\r
+DlgColorBtnClear       : "Clear",\r
+DlgColorHighlight      : "Highlight",\r
+DlgColorSelected       : "Selected",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Insert a Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Select Special Character",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Table Properties",\r
+DlgTableRows           : "Rows",\r
+DlgTableColumns                : "Columns",\r
+DlgTableBorder         : "Border size",\r
+DlgTableAlign          : "Alignment",\r
+DlgTableAlignNotSet    : "<Not set>",\r
+DlgTableAlignLeft      : "Left",\r
+DlgTableAlignCenter    : "Centre",\r
+DlgTableAlignRight     : "Right",\r
+DlgTableWidth          : "Width",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "percent",\r
+DlgTableHeight         : "Height",\r
+DlgTableCellSpace      : "Cell spacing",\r
+DlgTableCellPad                : "Cell padding",\r
+DlgTableCaption                : "Caption",\r
+DlgTableSummary                : "Summary",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Cell Properties",\r
+DlgCellWidth           : "Width",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "percent",\r
+DlgCellHeight          : "Height",\r
+DlgCellWordWrap                : "Word Wrap",\r
+DlgCellWordWrapNotSet  : "<Not set>",\r
+DlgCellWordWrapYes     : "Yes",\r
+DlgCellWordWrapNo      : "No",\r
+DlgCellHorAlign                : "Horizontal Alignment",\r
+DlgCellHorAlignNotSet  : "<Not set>",\r
+DlgCellHorAlignLeft    : "Left",\r
+DlgCellHorAlignCenter  : "Centre",\r
+DlgCellHorAlignRight: "Right",\r
+DlgCellVerAlign                : "Vertical Alignment",\r
+DlgCellVerAlignNotSet  : "<Not set>",\r
+DlgCellVerAlignTop     : "Top",\r
+DlgCellVerAlignMiddle  : "Middle",\r
+DlgCellVerAlignBottom  : "Bottom",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Rows Span",\r
+DlgCellCollSpan                : "Columns Span",\r
+DlgCellBackColor       : "Background Colour",\r
+DlgCellBorderColor     : "Border Colour",\r
+DlgCellBtnSelect       : "Select...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Find",\r
+DlgFindFindBtn         : "Find",\r
+DlgFindNotFoundMsg     : "The specified text was not found.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Replace",\r
+DlgReplaceFindLbl              : "Find what:",\r
+DlgReplaceReplaceLbl   : "Replace with:",\r
+DlgReplaceCaseChk              : "Match case",\r
+DlgReplaceReplaceBtn   : "Replace",\r
+DlgReplaceReplAllBtn   : "Replace All",\r
+DlgReplaceWordChk              : "Match whole word",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl+X).",\r
+PasteErrorCopy : "Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl+C).",\r
+\r
+PasteAsText            : "Paste as Plain Text",\r
+PasteFromWord  : "Paste from Word",\r
+\r
+DlgPasteMsg2   : "Please paste inside the following box using the keyboard (<STRONG>Ctrl+V</STRONG>) and hit <STRONG>OK</STRONG>.",\r
+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.",\r
+DlgPasteIgnoreFont             : "Ignore Font Face definitions",\r
+DlgPasteRemoveStyles   : "Remove Styles definitions",\r
+DlgPasteCleanBox               : "Clean Up Box",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatic",\r
+ColorMoreColors        : "More Colours...",\r
+\r
+// Document Properties\r
+DocProps               : "Document Properties",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Anchor Properties",\r
+DlgAnchorName          : "Anchor Name",\r
+DlgAnchorErrorName     : "Please type the anchor name",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Not in dictionary",\r
+DlgSpellChangeTo               : "Change to",\r
+DlgSpellBtnIgnore              : "Ignore",\r
+DlgSpellBtnIgnoreAll   : "Ignore All",\r
+DlgSpellBtnReplace             : "Replace",\r
+DlgSpellBtnReplaceAll  : "Replace All",\r
+DlgSpellBtnUndo                        : "Undo",\r
+DlgSpellNoSuggestions  : "- No suggestions -",\r
+DlgSpellProgress               : "Spell check in progress...",\r
+DlgSpellNoMispell              : "Spell check complete: No misspellings found",\r
+DlgSpellNoChanges              : "Spell check complete: No words changed",\r
+DlgSpellOneChange              : "Spell check complete: One word changed",\r
+DlgSpellManyChanges            : "Spell check complete: %1 words changed",\r
+\r
+IeSpellDownload                        : "Spell checker not installed. Do you want to download it now?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Value)",\r
+DlgButtonType          : "Type",\r
+DlgButtonTypeBtn       : "Button",\r
+DlgButtonTypeSbm       : "Submit",\r
+DlgButtonTypeRst       : "Reset",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Name",\r
+DlgCheckboxValue       : "Value",\r
+DlgCheckboxSelected    : "Selected",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Name",\r
+DlgFormAction  : "Action",\r
+DlgFormMethod  : "Method",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Name",\r
+DlgSelectValue         : "Value",\r
+DlgSelectSize          : "Size",\r
+DlgSelectLines         : "lines",\r
+DlgSelectChkMulti      : "Allow multiple selections",\r
+DlgSelectOpAvail       : "Available Options",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Value",\r
+DlgSelectBtnAdd                : "Add",\r
+DlgSelectBtnModify     : "Modify",\r
+DlgSelectBtnUp         : "Up",\r
+DlgSelectBtnDown       : "Down",\r
+DlgSelectBtnSetValue : "Set as selected value",\r
+DlgSelectBtnDelete     : "Delete",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Name",\r
+DlgTextareaCols        : "Columns",\r
+DlgTextareaRows        : "Rows",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Name",\r
+DlgTextValue           : "Value",\r
+DlgTextCharWidth       : "Character Width",\r
+DlgTextMaxChars                : "Maximum Characters",\r
+DlgTextType                    : "Type",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Password",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Name",\r
+DlgHiddenValue : "Value",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Bulleted List Properties",\r
+NumberedListProp       : "Numbered List Properties",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Type",\r
+DlgLstTypeCircle       : "Circle",\r
+DlgLstTypeDisc         : "Disc",\r
+DlgLstTypeSquare       : "Square",\r
+DlgLstTypeNumbers      : "Numbers (1, 2, 3)",\r
+DlgLstTypeLCase                : "Lowercase Letters (a, b, c)",\r
+DlgLstTypeUCase                : "Uppercase Letters (A, B, C)",\r
+DlgLstTypeSRoman       : "Small Roman Numerals (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Large Roman Numerals (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",\r
+DlgDocBackTab          : "Background",\r
+DlgDocColorsTab                : "Colours and Margins",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Page Title",\r
+DlgDocLangDir          : "Language Direction",\r
+DlgDocLangDirLTR       : "Left to Right (LTR)",\r
+DlgDocLangDirRTL       : "Right to Left (RTL)",\r
+DlgDocLangCode         : "Language Code",\r
+DlgDocCharSet          : "Character Set Encoding",\r
+DlgDocCharSetCE                : "Central European",\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Greek",\r
+DlgDocCharSetJP                : "Japanese",\r
+DlgDocCharSetKR                : "Korean",\r
+DlgDocCharSetTR                : "Turkish",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Western European",\r
+DlgDocCharSetOther     : "Other Character Set Encoding",\r
+\r
+DlgDocDocType          : "Document Type Heading",\r
+DlgDocDocTypeOther     : "Other Document Type Heading",\r
+DlgDocIncXHTML         : "Include XHTML Declarations",\r
+DlgDocBgColor          : "Background Colour",\r
+DlgDocBgImage          : "Background Image URL",\r
+DlgDocBgNoScroll       : "Nonscrolling Background",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Visited Link",\r
+DlgDocCActive          : "Active Link",\r
+DlgDocMargins          : "Page Margins",\r
+DlgDocMaTop                    : "Top",\r
+DlgDocMaLeft           : "Left",\r
+DlgDocMaRight          : "Right",\r
+DlgDocMaBottom         : "Bottom",\r
+DlgDocMeIndex          : "Document Indexing Keywords (comma separated)",\r
+DlgDocMeDescr          : "Document Description",\r
+DlgDocMeAuthor         : "Author",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Preview",\r
+\r
+// Templates Dialog\r
+Templates                      : "Templates",\r
+DlgTemplatesTitle      : "Content Templates",\r
+DlgTemplatesSelMsg     : "Please select the template to open in the editor<br>(the actual contents will be lost):",\r
+DlgTemplatesLoading    : "Loading templates list. Please wait...",\r
+DlgTemplatesNoTpl      : "(No templates defined)",\r
+DlgTemplatesReplace    : "Replace actual contents",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "About",\r
+DlgAboutBrowserInfoTab : "Browser Info",\r
+DlgAboutLicenseTab     : "License",\r
+DlgAboutVersion                : "version",\r
+DlgAboutInfo           : "For further information go to"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/en.js b/httemplate/elements/fckeditor/editor/lang/en.js
new file mode 100644 (file)
index 0000000..c579fc9
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * English language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Collapse Toolbar",\r
+ToolbarExpand          : "Expand Toolbar",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Save",\r
+NewPage                                : "New Page",\r
+Preview                                : "Preview",\r
+Cut                                    : "Cut",\r
+Copy                           : "Copy",\r
+Paste                          : "Paste",\r
+PasteText                      : "Paste as plain text",\r
+PasteWord                      : "Paste from Word",\r
+Print                          : "Print",\r
+SelectAll                      : "Select All",\r
+RemoveFormat           : "Remove Format",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Insert/Edit Link",\r
+RemoveLink                     : "Remove Link",\r
+Anchor                         : "Insert/Edit Anchor",\r
+InsertImageLbl         : "Image",\r
+InsertImage                    : "Insert/Edit Image",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Insert/Edit Flash",\r
+InsertTableLbl         : "Table",\r
+InsertTable                    : "Insert/Edit Table",\r
+InsertLineLbl          : "Line",\r
+InsertLine                     : "Insert Horizontal Line",\r
+InsertSpecialCharLbl: "Special Character",\r
+InsertSpecialChar      : "Insert Special Character",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Insert Smiley",\r
+About                          : "About FCKeditor",\r
+Bold                           : "Bold",\r
+Italic                         : "Italic",\r
+Underline                      : "Underline",\r
+StrikeThrough          : "Strike Through",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Left Justify",\r
+CenterJustify          : "Center Justify",\r
+RightJustify           : "Right Justify",\r
+BlockJustify           : "Block Justify",\r
+DecreaseIndent         : "Decrease Indent",\r
+IncreaseIndent         : "Increase Indent",\r
+Undo                           : "Undo",\r
+Redo                           : "Redo",\r
+NumberedListLbl                : "Numbered List",\r
+NumberedList           : "Insert/Remove Numbered List",\r
+BulletedListLbl                : "Bulleted List",\r
+BulletedList           : "Insert/Remove Bulleted List",\r
+ShowTableBorders       : "Show Table Borders",\r
+ShowDetails                    : "Show Details",\r
+Style                          : "Style",\r
+FontFormat                     : "Format",\r
+Font                           : "Font",\r
+FontSize                       : "Size",\r
+TextColor                      : "Text Color",\r
+BGColor                                : "Background Color",\r
+Source                         : "Source",\r
+Find                           : "Find",\r
+Replace                                : "Replace",\r
+SpellCheck                     : "Check Spelling",\r
+UniversalKeyboard      : "Universal Keyboard",\r
+PageBreakLbl           : "Page Break",\r
+PageBreak                      : "Insert Page Break",\r
+\r
+Form                   : "Form",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Radio Button",\r
+TextField              : "Text Field",\r
+Textarea               : "Textarea",\r
+HiddenField            : "Hidden Field",\r
+Button                 : "Button",\r
+SelectionField : "Selection Field",\r
+ImageButton            : "Image Button",\r
+\r
+FitWindow              : "Maximize the editor size",\r
+\r
+// Context Menu\r
+EditLink                       : "Edit Link",\r
+CellCM                         : "Cell",\r
+RowCM                          : "Row",\r
+ColumnCM                       : "Column",\r
+InsertRow                      : "Insert Row",\r
+DeleteRows                     : "Delete Rows",\r
+InsertColumn           : "Insert Column",\r
+DeleteColumns          : "Delete Columns",\r
+InsertCell                     : "Insert Cell",\r
+DeleteCells                    : "Delete Cells",\r
+MergeCells                     : "Merge Cells",\r
+SplitCell                      : "Split Cell",\r
+TableDelete                    : "Delete Table",\r
+CellProperties         : "Cell Properties",\r
+TableProperties                : "Table Properties",\r
+ImageProperties                : "Image Properties",\r
+FlashProperties                : "Flash Properties",\r
+\r
+AnchorProp                     : "Anchor Properties",\r
+ButtonProp                     : "Button Properties",\r
+CheckboxProp           : "Checkbox Properties",\r
+HiddenFieldProp                : "Hidden Field Properties",\r
+RadioButtonProp                : "Radio Button Properties",\r
+ImageButtonProp                : "Image Button Properties",\r
+TextFieldProp          : "Text Field Properties",\r
+SelectionFieldProp     : "Selection Field Properties",\r
+TextareaProp           : "Textarea Properties",\r
+FormProp                       : "Form Properties",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Processing XHTML. Please wait...",\r
+Done                           : "Done",\r
+PasteWordConfirm       : "The text you want to paste seems to be copied from Word. Do you want to clean it before pasting?",\r
+NotCompatiblePaste     : "This command is available for Internet Explorer version 5.5 or more. Do you want to paste without cleaning?",\r
+UnknownToolbarItem     : "Unknown toolbar item \"%1\"",\r
+UnknownCommand         : "Unknown command name \"%1\"",\r
+NotImplemented         : "Command not implemented",\r
+UnknownToolbarSet      : "Toolbar set \"%1\" doesn't exist",\r
+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.",\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Cancel",\r
+DlgBtnClose                    : "Close",\r
+DlgBtnBrowseServer     : "Browse Server",\r
+DlgAdvancedTag         : "Advanced",\r
+DlgOpOther                     : "<Other>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Please insert the URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<not set>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Language Direction",\r
+DlgGenLangDirLtr       : "Left to Right (LTR)",\r
+DlgGenLangDirRtl       : "Right to Left (RTL)",\r
+DlgGenLangCode         : "Language Code",\r
+DlgGenAccessKey                : "Access Key",\r
+DlgGenName                     : "Name",\r
+DlgGenTabIndex         : "Tab Index",\r
+DlgGenLongDescr                : "Long Description URL",\r
+DlgGenClass                    : "Stylesheet Classes",\r
+DlgGenTitle                    : "Advisory Title",\r
+DlgGenContType         : "Advisory Content Type",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Style",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Image Properties",\r
+DlgImgInfoTab          : "Image Info",\r
+DlgImgBtnUpload                : "Send it to the Server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Upload",\r
+DlgImgAlt                      : "Alternative Text",\r
+DlgImgWidth                    : "Width",\r
+DlgImgHeight           : "Height",\r
+DlgImgLockRatio                : "Lock Ratio",\r
+DlgBtnResetSize                : "Reset Size",\r
+DlgImgBorder           : "Border",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Align",\r
+DlgImgAlignLeft                : "Left",\r
+DlgImgAlignAbsBottom: "Abs Bottom",\r
+DlgImgAlignAbsMiddle: "Abs Middle",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Bottom",\r
+DlgImgAlignMiddle      : "Middle",\r
+DlgImgAlignRight       : "Right",\r
+DlgImgAlignTextTop     : "Text Top",\r
+DlgImgAlignTop         : "Top",\r
+DlgImgPreview          : "Preview",\r
+DlgImgAlertUrl         : "Please type the image URL",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Properties",\r
+DlgFlashChkPlay                : "Auto Play",\r
+DlgFlashChkLoop                : "Loop",\r
+DlgFlashChkMenu                : "Enable Flash Menu",\r
+DlgFlashScale          : "Scale",\r
+DlgFlashScaleAll       : "Show all",\r
+DlgFlashScaleNoBorder  : "No Border",\r
+DlgFlashScaleFit       : "Exact Fit",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Link Info",\r
+DlgLnkTargetTab                : "Target",\r
+\r
+DlgLnkType                     : "Link Type",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Link to anchor in the text",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocol",\r
+DlgLnkProtoOther       : "<other>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Select an Anchor",\r
+DlgLnkAnchorByName     : "By Anchor Name",\r
+DlgLnkAnchorById       : "By Element Id",\r
+DlgLnkNoAnchors                : "(No anchors available in the document)",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail Address",\r
+DlgLnkEMailSubject     : "Message Subject",\r
+DlgLnkEMailBody                : "Message Body",\r
+DlgLnkUpload           : "Upload",\r
+DlgLnkBtnUpload                : "Send it to the Server",\r
+\r
+DlgLnkTarget           : "Target",\r
+DlgLnkTargetFrame      : "<frame>",\r
+DlgLnkTargetPopup      : "<popup window>",\r
+DlgLnkTargetBlank      : "New Window (_blank)",\r
+DlgLnkTargetParent     : "Parent Window (_parent)",\r
+DlgLnkTargetSelf       : "Same Window (_self)",\r
+DlgLnkTargetTop                : "Topmost Window (_top)",\r
+DlgLnkTargetFrameName  : "Target Frame Name",\r
+DlgLnkPopWinName       : "Popup Window Name",\r
+DlgLnkPopWinFeat       : "Popup Window Features",\r
+DlgLnkPopResize                : "Resizable",\r
+DlgLnkPopLocation      : "Location Bar",\r
+DlgLnkPopMenu          : "Menu Bar",\r
+DlgLnkPopScroll                : "Scroll Bars",\r
+DlgLnkPopStatus                : "Status Bar",\r
+DlgLnkPopToolbar       : "Toolbar",\r
+DlgLnkPopFullScrn      : "Full Screen (IE)",\r
+DlgLnkPopDependent     : "Dependent (Netscape)",\r
+DlgLnkPopWidth         : "Width",\r
+DlgLnkPopHeight                : "Height",\r
+DlgLnkPopLeft          : "Left Position",\r
+DlgLnkPopTop           : "Top Position",\r
+\r
+DlnLnkMsgNoUrl         : "Please type the link URL",\r
+DlnLnkMsgNoEMail       : "Please type the e-mail address",\r
+DlnLnkMsgNoAnchor      : "Please select an anchor",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Select Color",\r
+DlgColorBtnClear       : "Clear",\r
+DlgColorHighlight      : "Highlight",\r
+DlgColorSelected       : "Selected",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Insert a Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Select Special Character",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Table Properties",\r
+DlgTableRows           : "Rows",\r
+DlgTableColumns                : "Columns",\r
+DlgTableBorder         : "Border size",\r
+DlgTableAlign          : "Alignment",\r
+DlgTableAlignNotSet    : "<Not set>",\r
+DlgTableAlignLeft      : "Left",\r
+DlgTableAlignCenter    : "Center",\r
+DlgTableAlignRight     : "Right",\r
+DlgTableWidth          : "Width",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "percent",\r
+DlgTableHeight         : "Height",\r
+DlgTableCellSpace      : "Cell spacing",\r
+DlgTableCellPad                : "Cell padding",\r
+DlgTableCaption                : "Caption",\r
+DlgTableSummary                : "Summary",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Cell Properties",\r
+DlgCellWidth           : "Width",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "percent",\r
+DlgCellHeight          : "Height",\r
+DlgCellWordWrap                : "Word Wrap",\r
+DlgCellWordWrapNotSet  : "<Not set>",\r
+DlgCellWordWrapYes     : "Yes",\r
+DlgCellWordWrapNo      : "No",\r
+DlgCellHorAlign                : "Horizontal Alignment",\r
+DlgCellHorAlignNotSet  : "<Not set>",\r
+DlgCellHorAlignLeft    : "Left",\r
+DlgCellHorAlignCenter  : "Center",\r
+DlgCellHorAlignRight: "Right",\r
+DlgCellVerAlign                : "Vertical Alignment",\r
+DlgCellVerAlignNotSet  : "<Not set>",\r
+DlgCellVerAlignTop     : "Top",\r
+DlgCellVerAlignMiddle  : "Middle",\r
+DlgCellVerAlignBottom  : "Bottom",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Rows Span",\r
+DlgCellCollSpan                : "Columns Span",\r
+DlgCellBackColor       : "Background Color",\r
+DlgCellBorderColor     : "Border Color",\r
+DlgCellBtnSelect       : "Select...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Find",\r
+DlgFindFindBtn         : "Find",\r
+DlgFindNotFoundMsg     : "The specified text was not found.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Replace",\r
+DlgReplaceFindLbl              : "Find what:",\r
+DlgReplaceReplaceLbl   : "Replace with:",\r
+DlgReplaceCaseChk              : "Match case",\r
+DlgReplaceReplaceBtn   : "Replace",\r
+DlgReplaceReplAllBtn   : "Replace All",\r
+DlgReplaceWordChk              : "Match whole word",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Your browser security settings don't permit the editor to automatically execute cutting operations. Please use the keyboard for that (Ctrl+X).",\r
+PasteErrorCopy : "Your browser security settings don't permit the editor to automatically execute copying operations. Please use the keyboard for that (Ctrl+C).",\r
+\r
+PasteAsText            : "Paste as Plain Text",\r
+PasteFromWord  : "Paste from Word",\r
+\r
+DlgPasteMsg2   : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",\r
+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.",\r
+DlgPasteIgnoreFont             : "Ignore Font Face definitions",\r
+DlgPasteRemoveStyles   : "Remove Styles definitions",\r
+DlgPasteCleanBox               : "Clean Up Box",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatic",\r
+ColorMoreColors        : "More Colors...",\r
+\r
+// Document Properties\r
+DocProps               : "Document Properties",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Anchor Properties",\r
+DlgAnchorName          : "Anchor Name",\r
+DlgAnchorErrorName     : "Please type the anchor name",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Not in dictionary",\r
+DlgSpellChangeTo               : "Change to",\r
+DlgSpellBtnIgnore              : "Ignore",\r
+DlgSpellBtnIgnoreAll   : "Ignore All",\r
+DlgSpellBtnReplace             : "Replace",\r
+DlgSpellBtnReplaceAll  : "Replace All",\r
+DlgSpellBtnUndo                        : "Undo",\r
+DlgSpellNoSuggestions  : "- No suggestions -",\r
+DlgSpellProgress               : "Spell check in progress...",\r
+DlgSpellNoMispell              : "Spell check complete: No misspellings found",\r
+DlgSpellNoChanges              : "Spell check complete: No words changed",\r
+DlgSpellOneChange              : "Spell check complete: One word changed",\r
+DlgSpellManyChanges            : "Spell check complete: %1 words changed",\r
+\r
+IeSpellDownload                        : "Spell checker not installed. Do you want to download it now?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Value)",\r
+DlgButtonType          : "Type",\r
+DlgButtonTypeBtn       : "Button",\r
+DlgButtonTypeSbm       : "Submit",\r
+DlgButtonTypeRst       : "Reset",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Name",\r
+DlgCheckboxValue       : "Value",\r
+DlgCheckboxSelected    : "Selected",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Name",\r
+DlgFormAction  : "Action",\r
+DlgFormMethod  : "Method",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Name",\r
+DlgSelectValue         : "Value",\r
+DlgSelectSize          : "Size",\r
+DlgSelectLines         : "lines",\r
+DlgSelectChkMulti      : "Allow multiple selections",\r
+DlgSelectOpAvail       : "Available Options",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Value",\r
+DlgSelectBtnAdd                : "Add",\r
+DlgSelectBtnModify     : "Modify",\r
+DlgSelectBtnUp         : "Up",\r
+DlgSelectBtnDown       : "Down",\r
+DlgSelectBtnSetValue : "Set as selected value",\r
+DlgSelectBtnDelete     : "Delete",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Name",\r
+DlgTextareaCols        : "Columns",\r
+DlgTextareaRows        : "Rows",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Name",\r
+DlgTextValue           : "Value",\r
+DlgTextCharWidth       : "Character Width",\r
+DlgTextMaxChars                : "Maximum Characters",\r
+DlgTextType                    : "Type",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Password",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Name",\r
+DlgHiddenValue : "Value",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Bulleted List Properties",\r
+NumberedListProp       : "Numbered List Properties",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Type",\r
+DlgLstTypeCircle       : "Circle",\r
+DlgLstTypeDisc         : "Disc",\r
+DlgLstTypeSquare       : "Square",\r
+DlgLstTypeNumbers      : "Numbers (1, 2, 3)",\r
+DlgLstTypeLCase                : "Lowercase Letters (a, b, c)",\r
+DlgLstTypeUCase                : "Uppercase Letters (A, B, C)",\r
+DlgLstTypeSRoman       : "Small Roman Numerals (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Large Roman Numerals (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",\r
+DlgDocBackTab          : "Background",\r
+DlgDocColorsTab                : "Colors and Margins",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Page Title",\r
+DlgDocLangDir          : "Language Direction",\r
+DlgDocLangDirLTR       : "Left to Right (LTR)",\r
+DlgDocLangDirRTL       : "Right to Left (RTL)",\r
+DlgDocLangCode         : "Language Code",\r
+DlgDocCharSet          : "Character Set Encoding",\r
+DlgDocCharSetCE                : "Central European",\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Greek",\r
+DlgDocCharSetJP                : "Japanese",\r
+DlgDocCharSetKR                : "Korean",\r
+DlgDocCharSetTR                : "Turkish",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Western European",\r
+DlgDocCharSetOther     : "Other Character Set Encoding",\r
+\r
+DlgDocDocType          : "Document Type Heading",\r
+DlgDocDocTypeOther     : "Other Document Type Heading",\r
+DlgDocIncXHTML         : "Include XHTML Declarations",\r
+DlgDocBgColor          : "Background Color",\r
+DlgDocBgImage          : "Background Image URL",\r
+DlgDocBgNoScroll       : "Nonscrolling Background",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Visited Link",\r
+DlgDocCActive          : "Active Link",\r
+DlgDocMargins          : "Page Margins",\r
+DlgDocMaTop                    : "Top",\r
+DlgDocMaLeft           : "Left",\r
+DlgDocMaRight          : "Right",\r
+DlgDocMaBottom         : "Bottom",\r
+DlgDocMeIndex          : "Document Indexing Keywords (comma separated)",\r
+DlgDocMeDescr          : "Document Description",\r
+DlgDocMeAuthor         : "Author",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Preview",\r
+\r
+// Templates Dialog\r
+Templates                      : "Templates",\r
+DlgTemplatesTitle      : "Content Templates",\r
+DlgTemplatesSelMsg     : "Please select the template to open in the editor<br />(the actual contents will be lost):",\r
+DlgTemplatesLoading    : "Loading templates list. Please wait...",\r
+DlgTemplatesNoTpl      : "(No templates defined)",\r
+DlgTemplatesReplace    : "Replace actual contents",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "About",\r
+DlgAboutBrowserInfoTab : "Browser Info",\r
+DlgAboutLicenseTab     : "License",\r
+DlgAboutVersion                : "version",\r
+DlgAboutInfo           : "For further information go to"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/eo.js b/httemplate/elements/fckeditor/editor/lang/eo.js
new file mode 100644 (file)
index 0000000..04f7364
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Esperanto language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Kaŝi Ilobreton",\r
+ToolbarExpand          : "Vidigi Ilojn",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Sekurigi",\r
+NewPage                                : "Nova Paĝo",\r
+Preview                                : "Vidigi Aspekton",\r
+Cut                                    : "Eltondi",\r
+Copy                           : "Kopii",\r
+Paste                          : "Interglui",\r
+PasteText                      : "Interglui kiel Tekston",\r
+PasteWord                      : "Interglui el Word",\r
+Print                          : "Presi",\r
+SelectAll                      : "Elekti ĉion",\r
+RemoveFormat           : "Forigi Formaton",\r
+InsertLinkLbl          : "Ligilo",\r
+InsertLink                     : "Enmeti/Ŝanĝi Ligilon",\r
+RemoveLink                     : "Forigi Ligilon",\r
+Anchor                         : "Enmeti/Ŝanĝi Ankron",\r
+InsertImageLbl         : "Bildo",\r
+InsertImage                    : "Enmeti/Ŝanĝi Bildon",\r
+InsertFlashLbl         : "Flash",      //MISSING\r
+InsertFlash                    : "Insert/Edit Flash",  //MISSING\r
+InsertTableLbl         : "Tabelo",\r
+InsertTable                    : "Enmeti/Ŝanĝi Tabelon",\r
+InsertLineLbl          : "Horizonta Linio",\r
+InsertLine                     : "Enmeti Horizonta Linio",\r
+InsertSpecialCharLbl: "Speciala Signo",\r
+InsertSpecialChar      : "Enmeti Specialan Signon",\r
+InsertSmileyLbl                : "Mienvinjeto",\r
+InsertSmiley           : "Enmeti Mienvinjeton",\r
+About                          : "Pri FCKeditor",\r
+Bold                           : "Grasa",\r
+Italic                         : "Kursiva",\r
+Underline                      : "Substreko",\r
+StrikeThrough          : "Trastreko",\r
+Subscript                      : "Subskribo",\r
+Superscript                    : "Superskribo",\r
+LeftJustify                    : "Maldekstrigi",\r
+CenterJustify          : "Centrigi",\r
+RightJustify           : "Dekstrigi",\r
+BlockJustify           : "Ĝisrandigi Ambaŭflanke",\r
+DecreaseIndent         : "Malpligrandigi Krommarĝenon",\r
+IncreaseIndent         : "Pligrandigi Krommarĝenon",\r
+Undo                           : "Malfari",\r
+Redo                           : "Refari",\r
+NumberedListLbl                : "Numera Listo",\r
+NumberedList           : "Enmeti/Forigi Numeran Liston",\r
+BulletedListLbl                : "Bula Listo",\r
+BulletedList           : "Enmeti/Forigi Bulan Liston",\r
+ShowTableBorders       : "Vidigi Borderojn de Tabelo",\r
+ShowDetails                    : "Vidigi Detalojn",\r
+Style                          : "Stilo",\r
+FontFormat                     : "Formato",\r
+Font                           : "Tiparo",\r
+FontSize                       : "Grando",\r
+TextColor                      : "Teksta Koloro",\r
+BGColor                                : "Fona Koloro",\r
+Source                         : "Fonto",\r
+Find                           : "Serĉi",\r
+Replace                                : "Anstataŭigi",\r
+SpellCheck                     : "Literumada Kontrolilo",\r
+UniversalKeyboard      : "Universala Klavaro",\r
+PageBreakLbl           : "Page Break", //MISSING\r
+PageBreak                      : "Insert Page Break",  //MISSING\r
+\r
+Form                   : "Formularo",\r
+Checkbox               : "Markobutono",\r
+RadioButton            : "Radiobutono",\r
+TextField              : "Teksta kampo",\r
+Textarea               : "Teksta Areo",\r
+HiddenField            : "Kaŝita Kampo",\r
+Button                 : "Butono",\r
+SelectionField : "Elekta Kampo",\r
+ImageButton            : "Bildbutono",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Modifier Ligilon",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Enmeti Linion",\r
+DeleteRows                     : "Forigi Liniojn",\r
+InsertColumn           : "Enmeti Kolumnon",\r
+DeleteColumns          : "Forigi Kolumnojn",\r
+InsertCell                     : "Enmeti Ĉelon",\r
+DeleteCells                    : "Forigi Ĉelojn",\r
+MergeCells                     : "Kunfandi Ĉelojn",\r
+SplitCell                      : "Dividi Ĉelojn",\r
+TableDelete                    : "Delete Table",       //MISSING\r
+CellProperties         : "Atributoj de Ĉelo",\r
+TableProperties                : "Atributoj de Tabelo",\r
+ImageProperties                : "Atributoj de Bildo",\r
+FlashProperties                : "Flash Properties",   //MISSING\r
+\r
+AnchorProp                     : "Ankraj Atributoj",\r
+ButtonProp                     : "Butonaj Atributoj",\r
+CheckboxProp           : "Markobutonaj Atributoj",\r
+HiddenFieldProp                : "Atributoj de Kaŝita Kampo",\r
+RadioButtonProp                : "Radiobutonaj Atributoj",\r
+ImageButtonProp                : "Bildbutonaj Atributoj",\r
+TextFieldProp          : "Atributoj de Teksta Kampo",\r
+SelectionFieldProp     : "Atributoj de Elekta Kampo",\r
+TextareaProp           : "Atributoj de Teksta Areo",\r
+FormProp                       : "Formularaj Atributoj",\r
+\r
+FontFormats                    : "Normala;Formatita;Adreso;Titolo 1;Titolo 2;Titolo 3;Titolo 4;Titolo 5;Titolo 6;Paragrafo (DIV)",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Traktado de XHTML. Bonvolu pacienci...",\r
+Done                           : "Finita",\r
+PasteWordConfirm       : "La algluota teksto ŝajnas esti Word-devena. Ĉu vi volas purigi ĝin antaŭ ol interglui?",\r
+NotCompatiblePaste     : "Tiu ĉi komando bezonas almenaŭ Internet Explorer 5.5. Ĉu vi volas daŭrigi sen purigado?",\r
+UnknownToolbarItem     : "Ilobretero nekonata \"%1\"",\r
+UnknownCommand         : "Komandonomo nekonata \"%1\"",\r
+NotImplemented         : "Komando ne ankoraŭ realigita",\r
+UnknownToolbarSet      : "La ilobreto \"%1\" ne ekzistas",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "Akcepti",\r
+DlgBtnCancel           : "Rezigni",\r
+DlgBtnClose                    : "Fermi",\r
+DlgBtnBrowseServer     : "Foliumi en la Servilo",\r
+DlgAdvancedTag         : "Speciala",\r
+DlgOpOther                     : "<Alia>",\r
+DlgInfoTab                     : "Info",       //MISSING\r
+DlgAlertUrl                    : "Please insert the URL",      //MISSING\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<Defaŭlta>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Skribdirekto",\r
+DlgGenLangDirLtr       : "De maldekstro dekstren (LTR)",\r
+DlgGenLangDirRtl       : "De dekstro maldekstren (RTL)",\r
+DlgGenLangCode         : "Lingva Kodo",\r
+DlgGenAccessKey                : "Fulmoklavo",\r
+DlgGenName                     : "Nomo",\r
+DlgGenTabIndex         : "Taba Ordo",\r
+DlgGenLongDescr                : "URL de Longa Priskribo",\r
+DlgGenClass                    : "Klasoj de Stilfolioj",\r
+DlgGenTitle                    : "Indika Titolo",\r
+DlgGenContType         : "Indika Enhavotipo",\r
+DlgGenLinkCharset      : "Signaro de la Ligita Rimedo",\r
+DlgGenStyle                    : "Stilo",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Atributoj de Bildo",\r
+DlgImgInfoTab          : "Informoj pri Bildo",\r
+DlgImgBtnUpload                : "Sendu al Servilo",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Alŝuti",\r
+DlgImgAlt                      : "Anstataŭiga Teksto",\r
+DlgImgWidth                    : "Larĝo",\r
+DlgImgHeight           : "Alto",\r
+DlgImgLockRatio                : "Konservi Proporcion",\r
+DlgBtnResetSize                : "Origina Grando",\r
+DlgImgBorder           : "Bordero",\r
+DlgImgHSpace           : "HSpaco",\r
+DlgImgVSpace           : "VSpaco",\r
+DlgImgAlign                    : "Ĝisrandigo",\r
+DlgImgAlignLeft                : "Maldekstre",\r
+DlgImgAlignAbsBottom: "Abs Malsupre",\r
+DlgImgAlignAbsMiddle: "Abs Centre",\r
+DlgImgAlignBaseline    : "Je Malsupro de Teksto",\r
+DlgImgAlignBottom      : "Malsupre",\r
+DlgImgAlignMiddle      : "Centre",\r
+DlgImgAlignRight       : "Dekstre",\r
+DlgImgAlignTextTop     : "Je Supro de Teksto",\r
+DlgImgAlignTop         : "Supre",\r
+DlgImgPreview          : "Vidigi Aspekton",\r
+DlgImgAlertUrl         : "Bonvolu tajpi la URL de la bildo",\r
+DlgImgLinkTab          : "Link",       //MISSING\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Properties",   //MISSING\r
+DlgFlashChkPlay                : "Auto Play",  //MISSING\r
+DlgFlashChkLoop                : "Loop",       //MISSING\r
+DlgFlashChkMenu                : "Enable Flash Menu",  //MISSING\r
+DlgFlashScale          : "Scale",      //MISSING\r
+DlgFlashScaleAll       : "Show all",   //MISSING\r
+DlgFlashScaleNoBorder  : "No Border",  //MISSING\r
+DlgFlashScaleFit       : "Exact Fit",  //MISSING\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Ligilo",\r
+DlgLnkInfoTab          : "Informoj pri la Ligilo",\r
+DlgLnkTargetTab                : "Celo",\r
+\r
+DlgLnkType                     : "Tipo de Ligilo",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Ankri en tiu ĉi paĝo",\r
+DlgLnkTypeEMail                : "Retpoŝto",\r
+DlgLnkProto                    : "Protokolo",\r
+DlgLnkProtoOther       : "<alia>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Elekti Ankron",\r
+DlgLnkAnchorByName     : "Per Ankronomo",\r
+DlgLnkAnchorById       : "Per Elementidentigilo",\r
+DlgLnkNoAnchors                : "<Ne disponeblas ankroj en la dokumento>",            //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Retadreso",\r
+DlgLnkEMailSubject     : "Temlinio",\r
+DlgLnkEMailBody                : "Mesaĝa korpo",\r
+DlgLnkUpload           : "Alŝuti",\r
+DlgLnkBtnUpload                : "Sendi al Servilo",\r
+\r
+DlgLnkTarget           : "Celo",\r
+DlgLnkTargetFrame      : "<kadro>",\r
+DlgLnkTargetPopup      : "<ŝprucfenestro>",\r
+DlgLnkTargetBlank      : "Nova Fenestro (_blank)",\r
+DlgLnkTargetParent     : "Gepatra Fenestro (_parent)",\r
+DlgLnkTargetSelf       : "Sama Fenestro (_self)",\r
+DlgLnkTargetTop                : "Plej Supra Fenestro (_top)",\r
+DlgLnkTargetFrameName  : "Nomo de Kadro",\r
+DlgLnkPopWinName       : "Nomo de Ŝprucfenestro",\r
+DlgLnkPopWinFeat       : "Atributoj de la Ŝprucfenestro",\r
+DlgLnkPopResize                : "Grando Ŝanĝebla",\r
+DlgLnkPopLocation      : "Adresobreto",\r
+DlgLnkPopMenu          : "Menubreto",\r
+DlgLnkPopScroll                : "Rulumlisteloj",\r
+DlgLnkPopStatus                : "Statobreto",\r
+DlgLnkPopToolbar       : "Ilobreto",\r
+DlgLnkPopFullScrn      : "Tutekrane (IE)",\r
+DlgLnkPopDependent     : "Dependa (Netscape)",\r
+DlgLnkPopWidth         : "Larĝo",\r
+DlgLnkPopHeight                : "Alto",\r
+DlgLnkPopLeft          : "Pozicio de Maldekstro",\r
+DlgLnkPopTop           : "Pozicio de Supro",\r
+\r
+DlnLnkMsgNoUrl         : "Bonvolu entajpi la URL-on",\r
+DlnLnkMsgNoEMail       : "Bonvolu entajpi la retadreson",\r
+DlnLnkMsgNoAnchor      : "Bonvolu elekti ankron",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Elekti",\r
+DlgColorBtnClear       : "Forigi",\r
+DlgColorHighlight      : "Emfazi",\r
+DlgColorSelected       : "Elektita",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Enmeti Mienvinjeton",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Enmeti Specialan Signon",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Atributoj de Tabelo",\r
+DlgTableRows           : "Linioj",\r
+DlgTableColumns                : "Kolumnoj",\r
+DlgTableBorder         : "Bordero",\r
+DlgTableAlign          : "Ĝisrandigo",\r
+DlgTableAlignNotSet    : "<Defaŭlte>",\r
+DlgTableAlignLeft      : "Maldekstre",\r
+DlgTableAlignCenter    : "Centre",\r
+DlgTableAlignRight     : "Dekstre",\r
+DlgTableWidth          : "Larĝo",\r
+DlgTableWidthPx                : "Bitbilderoj",\r
+DlgTableWidthPc                : "elcentoj",\r
+DlgTableHeight         : "Alto",\r
+DlgTableCellSpace      : "Interspacigo de Ĉeloj",\r
+DlgTableCellPad                : "Ĉirkaŭenhava Plenigado",\r
+DlgTableCaption                : "Titolo",\r
+DlgTableSummary                : "Summary",    //MISSING\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Atributoj de Celo",\r
+DlgCellWidth           : "Larĝo",\r
+DlgCellWidthPx         : "bitbilderoj",\r
+DlgCellWidthPc         : "elcentoj",\r
+DlgCellHeight          : "Alto",\r
+DlgCellWordWrap                : "Linifaldo",\r
+DlgCellWordWrapNotSet  : "<Defaŭlte>",\r
+DlgCellWordWrapYes     : "Jes",\r
+DlgCellWordWrapNo      : "Ne",\r
+DlgCellHorAlign                : "Horizonta Ĝisrandigo",\r
+DlgCellHorAlignNotSet  : "<Defaŭlte>",\r
+DlgCellHorAlignLeft    : "Maldekstre",\r
+DlgCellHorAlignCenter  : "Centre",\r
+DlgCellHorAlignRight: "Dekstre",\r
+DlgCellVerAlign                : "Vertikala Ĝisrandigo",\r
+DlgCellVerAlignNotSet  : "<Defaŭlte>",\r
+DlgCellVerAlignTop     : "Supre",\r
+DlgCellVerAlignMiddle  : "Centre",\r
+DlgCellVerAlignBottom  : "Malsupre",\r
+DlgCellVerAlignBaseline        : "Je Malsupro de Teksto",\r
+DlgCellRowSpan         : "Linioj Kunfanditaj",\r
+DlgCellCollSpan                : "Kolumnoj Kunfanditaj",\r
+DlgCellBackColor       : "Fono",\r
+DlgCellBorderColor     : "Bordero",\r
+DlgCellBtnSelect       : "Elekti...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Serĉi",\r
+DlgFindFindBtn         : "Serĉi",\r
+DlgFindNotFoundMsg     : "La celteksto ne estas trovita.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Anstataŭigi",\r
+DlgReplaceFindLbl              : "Serĉi:",\r
+DlgReplaceReplaceLbl   : "Anstataŭigi per:",\r
+DlgReplaceCaseChk              : "Kongruigi Usklecon",\r
+DlgReplaceReplaceBtn   : "Anstataŭigi",\r
+DlgReplaceReplAllBtn   : "Anstataŭigi Ĉiun",\r
+DlgReplaceWordChk              : "Tuta Vorto",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "La sekurecagordo de via TTT-legilo ne permesas, ke la redaktilo faras eltondajn operaciojn. Bonvolu uzi la klavaron por tio (ctrl-X).",\r
+PasteErrorCopy : "La sekurecagordo de via TTT-legilo ne permesas, ke la redaktilo faras kopiajn operaciojn. Bonvolu uzi la klavaron por tio (ctrl-C).",\r
+\r
+PasteAsText            : "Interglui kiel Tekston",\r
+PasteFromWord  : "Interglui el Word",\r
+\r
+DlgPasteMsg2   : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",    //MISSING\r
+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\r
+DlgPasteIgnoreFont             : "Ignore Font Face definitions",       //MISSING\r
+DlgPasteRemoveStyles   : "Remove Styles definitions",  //MISSING\r
+DlgPasteCleanBox               : "Clean Up Box",       //MISSING\r
+\r
+// Color Picker\r
+ColorAutomatic : "Aŭtomata",\r
+ColorMoreColors        : "Pli da Koloroj...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumentaj Atributoj",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ankraj Atributoj",\r
+DlgAnchorName          : "Ankra Nomo",\r
+DlgAnchorErrorName     : "Bv tajpi la ankran nomon",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Ne trovita en la vortaro",\r
+DlgSpellChangeTo               : "Ŝanĝi al",\r
+DlgSpellBtnIgnore              : "Malatenti",\r
+DlgSpellBtnIgnoreAll   : "Malatenti Ĉiun",\r
+DlgSpellBtnReplace             : "Anstataŭigi",\r
+DlgSpellBtnReplaceAll  : "Anstataŭigi Ĉiun",\r
+DlgSpellBtnUndo                        : "Malfari",\r
+DlgSpellNoSuggestions  : "- Neniu propono -",\r
+DlgSpellProgress               : "Literumkontrolado daŭras...",\r
+DlgSpellNoMispell              : "Literumkontrolado finita: neniu fuŝo trovita",\r
+DlgSpellNoChanges              : "Literumkontrolado finita: neniu vorto ŝanĝita",\r
+DlgSpellOneChange              : "Literumkontrolado finita: unu vorto ŝanĝita",\r
+DlgSpellManyChanges            : "Literumkontrolado finita: %1 vortoj ŝanĝitaj",\r
+\r
+IeSpellDownload                        : "Literumada Kontrolilo ne instalita. Ĉu vi volas elŝuti ĝin nun?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Teksto (Valoro)",\r
+DlgButtonType          : "Tipo",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nomo",\r
+DlgCheckboxValue       : "Valoro",\r
+DlgCheckboxSelected    : "Elektita",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nomo",\r
+DlgFormAction  : "Ago",\r
+DlgFormMethod  : "Metodo",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nomo",\r
+DlgSelectValue         : "Valoro",\r
+DlgSelectSize          : "Grando",\r
+DlgSelectLines         : "Linioj",\r
+DlgSelectChkMulti      : "Permesi Plurajn Elektojn",\r
+DlgSelectOpAvail       : "Elektoj Disponeblaj",\r
+DlgSelectOpText                : "Teksto",\r
+DlgSelectOpValue       : "Valoro",\r
+DlgSelectBtnAdd                : "Aldoni",\r
+DlgSelectBtnModify     : "Modifi",\r
+DlgSelectBtnUp         : "Supren",\r
+DlgSelectBtnDown       : "Malsupren",\r
+DlgSelectBtnSetValue : "Agordi kiel Elektitan Valoron",\r
+DlgSelectBtnDelete     : "Forigi",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nomo",\r
+DlgTextareaCols        : "Kolumnoj",\r
+DlgTextareaRows        : "Vicoj",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nomo",\r
+DlgTextValue           : "Valoro",\r
+DlgTextCharWidth       : "Signolarĝo",\r
+DlgTextMaxChars                : "Maksimuma Nombro da Signoj",\r
+DlgTextType                    : "Tipo",\r
+DlgTextTypeText                : "Teksto",\r
+DlgTextTypePass                : "Pasvorto",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nomo",\r
+DlgHiddenValue : "Valoro",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Atributoj de Bula Listo",\r
+NumberedListProp       : "Atributoj de Numera Listo",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Tipo",\r
+DlgLstTypeCircle       : "Cirklo",\r
+DlgLstTypeDisc         : "Disc",       //MISSING\r
+DlgLstTypeSquare       : "Kvadrato",\r
+DlgLstTypeNumbers      : "Ciferoj (1, 2, 3)",\r
+DlgLstTypeLCase                : "Minusklaj Literoj (a, b, c)",\r
+DlgLstTypeUCase                : "Majusklaj Literoj (A, B, C)",\r
+DlgLstTypeSRoman       : "Malgrandaj Romanaj Ciferoj (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Grandaj Romanaj Ciferoj (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Ĝeneralaĵoj",\r
+DlgDocBackTab          : "Fono",\r
+DlgDocColorsTab                : "Koloroj kaj Marĝenoj",\r
+DlgDocMetaTab          : "Metadatumoj",\r
+\r
+DlgDocPageTitle                : "Paĝotitolo",\r
+DlgDocLangDir          : "Skribdirekto de la Lingvo",\r
+DlgDocLangDirLTR       : "De maldekstro dekstren (LTR)",\r
+DlgDocLangDirRTL       : "De dekstro maldekstren (LTR)",\r
+DlgDocLangCode         : "Lingvokodo",\r
+DlgDocCharSet          : "Signara Kodo",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Alia Signara Kodo",\r
+\r
+DlgDocDocType          : "Dokumenta Tipo",\r
+DlgDocDocTypeOther     : "Alia Dokumenta Tipo",\r
+DlgDocIncXHTML         : "Inkluzivi XHTML Deklaroj",\r
+DlgDocBgColor          : "Fona Koloro",\r
+DlgDocBgImage          : "URL de Fona Bildo",\r
+DlgDocBgNoScroll       : "Neruluma Fono",\r
+DlgDocCText                    : "Teksto",\r
+DlgDocCLink                    : "Ligilo",\r
+DlgDocCVisited         : "Vizitita Ligilo",\r
+DlgDocCActive          : "Aktiva Ligilo",\r
+DlgDocMargins          : "Paĝaj Marĝenoj",\r
+DlgDocMaTop                    : "Supra",\r
+DlgDocMaLeft           : "Maldekstra",\r
+DlgDocMaRight          : "Dekstra",\r
+DlgDocMaBottom         : "Malsupra",\r
+DlgDocMeIndex          : "Ŝlosilvortoj de la Dokumento (apartigita de komoj)",\r
+DlgDocMeDescr          : "Dokumenta Priskribo",\r
+DlgDocMeAuthor         : "Verkinto",\r
+DlgDocMeCopy           : "Kopirajto",\r
+DlgDocPreview          : "Aspekto",\r
+\r
+// Templates Dialog\r
+Templates                      : "Templates",  //MISSING\r
+DlgTemplatesTitle      : "Content Templates",  //MISSING\r
+DlgTemplatesSelMsg     : "Please select the template to open in the editor<br />(the actual contents will be lost):",  //MISSING\r
+DlgTemplatesLoading    : "Loading templates list. Please wait...",     //MISSING\r
+DlgTemplatesNoTpl      : "(No templates defined)",     //MISSING\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Pri",\r
+DlgAboutBrowserInfoTab : "Informoj pri TTT-legilo",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "versio",\r
+DlgAboutInfo           : "Por pli da informoj, vizitu"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/es.js b/httemplate/elements/fckeditor/editor/lang/es.js
new file mode 100644 (file)
index 0000000..cfed808
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Spanish language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Contraer Barra",\r
+ToolbarExpand          : "Expandir Barra",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Guardar",\r
+NewPage                                : "Nueva Página",\r
+Preview                                : "Vista Previa",\r
+Cut                                    : "Cortar",\r
+Copy                           : "Copiar",\r
+Paste                          : "Pegar",\r
+PasteText                      : "Pegar como texto plano",\r
+PasteWord                      : "Pegar desde Word",\r
+Print                          : "Imprimir",\r
+SelectAll                      : "Seleccionar Todo",\r
+RemoveFormat           : "Eliminar Formato",\r
+InsertLinkLbl          : "Vínculo",\r
+InsertLink                     : "Insertar/Editar Vínculo",\r
+RemoveLink                     : "Eliminar Vínculo",\r
+Anchor                         : "Referencia",\r
+InsertImageLbl         : "Imagen",\r
+InsertImage                    : "Insertar/Editar Imagen",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Insertar/Editar Flash",\r
+InsertTableLbl         : "Tabla",\r
+InsertTable                    : "Insertar/Editar Tabla",\r
+InsertLineLbl          : "Línea",\r
+InsertLine                     : "Insertar Línea Horizontal",\r
+InsertSpecialCharLbl: "Caracter Especial",\r
+InsertSpecialChar      : "Insertar Caracter Especial",\r
+InsertSmileyLbl                : "Emoticons",\r
+InsertSmiley           : "Insertar Emoticons",\r
+About                          : "Acerca de FCKeditor",\r
+Bold                           : "Negrita",\r
+Italic                         : "Cursiva",\r
+Underline                      : "Subrayado",\r
+StrikeThrough          : "Tachado",\r
+Subscript                      : "Subíndice",\r
+Superscript                    : "Superíndice",\r
+LeftJustify                    : "Alinear a Izquierda",\r
+CenterJustify          : "Centrar",\r
+RightJustify           : "Alinear a Derecha",\r
+BlockJustify           : "Justificado",\r
+DecreaseIndent         : "Disminuir Sangría",\r
+IncreaseIndent         : "Aumentar Sangría",\r
+Undo                           : "Deshacer",\r
+Redo                           : "Rehacer",\r
+NumberedListLbl                : "Numeración",\r
+NumberedList           : "Insertar/Eliminar Numeración",\r
+BulletedListLbl                : "Viñetas",\r
+BulletedList           : "Insertar/Eliminar Viñetas",\r
+ShowTableBorders       : "Mostrar Bordes de Tablas",\r
+ShowDetails                    : "Mostrar saltos de Párrafo",\r
+Style                          : "Estilo",\r
+FontFormat                     : "Formato",\r
+Font                           : "Fuente",\r
+FontSize                       : "Tamaño",\r
+TextColor                      : "Color de Texto",\r
+BGColor                                : "Color de Fondo",\r
+Source                         : "Fuente HTML",\r
+Find                           : "Buscar",\r
+Replace                                : "Reemplazar",\r
+SpellCheck                     : "Ortografía",\r
+UniversalKeyboard      : "Teclado Universal",\r
+PageBreakLbl           : "Salto de Página",\r
+PageBreak                      : "Insertar Salto de Página",\r
+\r
+Form                   : "Formulario",\r
+Checkbox               : "Casilla de Verificación",\r
+RadioButton            : "Botones de Radio",\r
+TextField              : "Campo de Texto",\r
+Textarea               : "Area de Texto",\r
+HiddenField            : "Campo Oculto",\r
+Button                 : "Botón",\r
+SelectionField : "Campo de Selección",\r
+ImageButton            : "Botón Imagen",\r
+\r
+FitWindow              : "Maximizar el tamaño del editor",\r
+\r
+// Context Menu\r
+EditLink                       : "Editar Vínculo",\r
+CellCM                         : "Celda",\r
+RowCM                          : "Fila",\r
+ColumnCM                       : "Columna",\r
+InsertRow                      : "Insertar Fila",\r
+DeleteRows                     : "Eliminar Filas",\r
+InsertColumn           : "Insertar Columna",\r
+DeleteColumns          : "Eliminar Columnas",\r
+InsertCell                     : "Insertar Celda",\r
+DeleteCells                    : "Eliminar Celdas",\r
+MergeCells                     : "Combinar Celdas",\r
+SplitCell                      : "Dividir Celda",\r
+TableDelete                    : "Eliminar Tabla",\r
+CellProperties         : "Propiedades de Celda",\r
+TableProperties                : "Propiedades de Tabla",\r
+ImageProperties                : "Propiedades de Imagen",\r
+FlashProperties                : "Propiedades de Flash",\r
+\r
+AnchorProp                     : "Propiedades de Referencia",\r
+ButtonProp                     : "Propiedades de Botón",\r
+CheckboxProp           : "Propiedades de Casilla",\r
+HiddenFieldProp                : "Propiedades de Campo Oculto",\r
+RadioButtonProp                : "Propiedades de Botón de Radio",\r
+ImageButtonProp                : "Propiedades de Botón de Imagen",\r
+TextFieldProp          : "Propiedades de Campo de Texto",\r
+SelectionFieldProp     : "Propiedades de Campo de Selección",\r
+TextareaProp           : "Propiedades de Area de Texto",\r
+FormProp                       : "Propiedades de Formulario",\r
+\r
+FontFormats                    : "Normal;Con formato;Dirección;Encabezado 1;Encabezado 2;Encabezado 3;Encabezado 4;Encabezado 5;Encabezado 6;Normal (DIV)",           //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Procesando XHTML. Por favor, espere...",\r
+Done                           : "Hecho",\r
+PasteWordConfirm       : "El texto que desea parece provenir de Word. Desea depurarlo antes de pegarlo?",\r
+NotCompatiblePaste     : "Este comando está disponible sólo para Internet Explorer version 5.5 or superior. Desea pegar sin depurar?",\r
+UnknownToolbarItem     : "Item de barra desconocido \"%1\"",\r
+UnknownCommand         : "Nombre de comando desconocido \"%1\"",\r
+NotImplemented         : "Comando no implementado",\r
+UnknownToolbarSet      : "Nombre de barra \"%1\" no definido",\r
+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.",\r
+BrowseServerBlocked : "La ventana de visualización del servidor no pudo ser abierta. Verifique que su navegador no esté bloqueando las ventanas emergentes (pop up).",\r
+DialogBlocked          : "No se ha podido abrir la ventana de diálogo. Verifique que su navegador no esté bloqueando las ventanas emergentes (pop up).",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Cancelar",\r
+DlgBtnClose                    : "Cerrar",\r
+DlgBtnBrowseServer     : "Ver Servidor",\r
+DlgAdvancedTag         : "Avanzado",\r
+DlgOpOther                     : "<Otro>",\r
+DlgInfoTab                     : "Información",\r
+DlgAlertUrl                    : "Inserte el URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<No definido>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Orientación de idioma",\r
+DlgGenLangDirLtr       : "Izquierda a Derecha (LTR)",\r
+DlgGenLangDirRtl       : "Derecha a Izquierda (RTL)",\r
+DlgGenLangCode         : "Código de idioma",\r
+DlgGenAccessKey                : "Clave de Acceso",\r
+DlgGenName                     : "Nombre",\r
+DlgGenTabIndex         : "Indice de tabulación",\r
+DlgGenLongDescr                : "Descripción larga URL",\r
+DlgGenClass                    : "Clases de hojas de estilo",\r
+DlgGenTitle                    : "Título",\r
+DlgGenContType         : "Tipo de Contenido",\r
+DlgGenLinkCharset      : "Fuente de caracteres vinculado",\r
+DlgGenStyle                    : "Estilo",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Propiedades de Imagen",\r
+DlgImgInfoTab          : "Información de Imagen",\r
+DlgImgBtnUpload                : "Enviar al Servidor",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Cargar",\r
+DlgImgAlt                      : "Texto Alternativo",\r
+DlgImgWidth                    : "Anchura",\r
+DlgImgHeight           : "Altura",\r
+DlgImgLockRatio                : "Proporcional",\r
+DlgBtnResetSize                : "Tamaño Original",\r
+DlgImgBorder           : "Borde",\r
+DlgImgHSpace           : "Esp.Horiz",\r
+DlgImgVSpace           : "Esp.Vert",\r
+DlgImgAlign                    : "Alineación",\r
+DlgImgAlignLeft                : "Izquierda",\r
+DlgImgAlignAbsBottom: "Abs inferior",\r
+DlgImgAlignAbsMiddle: "Abs centro",\r
+DlgImgAlignBaseline    : "Línea de base",\r
+DlgImgAlignBottom      : "Pie",\r
+DlgImgAlignMiddle      : "Centro",\r
+DlgImgAlignRight       : "Derecha",\r
+DlgImgAlignTextTop     : "Tope del texto",\r
+DlgImgAlignTop         : "Tope",\r
+DlgImgPreview          : "Vista Previa",\r
+DlgImgAlertUrl         : "Por favor tipee el URL de la imagen",\r
+DlgImgLinkTab          : "Vínculo",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Propiedades de Flash",\r
+DlgFlashChkPlay                : "Autoejecución",\r
+DlgFlashChkLoop                : "Repetir",\r
+DlgFlashChkMenu                : "Activar Menú Flash",\r
+DlgFlashScale          : "Escala",\r
+DlgFlashScaleAll       : "Mostrar todo",\r
+DlgFlashScaleNoBorder  : "Sin Borde",\r
+DlgFlashScaleFit       : "Ajustado",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Vínculo",\r
+DlgLnkInfoTab          : "Información de Vínculo",\r
+DlgLnkTargetTab                : "Destino",\r
+\r
+DlgLnkType                     : "Tipo de vínculo",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Referencia en esta página",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocolo",\r
+DlgLnkProtoOther       : "<otro>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Seleccionar una referencia",\r
+DlgLnkAnchorByName     : "Por Nombre de Referencia",\r
+DlgLnkAnchorById       : "Por ID de elemento",\r
+DlgLnkNoAnchors                : "<No hay referencias disponibles en el documento>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Dirección de E-Mail",\r
+DlgLnkEMailSubject     : "Título del Mensaje",\r
+DlgLnkEMailBody                : "Cuerpo del Mensaje",\r
+DlgLnkUpload           : "Cargar",\r
+DlgLnkBtnUpload                : "Enviar al Servidor",\r
+\r
+DlgLnkTarget           : "Destino",\r
+DlgLnkTargetFrame      : "<marco>",\r
+DlgLnkTargetPopup      : "<ventana emergente>",\r
+DlgLnkTargetBlank      : "Nueva Ventana(_blank)",\r
+DlgLnkTargetParent     : "Ventana Padre (_parent)",\r
+DlgLnkTargetSelf       : "Misma Ventana (_self)",\r
+DlgLnkTargetTop                : "Ventana primaria (_top)",\r
+DlgLnkTargetFrameName  : "Nombre del Marco Destino",\r
+DlgLnkPopWinName       : "Nombre de Ventana Emergente",\r
+DlgLnkPopWinFeat       : "Características de Ventana Emergente",\r
+DlgLnkPopResize                : "Ajustable",\r
+DlgLnkPopLocation      : "Barra de ubicación",\r
+DlgLnkPopMenu          : "Barra de Menú",\r
+DlgLnkPopScroll                : "Barras de desplazamiento",\r
+DlgLnkPopStatus                : "Barra de Estado",\r
+DlgLnkPopToolbar       : "Barra de Herramientas",\r
+DlgLnkPopFullScrn      : "Pantalla Completa (IE)",\r
+DlgLnkPopDependent     : "Dependiente (Netscape)",\r
+DlgLnkPopWidth         : "Anchura",\r
+DlgLnkPopHeight                : "Altura",\r
+DlgLnkPopLeft          : "Posición Izquierda",\r
+DlgLnkPopTop           : "Posición Derecha",\r
+\r
+DlnLnkMsgNoUrl         : "Por favor tipee el vínculo URL",\r
+DlnLnkMsgNoEMail       : "Por favor tipee la dirección de e-mail",\r
+DlnLnkMsgNoAnchor      : "Por favor seleccione una referencia",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Seleccionar Color",\r
+DlgColorBtnClear       : "Ninguno",\r
+DlgColorHighlight      : "Resaltado",\r
+DlgColorSelected       : "Seleccionado",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Insertar un Emoticon",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Seleccione un caracter especial",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Propiedades de Tabla",\r
+DlgTableRows           : "Filas",\r
+DlgTableColumns                : "Columnas",\r
+DlgTableBorder         : "Tamaño de Borde",\r
+DlgTableAlign          : "Alineación",\r
+DlgTableAlignNotSet    : "<No establecido>",\r
+DlgTableAlignLeft      : "Izquierda",\r
+DlgTableAlignCenter    : "Centrado",\r
+DlgTableAlignRight     : "Derecha",\r
+DlgTableWidth          : "Anchura",\r
+DlgTableWidthPx                : "pixeles",\r
+DlgTableWidthPc                : "porcentaje",\r
+DlgTableHeight         : "Altura",\r
+DlgTableCellSpace      : "Esp. e/celdas",\r
+DlgTableCellPad                : "Esp. interior",\r
+DlgTableCaption                : "Título",\r
+DlgTableSummary                : "Síntesis",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Propiedades de Celda",\r
+DlgCellWidth           : "Anchura",\r
+DlgCellWidthPx         : "pixeles",\r
+DlgCellWidthPc         : "porcentaje",\r
+DlgCellHeight          : "Altura",\r
+DlgCellWordWrap                : "Cortar Línea",\r
+DlgCellWordWrapNotSet  : "<No establecido>",\r
+DlgCellWordWrapYes     : "Si",\r
+DlgCellWordWrapNo      : "No",\r
+DlgCellHorAlign                : "Alineación Horizontal",\r
+DlgCellHorAlignNotSet  : "<No establecido>",\r
+DlgCellHorAlignLeft    : "Izquierda",\r
+DlgCellHorAlignCenter  : "Centrado",\r
+DlgCellHorAlignRight: "Derecha",\r
+DlgCellVerAlign                : "Alineación Vertical",\r
+DlgCellVerAlignNotSet  : "<Not establecido>",\r
+DlgCellVerAlignTop     : "Tope",\r
+DlgCellVerAlignMiddle  : "Medio",\r
+DlgCellVerAlignBottom  : "ie",\r
+DlgCellVerAlignBaseline        : "Línea de Base",\r
+DlgCellRowSpan         : "Abarcar Filas",\r
+DlgCellCollSpan                : "Abarcar Columnas",\r
+DlgCellBackColor       : "Color de Fondo",\r
+DlgCellBorderColor     : "Color de Borde",\r
+DlgCellBtnSelect       : "Seleccione...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Buscar",\r
+DlgFindFindBtn         : "Buscar",\r
+DlgFindNotFoundMsg     : "El texto especificado no ha sido encontrado.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Reemplazar",\r
+DlgReplaceFindLbl              : "Texto a buscar:",\r
+DlgReplaceReplaceLbl   : "Reemplazar con:",\r
+DlgReplaceCaseChk              : "Coincidir may/min",\r
+DlgReplaceReplaceBtn   : "Reemplazar",\r
+DlgReplaceReplAllBtn   : "Reemplazar Todo",\r
+DlgReplaceWordChk              : "Coincidir toda la palabra",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Pegar como Texto Plano",\r
+PasteFromWord  : "Pegar desde Word",\r
+\r
+DlgPasteMsg2   : "Por favor pegue dentro del cuadro utilizando el teclado (<STRONG>Ctrl+V</STRONG>); luego presione <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorar definiciones de fuentes",\r
+DlgPasteRemoveStyles   : "Remover definiciones de estilo",\r
+DlgPasteCleanBox               : "Borrar el contenido del cuadro",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automático",\r
+ColorMoreColors        : "Más Colores...",\r
+\r
+// Document Properties\r
+DocProps               : "Propiedades del Documento",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Propiedades de la Referencia",\r
+DlgAnchorName          : "Nombre de la Referencia",\r
+DlgAnchorErrorName     : "Por favor, complete el nombre de la Referencia",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "No se encuentra en el Diccionario",\r
+DlgSpellChangeTo               : "Cambiar a",\r
+DlgSpellBtnIgnore              : "Ignorar",\r
+DlgSpellBtnIgnoreAll   : "Ignorar Todo",\r
+DlgSpellBtnReplace             : "Reemplazar",\r
+DlgSpellBtnReplaceAll  : "Reemplazar Todo",\r
+DlgSpellBtnUndo                        : "Deshacer",\r
+DlgSpellNoSuggestions  : "- No hay sugerencias -",\r
+DlgSpellProgress               : "Control de Ortografía en progreso...",\r
+DlgSpellNoMispell              : "Control finalizado: no se encontraron errores",\r
+DlgSpellNoChanges              : "Control finalizado: no se ha cambiado ninguna palabra",\r
+DlgSpellOneChange              : "Control finalizado: se ha cambiado una palabra",\r
+DlgSpellManyChanges            : "Control finalizado: se ha cambiado %1 palabras",\r
+\r
+IeSpellDownload                        : "Módulo de Control de Ortografía no instalado. ¿Desea descargarlo ahora?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Texto (Valor)",\r
+DlgButtonType          : "Tipo",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nombre",\r
+DlgCheckboxValue       : "Valor",\r
+DlgCheckboxSelected    : "Seleccionado",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nombre",\r
+DlgFormAction  : "Acción",\r
+DlgFormMethod  : "Método",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nombre",\r
+DlgSelectValue         : "Valor",\r
+DlgSelectSize          : "Tamaño",\r
+DlgSelectLines         : "Lineas",\r
+DlgSelectChkMulti      : "Permitir múltiple selección",\r
+DlgSelectOpAvail       : "Opciones disponibles",\r
+DlgSelectOpText                : "Texto",\r
+DlgSelectOpValue       : "Valor",\r
+DlgSelectBtnAdd                : "Agregar",\r
+DlgSelectBtnModify     : "Modificar",\r
+DlgSelectBtnUp         : "Subir",\r
+DlgSelectBtnDown       : "Bajar",\r
+DlgSelectBtnSetValue : "Establecer como predeterminado",\r
+DlgSelectBtnDelete     : "Eliminar",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nombre",\r
+DlgTextareaCols        : "Columnas",\r
+DlgTextareaRows        : "Filas",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nombre",\r
+DlgTextValue           : "Valor",\r
+DlgTextCharWidth       : "Caracteres de ancho",\r
+DlgTextMaxChars                : "Máximo caracteres",\r
+DlgTextType                    : "Tipo",\r
+DlgTextTypeText                : "Texto",\r
+DlgTextTypePass                : "Contraseña",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nombre",\r
+DlgHiddenValue : "Valor",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Propiedades de Viñetas",\r
+NumberedListProp       : "Propiedades de Numeraciones",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Tipo",\r
+DlgLstTypeCircle       : "Círculo",\r
+DlgLstTypeDisc         : "Disco",\r
+DlgLstTypeSquare       : "Cuadrado",\r
+DlgLstTypeNumbers      : "Números (1, 2, 3)",\r
+DlgLstTypeLCase                : "letras en minúsculas (a, b, c)",\r
+DlgLstTypeUCase                : "letras en mayúsculas (A, B, C)",\r
+DlgLstTypeSRoman       : "Números Romanos (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Números Romanos (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",\r
+DlgDocBackTab          : "Fondo",\r
+DlgDocColorsTab                : "Colores y Márgenes",\r
+DlgDocMetaTab          : "Meta Información",\r
+\r
+DlgDocPageTitle                : "Título de Página",\r
+DlgDocLangDir          : "Orientación de idioma",\r
+DlgDocLangDirLTR       : "Izq. a Derecha (LTR)",\r
+DlgDocLangDirRTL       : "Der. a Izquierda (RTL)",\r
+DlgDocLangCode         : "Código de Idioma",\r
+DlgDocCharSet          : "Codif. de Conjunto de Caracteres",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Otra Codificación",\r
+\r
+DlgDocDocType          : "Encabezado de Tipo de Documento",\r
+DlgDocDocTypeOther     : "Otro Encabezado",\r
+DlgDocIncXHTML         : "Incluir Declaraciones XHTML",\r
+DlgDocBgColor          : "Color de Fondo",\r
+DlgDocBgImage          : "URL de Imagen de Fondo",\r
+DlgDocBgNoScroll       : "Fondo sin rolido",\r
+DlgDocCText                    : "Texto",\r
+DlgDocCLink                    : "Vínculo",\r
+DlgDocCVisited         : "Vínculo Visitado",\r
+DlgDocCActive          : "Vínculo Activo",\r
+DlgDocMargins          : "Márgenes de Página",\r
+DlgDocMaTop                    : "Tope",\r
+DlgDocMaLeft           : "Izquierda",\r
+DlgDocMaRight          : "Derecha",\r
+DlgDocMaBottom         : "Pie",\r
+DlgDocMeIndex          : "Claves de indexación del Documento (separados por comas)",\r
+DlgDocMeDescr          : "Descripción del Documento",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Vista Previa",\r
+\r
+// Templates Dialog\r
+Templates                      : "Plantillas",\r
+DlgTemplatesTitle      : "Contenido de Plantillas",\r
+DlgTemplatesSelMsg     : "Por favor selecciona la plantilla a abrir en el editor<br>(el contenido actual se perderá):",\r
+DlgTemplatesLoading    : "Cargando lista de Plantillas. Por favor, aguarde...",\r
+DlgTemplatesNoTpl      : "(No hay plantillas definidas)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Acerca de",\r
+DlgAboutBrowserInfoTab : "Información de Navegador",\r
+DlgAboutLicenseTab     : "Licencia",\r
+DlgAboutVersion                : "versión",\r
+DlgAboutInfo           : "Para mayor información por favor dirigirse a"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/et.js b/httemplate/elements/fckeditor/editor/lang/et.js
new file mode 100644 (file)
index 0000000..53a147e
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Estonian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Voldi tööriistariba",\r
+ToolbarExpand          : "Laienda tööriistariba",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Salvesta",\r
+NewPage                                : "Uus leht",\r
+Preview                                : "Eelvaade",\r
+Cut                                    : "Lõika",\r
+Copy                           : "Kopeeri",\r
+Paste                          : "Kleebi",\r
+PasteText                      : "Kleebi tavalise tekstina",\r
+PasteWord                      : "Kleebi Wordist",\r
+Print                          : "Prindi",\r
+SelectAll                      : "Vali kõik",\r
+RemoveFormat           : "Eemalda vorming",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Sisesta/Muuda link",\r
+RemoveLink                     : "Eemalda link",\r
+Anchor                         : "Sisesta/Muuda ankur",\r
+InsertImageLbl         : "Pilt",\r
+InsertImage                    : "Sisesta/Muuda pilt",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Sisesta/Muuda flash",\r
+InsertTableLbl         : "Tabel",\r
+InsertTable                    : "Sisesta/Muuda tabel",\r
+InsertLineLbl          : "Joon",\r
+InsertLine                     : "Sisesta horisontaaljoon",\r
+InsertSpecialCharLbl: "Erimärgid",\r
+InsertSpecialChar      : "Sisesta erimärk",\r
+InsertSmileyLbl                : "Emotikon",\r
+InsertSmiley           : "Sisesta emotikon",\r
+About                          : "FCKeditor teave",\r
+Bold                           : "Rasvane kiri",\r
+Italic                         : "Kursiiv kiri",\r
+Underline                      : "Allajoonitud kiri",\r
+StrikeThrough          : "Läbijoonitud kiri",\r
+Subscript                      : "Allindeks",\r
+Superscript                    : "Ülaindeks",\r
+LeftJustify                    : "Vasakjoondus",\r
+CenterJustify          : "Keskjoondus",\r
+RightJustify           : "Paremjoondus",\r
+BlockJustify           : "Rööpjoondus",\r
+DecreaseIndent         : "Vähenda taanet",\r
+IncreaseIndent         : "Suurenda taanet",\r
+Undo                           : "Võta tagasi",\r
+Redo                           : "Korda toimingut",\r
+NumberedListLbl                : "Nummerdatud loetelu",\r
+NumberedList           : "Sisesta/Eemalda nummerdatud loetelu",\r
+BulletedListLbl                : "Punktiseeritud loetelu",\r
+BulletedList           : "Sisesta/Eemalda punktiseeritud loetelu",\r
+ShowTableBorders       : "Näita tabeli jooni",\r
+ShowDetails                    : "Näita üksikasju",\r
+Style                          : "Laad",\r
+FontFormat                     : "Vorming",\r
+Font                           : "Kiri",\r
+FontSize                       : "Suurus",\r
+TextColor                      : "Teksti värv",\r
+BGColor                                : "Tausta värv",\r
+Source                         : "Lähtekood",\r
+Find                           : "Otsi",\r
+Replace                                : "Asenda",\r
+SpellCheck                     : "Kontrolli õigekirja",\r
+UniversalKeyboard      : "Universaalne klaviatuur",\r
+PageBreakLbl           : "Lehepiir",\r
+PageBreak                      : "Sisesta lehevahetus koht",\r
+\r
+Form                   : "Vorm",\r
+Checkbox               : "Märkeruut",\r
+RadioButton            : "Raadionupp",\r
+TextField              : "Tekstilahter",\r
+Textarea               : "Tekstiala",\r
+HiddenField            : "Varjatud lahter",\r
+Button                 : "Nupp",\r
+SelectionField : "Valiklahter",\r
+ImageButton            : "Piltnupp",\r
+\r
+FitWindow              : "Maksimeeri redaktori mõõtmed",\r
+\r
+// Context Menu\r
+EditLink                       : "Muuda linki",\r
+CellCM                         : "Lahter",\r
+RowCM                          : "Rida",\r
+ColumnCM                       : "Veerg",\r
+InsertRow                      : "Lisa rida",\r
+DeleteRows                     : "Eemalda ridu",\r
+InsertColumn           : "Lisa veerg",\r
+DeleteColumns          : "Eemalda veerud",\r
+InsertCell                     : "Lisa lahter",\r
+DeleteCells                    : "Eemalda lahtrid",\r
+MergeCells                     : "Ühenda lahtrid",\r
+SplitCell                      : "Lahuta lahtrid",\r
+TableDelete                    : "Kustuta tabel",\r
+CellProperties         : "Lahtri atribuudid",\r
+TableProperties                : "Tabeli atribuudid",\r
+ImageProperties                : "Pildi  atribuudid",\r
+FlashProperties                : "Flash omadused",\r
+\r
+AnchorProp                     : "Ankru omadused",\r
+ButtonProp                     : "Nupu omadused",\r
+CheckboxProp           : "Märkeruudu omadused",\r
+HiddenFieldProp                : "Varjatud lahtri omadused",\r
+RadioButtonProp                : "Raadionupu omadused",\r
+ImageButtonProp                : "Piltnupu omadused",\r
+TextFieldProp          : "Tekstilahtri omadused",\r
+SelectionFieldProp     : "Valiklahtri omadused",\r
+TextareaProp           : "Tekstiala omadused",\r
+FormProp                       : "Vormi omadused",\r
+\r
+FontFormats                    : "Tavaline;Vormindatud;Aadress;Pealkiri 1;Pealkiri 2;Pealkiri 3;Pealkiri 4;Pealkiri 5;Pealkiri 6;Tavaline (DIV)",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Töötlen XHTML. Palun oota...",\r
+Done                           : "Tehtud",\r
+PasteWordConfirm       : "Tekst, mida soovid lisada paistab pärinevat Wordist. Kas soovid seda enne kleepimist puhastada?",\r
+NotCompatiblePaste     : "See käsk on saadaval ainult Internet Explorer versioon 5.5 või uuema puhul. Kas soovid kleepida ilma puhastamata?",\r
+UnknownToolbarItem     : "Tundmatu tööriistariba üksus \"%1\"",\r
+UnknownCommand         : "Tundmatu käsunimi \"%1\"",\r
+NotImplemented         : "Käsku ei täidetud",\r
+UnknownToolbarSet      : "Tööriistariba \"%1\" ei eksisteeri",\r
+NoActiveX                      : "Sinu veebisirvija turvalisuse seaded võivad limiteerida mõningaid tekstirdaktori kasutus võimalusi. Sa peaksid võimaldama valiku \"Run ActiveX controls and plug-ins\" oma sirvija seadetes. Muidu võid sa täheldada vigu tekstiredaktori töös ja märgata puuduvaid funktsioone.",\r
+BrowseServerBlocked : "Ressursside sirvija avamine ebaõnnestus. Võimalda pop-up akende avanemine.",\r
+DialogBlocked          : "Ei olenud võimalik avada dialoogi akent. Võimalda pop-up akende avanemine.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Loobu",\r
+DlgBtnClose                    : "Sulge",\r
+DlgBtnBrowseServer     : "Sirvi serverit",\r
+DlgAdvancedTag         : "Täpsemalt",\r
+DlgOpOther                     : "<Teine>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Palun sisesta URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<määramata>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Keele suund",\r
+DlgGenLangDirLtr       : "Vasakult paremale (LTR)",\r
+DlgGenLangDirRtl       : "Paremalt vasakule (RTL)",\r
+DlgGenLangCode         : "Keele kood",\r
+DlgGenAccessKey                : "Juurdepääsu võti",\r
+DlgGenName                     : "Nimi",\r
+DlgGenTabIndex         : "Tab indeks",\r
+DlgGenLongDescr                : "Pikk kirjeldus URL",\r
+DlgGenClass                    : "Stiilistiku klassid",\r
+DlgGenTitle                    : "Juhendav tiitel",\r
+DlgGenContType         : "Juhendava sisu tüüp",\r
+DlgGenLinkCharset      : "Lingitud ressurssi märgistik",\r
+DlgGenStyle                    : "Laad",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Pildi atribuudid",\r
+DlgImgInfoTab          : "Pildi info",\r
+DlgImgBtnUpload                : "Saada serverissee",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Lae üles",\r
+DlgImgAlt                      : "Alternatiivne tekst",\r
+DlgImgWidth                    : "Laius",\r
+DlgImgHeight           : "Kõrgus",\r
+DlgImgLockRatio                : "Lukusta kuvasuhe",\r
+DlgBtnResetSize                : "Lähtesta suurus",\r
+DlgImgBorder           : "Joon",\r
+DlgImgHSpace           : "H. vaheruum",\r
+DlgImgVSpace           : "V. vaheruum",\r
+DlgImgAlign                    : "Joondus",\r
+DlgImgAlignLeft                : "Vasak",\r
+DlgImgAlignAbsBottom: "Abs alla",\r
+DlgImgAlignAbsMiddle: "Abs keskele",\r
+DlgImgAlignBaseline    : "Baasjoonele",\r
+DlgImgAlignBottom      : "Alla",\r
+DlgImgAlignMiddle      : "Keskele",\r
+DlgImgAlignRight       : "Paremale",\r
+DlgImgAlignTextTop     : "Tekstit üles",\r
+DlgImgAlignTop         : "Üles",\r
+DlgImgPreview          : "Eelvaade",\r
+DlgImgAlertUrl         : "Palun kirjuta pildi URL",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash omadused",\r
+DlgFlashChkPlay                : "Automaatne start ",\r
+DlgFlashChkLoop                : "Korduv",\r
+DlgFlashChkMenu                : "Võimalda flash menüü",\r
+DlgFlashScale          : "Mastaap",\r
+DlgFlashScaleAll       : "Näita kõike",\r
+DlgFlashScaleNoBorder  : "Äärist ei ole",\r
+DlgFlashScaleFit       : "Täpne sobivus",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Lingi info",\r
+DlgLnkTargetTab                : "Sihtkoht",\r
+\r
+DlgLnkType                     : "Lingi tüüp",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Ankur sellel lehel",\r
+DlgLnkTypeEMail                : "E-post",\r
+DlgLnkProto                    : "Protokoll",\r
+DlgLnkProtoOther       : "<muu>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Vali ankur",\r
+DlgLnkAnchorByName     : "Ankru nime järgi",\r
+DlgLnkAnchorById       : "Elemendi id järgi",\r
+DlgLnkNoAnchors                : "(Selles dokumendis ei ole ankruid)",         //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-posti aadress",\r
+DlgLnkEMailSubject     : "Sõnumi teema",\r
+DlgLnkEMailBody                : "Sõnumi tekst",\r
+DlgLnkUpload           : "Lae üles",\r
+DlgLnkBtnUpload                : "Saada serverisse",\r
+\r
+DlgLnkTarget           : "Sihtkoht",\r
+DlgLnkTargetFrame      : "<raam>",\r
+DlgLnkTargetPopup      : "<hüpikaken>",\r
+DlgLnkTargetBlank      : "Uus aken (_blank)",\r
+DlgLnkTargetParent     : "Vanem aken (_parent)",\r
+DlgLnkTargetSelf       : "Sama aken (_self)",\r
+DlgLnkTargetTop                : "Pealmine aken (_top)",\r
+DlgLnkTargetFrameName  : "Sihtmärk raami nimi",\r
+DlgLnkPopWinName       : "Hüpikakna nimi",\r
+DlgLnkPopWinFeat       : "Hüpikakna omadused",\r
+DlgLnkPopResize                : "Suurendatav",\r
+DlgLnkPopLocation      : "Aadressiriba",\r
+DlgLnkPopMenu          : "Menüüriba",\r
+DlgLnkPopScroll                : "Kerimisribad",\r
+DlgLnkPopStatus                : "Olekuriba",\r
+DlgLnkPopToolbar       : "Tööriistariba",\r
+DlgLnkPopFullScrn      : "Täisekraan (IE)",\r
+DlgLnkPopDependent     : "Sõltuv (Netscape)",\r
+DlgLnkPopWidth         : "Laius",\r
+DlgLnkPopHeight                : "Kõrgus",\r
+DlgLnkPopLeft          : "Vasak asukoht",\r
+DlgLnkPopTop           : "Ülemine asukoht",\r
+\r
+DlnLnkMsgNoUrl         : "Palun kirjuta lingi URL",\r
+DlnLnkMsgNoEMail       : "Palun kirjuta E-Posti aadress",\r
+DlnLnkMsgNoAnchor      : "Palun vali ankur",\r
+DlnLnkMsgInvPopName    : "Hüpikakna nimi peab algama alfabeetilise tähega ja ei tohi sisaldada tühikuid",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Vali värv",\r
+DlgColorBtnClear       : "Tühjenda",\r
+DlgColorHighlight      : "Märgi",\r
+DlgColorSelected       : "Valitud",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Sisesta emotikon",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Vali erimärk",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Tabeli atribuudid",\r
+DlgTableRows           : "Read",\r
+DlgTableColumns                : "Veerud",\r
+DlgTableBorder         : "Joone suurus",\r
+DlgTableAlign          : "Joondus",\r
+DlgTableAlignNotSet    : "<Määramata>",\r
+DlgTableAlignLeft      : "Vasak",\r
+DlgTableAlignCenter    : "Kesk",\r
+DlgTableAlignRight     : "Parem",\r
+DlgTableWidth          : "Laius",\r
+DlgTableWidthPx                : "pikslit",\r
+DlgTableWidthPc                : "protsenti",\r
+DlgTableHeight         : "Kõrgus",\r
+DlgTableCellSpace      : "Lahtri vahe",\r
+DlgTableCellPad                : "Lahtri täidis",\r
+DlgTableCaption                : "Tabeli tiitel",\r
+DlgTableSummary                : "Kokkuvõte",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Lahtri atribuudid",\r
+DlgCellWidth           : "Laius",\r
+DlgCellWidthPx         : "pikslit",\r
+DlgCellWidthPc         : "protsenti",\r
+DlgCellHeight          : "Kõrgus",\r
+DlgCellWordWrap                : "Sõna ülekanne",\r
+DlgCellWordWrapNotSet  : "<Määramata>",\r
+DlgCellWordWrapYes     : "Jah",\r
+DlgCellWordWrapNo      : "Ei",\r
+DlgCellHorAlign                : "Horisontaaljoondus",\r
+DlgCellHorAlignNotSet  : "<Määramata>",\r
+DlgCellHorAlignLeft    : "Vasak",\r
+DlgCellHorAlignCenter  : "Kesk",\r
+DlgCellHorAlignRight: "Parem",\r
+DlgCellVerAlign                : "Vertikaaljoondus",\r
+DlgCellVerAlignNotSet  : "<Määramata>",\r
+DlgCellVerAlignTop     : "Üles",\r
+DlgCellVerAlignMiddle  : "Keskele",\r
+DlgCellVerAlignBottom  : "Alla",\r
+DlgCellVerAlignBaseline        : "Baasjoonele",\r
+DlgCellRowSpan         : "Reaulatus",\r
+DlgCellCollSpan                : "Veeruulatus",\r
+DlgCellBackColor       : "Tausta värv",\r
+DlgCellBorderColor     : "Joone värv",\r
+DlgCellBtnSelect       : "Vali...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Otsi",\r
+DlgFindFindBtn         : "Otsi",\r
+DlgFindNotFoundMsg     : "Valitud teksti ei leitud.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Asenda",\r
+DlgReplaceFindLbl              : "Leia mida:",\r
+DlgReplaceReplaceLbl   : "Asenda millega:",\r
+DlgReplaceCaseChk              : "Erista suur- ja väiketähti",\r
+DlgReplaceReplaceBtn   : "Asenda",\r
+DlgReplaceReplAllBtn   : "Asenda kõik",\r
+DlgReplaceWordChk              : "Otsi terviklike sõnu",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Sinu veebisirvija turvaseaded ei luba redaktoril automaatselt lõigata. Palun kasutage selleks klaviatuuri klahvikombinatsiooni (Ctrl+X).",\r
+PasteErrorCopy : "Sinu veebisirvija turvaseaded ei luba redaktoril automaatselt kopeerida. Palun kasutage selleks klaviatuuri klahvikombinatsiooni (Ctrl+C).",\r
+\r
+PasteAsText            : "Kleebi tavalise tekstina",\r
+PasteFromWord  : "Kleebi Wordist",\r
+\r
+DlgPasteMsg2   : "Palun kleebi järgnevasse kasti kasutades klaviatuuri klahvikombinatsiooni (<STRONG>Ctrl+V</STRONG>) ja vajuta seejärel <STRONG>OK</STRONG>.",\r
+DlgPasteSec            : "Sinu veebisirvija turvaseadete tõttu, ei oma redaktor otsest ligipääsu lõikelaua andmetele. Sa pead kleepima need uuesti siia aknasse.",\r
+DlgPasteIgnoreFont             : "Ignoreeri kirja definitsioone",\r
+DlgPasteRemoveStyles   : "Eemalda stiilide definitsioonid",\r
+DlgPasteCleanBox               : "Puhasta ära kast",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automaatne",\r
+ColorMoreColors        : "Rohkem värve...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumendi omadused",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ankru omadused",\r
+DlgAnchorName          : "Ankru nimi",\r
+DlgAnchorErrorName     : "Palun sisest ankru nimi",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Puudub sõnastikust",\r
+DlgSpellChangeTo               : "Muuda",\r
+DlgSpellBtnIgnore              : "Ignoreeri",\r
+DlgSpellBtnIgnoreAll   : "Ignoreeri kõiki",\r
+DlgSpellBtnReplace             : "Asenda",\r
+DlgSpellBtnReplaceAll  : "Asenda kõik",\r
+DlgSpellBtnUndo                        : "Võta tagasi",\r
+DlgSpellNoSuggestions  : "- Soovitused puuduvad -",\r
+DlgSpellProgress               : "Toimub õigekirja kontroll...",\r
+DlgSpellNoMispell              : "Õigekirja kontroll sooritatud: õigekirjuvigu ei leitud",\r
+DlgSpellNoChanges              : "Õigekirja kontroll sooritatud: ühtegi sõna ei muudetud",\r
+DlgSpellOneChange              : "Õigekirja kontroll sooritatud: üks sõna muudeti",\r
+DlgSpellManyChanges            : "Õigekirja kontroll sooritatud: %1 sõna muudetud",\r
+\r
+IeSpellDownload                        : "Õigekirja kontrollija ei ole installeeritud. Soovid sa selle alla laadida?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekst (väärtus)",\r
+DlgButtonType          : "Tüüp",\r
+DlgButtonTypeBtn       : "Nupp",\r
+DlgButtonTypeSbm       : "Saada",\r
+DlgButtonTypeRst       : "Lähtesta",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nimi",\r
+DlgCheckboxValue       : "Väärtus",\r
+DlgCheckboxSelected    : "Valitud",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nimi",\r
+DlgFormAction  : "Toiming",\r
+DlgFormMethod  : "Meetod",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nimi",\r
+DlgSelectValue         : "Väärtus",\r
+DlgSelectSize          : "Suurus",\r
+DlgSelectLines         : "ridu",\r
+DlgSelectChkMulti      : "Võimalda mitu valikut",\r
+DlgSelectOpAvail       : "Võimalikud valikud",\r
+DlgSelectOpText                : "Tekst",\r
+DlgSelectOpValue       : "Väärtus",\r
+DlgSelectBtnAdd                : "Lisa",\r
+DlgSelectBtnModify     : "Muuda",\r
+DlgSelectBtnUp         : "Üles",\r
+DlgSelectBtnDown       : "Alla",\r
+DlgSelectBtnSetValue : "Sea valitud olekuna",\r
+DlgSelectBtnDelete     : "Kustuta",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nimi",\r
+DlgTextareaCols        : "Veerge",\r
+DlgTextareaRows        : "Ridu",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nimi",\r
+DlgTextValue           : "Väärtus",\r
+DlgTextCharWidth       : "Laius (tähemärkides)",\r
+DlgTextMaxChars                : "Maksimaalselt tähemärke",\r
+DlgTextType                    : "Tüüp",\r
+DlgTextTypeText                : "Tekst",\r
+DlgTextTypePass                : "Parool",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nimi",\r
+DlgHiddenValue : "Väärtus",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Täpitud loetelu omadused",\r
+NumberedListProp       : "Nummerdatud loetelu omadused",\r
+DlgLstStart                    : "Alusta",\r
+DlgLstType                     : "Tüüp",\r
+DlgLstTypeCircle       : "Ring",\r
+DlgLstTypeDisc         : "Ketas",\r
+DlgLstTypeSquare       : "Ruut",\r
+DlgLstTypeNumbers      : "Numbrid (1, 2, 3)",\r
+DlgLstTypeLCase                : "Väiketähed (a, b, c)",\r
+DlgLstTypeUCase                : "Suurtähed (A, B, C)",\r
+DlgLstTypeSRoman       : "Väiksed Rooma numbrid (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Suured Rooma numbrid (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Üldine",\r
+DlgDocBackTab          : "Taust",\r
+DlgDocColorsTab                : "Värvid ja veerised",\r
+DlgDocMetaTab          : "Meta andmed",\r
+\r
+DlgDocPageTitle                : "Lehekülje tiitel",\r
+DlgDocLangDir          : "Kirja suund",\r
+DlgDocLangDirLTR       : "Vasakult paremale (LTR)",\r
+DlgDocLangDirRTL       : "Paremalt vasakule (RTL)",\r
+DlgDocLangCode         : "Keele kood",\r
+DlgDocCharSet          : "Märgistiku kodeering",\r
+DlgDocCharSetCE                : "Kesk-Euroopa",\r
+DlgDocCharSetCT                : "Hiina traditsiooniline (Big5)",\r
+DlgDocCharSetCR                : "Kirillisa",\r
+DlgDocCharSetGR                : "Kreeka",\r
+DlgDocCharSetJP                : "Jaapani",\r
+DlgDocCharSetKR                : "Korea",\r
+DlgDocCharSetTR                : "Türgi",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Lääne-Euroopa",\r
+DlgDocCharSetOther     : "Ülejäänud märgistike kodeeringud",\r
+\r
+DlgDocDocType          : "Dokumendi tüüppäis",\r
+DlgDocDocTypeOther     : "Teised dokumendi tüüppäised",\r
+DlgDocIncXHTML         : "Arva kaasa XHTML deklaratsioonid",\r
+DlgDocBgColor          : "Taustavärv",\r
+DlgDocBgImage          : "Taustapildi URL",\r
+DlgDocBgNoScroll       : "Mittekeritav tagataust",\r
+DlgDocCText                    : "Tekst",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Külastatud link",\r
+DlgDocCActive          : "Aktiivne link",\r
+DlgDocMargins          : "Lehekülje äärised",\r
+DlgDocMaTop                    : "Ülaserv",\r
+DlgDocMaLeft           : "Vasakserv",\r
+DlgDocMaRight          : "Paremserv",\r
+DlgDocMaBottom         : "Alaserv",\r
+DlgDocMeIndex          : "Dokumendi võtmesõnad (eraldatud komadega)",\r
+DlgDocMeDescr          : "Dokumendi kirjeldus",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Autoriõigus",\r
+DlgDocPreview          : "Eelvaade",\r
+\r
+// Templates Dialog\r
+Templates                      : "Šabloon",\r
+DlgTemplatesTitle      : "Sisu šabloonid",\r
+DlgTemplatesSelMsg     : "Palun vali šabloon, et avada see redaktoris<br />(praegune sisu läheb kaotsi):",\r
+DlgTemplatesLoading    : "Laen šabloonide nimekirja. Palun oota...",\r
+DlgTemplatesNoTpl      : "(Ühtegi šablooni ei ole defineeritud)",\r
+DlgTemplatesReplace    : "Asenda tegelik sisu",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Teave",\r
+DlgAboutBrowserInfoTab : "Veebisirvija info",\r
+DlgAboutLicenseTab     : "Litsents",\r
+DlgAboutVersion                : "versioon",\r
+DlgAboutInfo           : "Täpsema info saamiseks mine"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/eu.js b/httemplate/elements/fckeditor/editor/lang/eu.js
new file mode 100644 (file)
index 0000000..266d427
--- /dev/null
@@ -0,0 +1,505 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Basque language file.\r
+ * Euskara hizkuntza fitxategia.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Estutu Tresna Barra",\r
+ToolbarExpand          : "Hedatu Tresna Barra",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Gorde",\r
+NewPage                                : "Orrialde Berria",\r
+Preview                                : "Aurrebista",\r
+Cut                                    : "Ebaki",\r
+Copy                           : "Kopiatu",\r
+Paste                          : "Itsatsi",\r
+PasteText                      : "Itsatsi testu bezala",\r
+PasteWord                      : "Itsatsi Word-etik",\r
+Print                          : "Inprimatu",\r
+SelectAll                      : "Hautatu dena",\r
+RemoveFormat           : "Kendu Formatoa",\r
+InsertLinkLbl          : "Esteka",\r
+InsertLink                     : "Txertatu/Editatu Esteka",\r
+RemoveLink                     : "Kendu Esteka",\r
+Anchor                         : "Aingura",\r
+InsertImageLbl         : "Irudia",\r
+InsertImage                    : "Txertatu/Editatu Irudia",\r
+InsertFlashLbl         : "Flasha",\r
+InsertFlash                    : "Txertatu/Editatu Flasha",\r
+InsertTableLbl         : "Taula",\r
+InsertTable                    : "Txertatu/Editatu Taula",\r
+InsertLineLbl          : "Lerroa",\r
+InsertLine                     : "Txertatu Marra Horizontala",\r
+InsertSpecialCharLbl: "Karaktere Berezia",\r
+InsertSpecialChar      : "Txertatu Karaktere Berezia",\r
+InsertSmileyLbl                : "Aurpegierak",\r
+InsertSmiley           : "Txertatu Aurpegierak",\r
+About                          : "FCKeditor-ri buruz",\r
+Bold                           : "Lodia",\r
+Italic                         : "Etzana",\r
+Underline                      : "Azpimarratu",\r
+StrikeThrough          : "Marratua",\r
+Subscript                      : "Azpi-indize",\r
+Superscript                    : "Goi-indize",\r
+LeftJustify                    : "Lerrokatu Ezkerrean",\r
+CenterJustify          : "Lerrokatu Erdian",\r
+RightJustify           : "Lerrokatu Eskuman",\r
+BlockJustify           : "Justifikatu",\r
+DecreaseIndent         : "Txikitu Koska",\r
+IncreaseIndent         : "Handitu Koska",\r
+Undo                           : "Desegin",\r
+Redo                           : "Berregin",\r
+NumberedListLbl                : "Zenbakidun Zerrenda",\r
+NumberedList           : "Txertatu/Kendu Zenbakidun zerrenda",\r
+BulletedListLbl                : "Buletdun Zerrenda",\r
+BulletedList           : "Txertatu/Kendu Buletdun zerrenda",\r
+ShowTableBorders       : "Erakutsi Taularen Ertzak",\r
+ShowDetails                    : "Erakutsi Xehetasunak",\r
+Style                          : "Estiloa",\r
+FontFormat                     : "Formatoa",\r
+Font                           : "Letra-tipoa",\r
+FontSize                       : "Tamaina",\r
+TextColor                      : "Testu Kolorea",\r
+BGColor                                : "Atzeko kolorea",\r
+Source                         : "HTML Iturburua",\r
+Find                           : "Bilatu",\r
+Replace                                : "Ordezkatu",\r
+SpellCheck                     : "Ortografia",\r
+UniversalKeyboard      : "Teklatu Unibertsala",\r
+PageBreakLbl           : "Orrialde-jauzia",\r
+PageBreak                      : "Txertatu Orrialde-jauzia",\r
+\r
+Form                   : "Formularioa",\r
+Checkbox               : "Kontrol-laukia",\r
+RadioButton            : "Aukera-botoia",\r
+TextField              : "Testu Eremua",\r
+Textarea               : "Testu-area",\r
+HiddenField            : "Ezkutuko Eremua",\r
+Button                 : "Botoia",\r
+SelectionField : "Hautespen Eremua",\r
+ImageButton            : "Irudi Botoia",\r
+\r
+FitWindow              : "Maximizatu editorearen tamaina",\r
+\r
+// Context Menu\r
+EditLink                       : "Aldatu Esteka",\r
+CellCM                         : "Gelaxka",\r
+RowCM                          : "Errenkada",\r
+ColumnCM                       : "Zutabea",\r
+InsertRow                      : "Txertatu Errenkada",\r
+DeleteRows                     : "Ezabatu Errenkadak",\r
+InsertColumn           : "Txertatu Zutabea",\r
+DeleteColumns          : "Ezabatu Zutabeak",\r
+InsertCell                     : "Txertatu Gelaxka",\r
+DeleteCells                    : "Kendu Gelaxkak",\r
+MergeCells                     : "Batu Gelaxkak",\r
+SplitCell                      : "Zatitu Gelaxka",\r
+TableDelete                    : "Ezabatu Taula",\r
+CellProperties         : "Gelaxkaren Ezaugarriak",\r
+TableProperties                : "Taularen Ezaugarriak",\r
+ImageProperties                : "Irudiaren Ezaugarriak",\r
+FlashProperties                : "Flasharen Ezaugarriak",\r
+\r
+AnchorProp                     : "Ainguraren Ezaugarriak",\r
+ButtonProp                     : "Botoiaren Ezaugarriak",\r
+CheckboxProp           : "Kontrol-laukiko Ezaugarriak",\r
+HiddenFieldProp                : "Ezkutuko Eremuaren Ezaugarriak",\r
+RadioButtonProp                : "Aukera-botoiaren Ezaugarriak",\r
+ImageButtonProp                : "Irudi Botoiaren Ezaugarriak",\r
+TextFieldProp          : "Testu Eremuaren Ezaugarriak",\r
+SelectionFieldProp     : "Hautespen Eremuaren Ezaugarriak",\r
+TextareaProp           : "Testu-arearen Ezaugarriak",\r
+FormProp                       : "Formularioaren Ezaugarriak",\r
+\r
+FontFormats                    : "Arrunta;Formateatua;Helbidea;Izenburua 1;Izenburua 2;Izenburua 3;Izenburua 4;Izenburua 5;Izenburua 6;Paragrafoa (DIV)",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML Prozesatzen. Itxaron mesedez...",\r
+Done                           : "Eginda",\r
+PasteWordConfirm       : "Itsatsi nahi duzun textua Wordetik hartua dela dirudi. Itsatsi baino lehen garbitu nahi duzu?",\r
+NotCompatiblePaste     : "Komando hau Internet Explorer 5.5 bertsiorako edo ondorengoentzako erabilgarria dago. Garbitu gabe itsatsi nahi duzu?",\r
+UnknownToolbarItem     : "Ataza barrako elementu ezezaguna \"%1\"",\r
+UnknownCommand         : "Komando izen ezezaguna \"%1\"",\r
+NotImplemented         : "Komando ez inplementatua",\r
+UnknownToolbarSet      : "Ataza barra \"%1\" taldea ez da existitzen",\r
+NoActiveX                      : "Zure nabigatzailearen segustasun hobespenak editore honen zenbait ezaugarri mugatu ditzake. \"ActiveX kontrolak eta plug-inak\" aktibatu beharko zenituzke, bestela erroreak eta ezaugarrietan mugak egon daitezke.",\r
+BrowseServerBlocked : "Baliabideen arakatzailea ezin da ireki. Ziurtatu popup blokeatzaileak desgaituta dituzula.",\r
+DialogBlocked          : "Ezin da elkarrizketa-leihoa ireki. Ziurtatu popup blokeatzaileak desgaituta dituzula.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "Ados",\r
+DlgBtnCancel           : "Utzi",\r
+DlgBtnClose                    : "Itxi",\r
+DlgBtnBrowseServer     : "Zerbitzaria arakatu",\r
+DlgAdvancedTag         : "Aurreratua",\r
+DlgOpOther                     : "<Bestelakoak>",\r
+DlgInfoTab                     : "Informazioa",\r
+DlgAlertUrl                    : "Mesedez URLa idatzi ezazu",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<Ezarri gabe>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Hizkuntzaren Norabidea",\r
+DlgGenLangDirLtr       : "Ezkerretik Eskumara(LTR)",\r
+DlgGenLangDirRtl       : "Eskumatik Ezkerrera (RTL)",\r
+DlgGenLangCode         : "Hizkuntza Kodea",\r
+DlgGenAccessKey                : "Sarbide-gakoa",\r
+DlgGenName                     : "Izena",\r
+DlgGenTabIndex         : "Tabulazio Indizea",\r
+DlgGenLongDescr                : "URL Deskribapen Luzea",\r
+DlgGenClass                    : "Estilo-orriko Klaseak",\r
+DlgGenTitle                    : "Izenburua",\r
+DlgGenContType         : "Eduki Mota (Content Type)",\r
+DlgGenLinkCharset      : "Estekatutako Karaktere Multzoa",\r
+DlgGenStyle                    : "Estiloa",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Irudi Ezaugarriak",\r
+DlgImgInfoTab          : "Irudi informazioa",\r
+DlgImgBtnUpload                : "Zerbitzarira bidalia",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Gora Kargatu",\r
+DlgImgAlt                      : "Textu Alternatiboa",\r
+DlgImgWidth                    : "Zabalera",\r
+DlgImgHeight           : "Altuera",\r
+DlgImgLockRatio                : "Erlazioa Blokeatu",\r
+DlgBtnResetSize                : "Tamaina Berrezarri",\r
+DlgImgBorder           : "Ertza",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Lerrokatu",\r
+DlgImgAlignLeft                : "Ezkerrera",\r
+DlgImgAlignAbsBottom: "Abs Behean",\r
+DlgImgAlignAbsMiddle: "Abs Erdian",\r
+DlgImgAlignBaseline    : "Oinan",\r
+DlgImgAlignBottom      : "Behean",\r
+DlgImgAlignMiddle      : "Erdian",\r
+DlgImgAlignRight       : "Eskuman",\r
+DlgImgAlignTextTop     : "Testua Goian",\r
+DlgImgAlignTop         : "Goian",\r
+DlgImgPreview          : "Aurrebista",\r
+DlgImgAlertUrl         : "Mesedez Irudiaren URLa idatzi",\r
+DlgImgLinkTab          : "Esteka",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flasharen Ezaugarriak",\r
+DlgFlashChkPlay                : "Automatikoki Erreproduzitu",\r
+DlgFlashChkLoop                : "Begizta",\r
+DlgFlashChkMenu                : "Flasharen Menua Gaitu",\r
+DlgFlashScale          : "Eskalatu",\r
+DlgFlashScaleAll       : "Dena erakutsi",\r
+DlgFlashScaleNoBorder  : "Ertzarik gabe",\r
+DlgFlashScaleFit       : "Doitu",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Esteka",\r
+DlgLnkInfoTab          : "Estekaren Informazioa",\r
+DlgLnkTargetTab                : "Helburua",\r
+\r
+DlgLnkType                     : "Esteka Mota",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Aingura horrialde honentan",\r
+DlgLnkTypeEMail                : "ePosta",\r
+DlgLnkProto                    : "Protokoloa",\r
+DlgLnkProtoOther       : "<Beste batzuk>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Aingura bat hautatu",\r
+DlgLnkAnchorByName     : "Aingura izenagatik",\r
+DlgLnkAnchorById       : "Elementuaren ID-gatik",\r
+DlgLnkNoAnchors                : "<Ez daude aingurak eskuragarri dokumentuan>",                //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "ePosta Helbidea",\r
+DlgLnkEMailSubject     : "Mezuaren Gaia",\r
+DlgLnkEMailBody                : "Mezuaren Gorputza",\r
+DlgLnkUpload           : "Gora kargatu",\r
+DlgLnkBtnUpload                : "Zerbitzarira bidali",\r
+\r
+DlgLnkTarget           : "Target (Helburua)",\r
+DlgLnkTargetFrame      : "<marko>",\r
+DlgLnkTargetPopup      : "<popup lehioa>",\r
+DlgLnkTargetBlank      : "Lehio Berria (_blank)",\r
+DlgLnkTargetParent     : "Lehio Gurasoa (_parent)",\r
+DlgLnkTargetSelf       : "Lehio Berdina (_self)",\r
+DlgLnkTargetTop                : "Goiko Lehioa (_top)",\r
+DlgLnkTargetFrameName  : "Marko Helburuaren Izena",\r
+DlgLnkPopWinName       : "Popup Lehioaren Izena",\r
+DlgLnkPopWinFeat       : "Popup Lehioaren Ezaugarriak",\r
+DlgLnkPopResize                : "Tamaina Aldakorra",\r
+DlgLnkPopLocation      : "Kokaleku Barra",\r
+DlgLnkPopMenu          : "Menu Barra",\r
+DlgLnkPopScroll                : "Korritze Barrak",\r
+DlgLnkPopStatus                : "Egoera Barra",\r
+DlgLnkPopToolbar       : "Tresna Barra",\r
+DlgLnkPopFullScrn      : "Pantaila Osoa (IE)",\r
+DlgLnkPopDependent     : "Menpekoa (Netscape)",\r
+DlgLnkPopWidth         : "Zabalera",\r
+DlgLnkPopHeight                : "Altuera",\r
+DlgLnkPopLeft          : "Ezkerreko  Posizioa",\r
+DlgLnkPopTop           : "Goiko Posizioa",\r
+\r
+DlnLnkMsgNoUrl         : "Mesedez URL esteka idatzi",\r
+DlnLnkMsgNoEMail       : "Mesedez ePosta helbidea idatzi",\r
+DlnLnkMsgNoAnchor      : "Mesedez aingura bat aukeratu",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Kolore Aukeraketa",\r
+DlgColorBtnClear       : "Garbitu",\r
+DlgColorHighlight      : "Nabarmendu",\r
+DlgColorSelected       : "Aukeratuta",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Aurpegiera Sartu",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Karaktere Berezia Aukeratu",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Taularen Ezaugarriak",\r
+DlgTableRows           : "Lerroak",\r
+DlgTableColumns                : "Zutabeak",\r
+DlgTableBorder         : "Ertzaren Zabalera",\r
+DlgTableAlign          : "Lerrokatu",\r
+DlgTableAlignNotSet    : "<Ezarri gabe>",\r
+DlgTableAlignLeft      : "Ezkerrean",\r
+DlgTableAlignCenter    : "Erdian",\r
+DlgTableAlignRight     : "Eskuman",\r
+DlgTableWidth          : "Zabalera",\r
+DlgTableWidthPx                : "pixel",\r
+DlgTableWidthPc                : "ehuneko",\r
+DlgTableHeight         : "Altuera",\r
+DlgTableCellSpace      : "Gelaxka arteko tartea",\r
+DlgTableCellPad                : "Gelaxken betegarria",\r
+DlgTableCaption                : "Epigrafea",\r
+DlgTableSummary                : "Laburpena",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Gelaxken Ezaugarriak",\r
+DlgCellWidth           : "Zabalera",\r
+DlgCellWidthPx         : "pixel",\r
+DlgCellWidthPc         : "ehuneko",\r
+DlgCellHeight          : "Altuera",\r
+DlgCellWordWrap                : "Itzulbira",\r
+DlgCellWordWrapNotSet  : "<Ezarri gabe>",\r
+DlgCellWordWrapYes     : "Bai",\r
+DlgCellWordWrapNo      : "Ez",\r
+DlgCellHorAlign                : "Horizontal Alignment",\r
+DlgCellHorAlignNotSet  : "<Ezarri gabe>",\r
+DlgCellHorAlignLeft    : "Ezkerrean",\r
+DlgCellHorAlignCenter  : "Erdian",\r
+DlgCellHorAlignRight: "Eskuman",\r
+DlgCellVerAlign                : "Lerrokatu Bertikalki",\r
+DlgCellVerAlignNotSet  : "<Ezarri gabe>",\r
+DlgCellVerAlignTop     : "Goian",\r
+DlgCellVerAlignMiddle  : "Erdian",\r
+DlgCellVerAlignBottom  : "Behean",\r
+DlgCellVerAlignBaseline        : "Oinan",\r
+DlgCellRowSpan         : "Lerroak Hedatu",\r
+DlgCellCollSpan                : "Zutabeak Hedatu",\r
+DlgCellBackColor       : "Atzeko Kolorea",\r
+DlgCellBorderColor     : "Ertzako Kolorea",\r
+DlgCellBtnSelect       : "Aukertau...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Bilaketa",\r
+DlgFindFindBtn         : "Bilatu",\r
+DlgFindNotFoundMsg     : "Idatzitako testua ez da topatu.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Ordeztu",\r
+DlgReplaceFindLbl              : "Zer bilatu:",\r
+DlgReplaceReplaceLbl   : "Zerekin ordeztu:",\r
+DlgReplaceCaseChk              : "Maiuskula/minuskula",\r
+DlgReplaceReplaceBtn   : "Ordeztu",\r
+DlgReplaceReplAllBtn   : "Ordeztu Guztiak",\r
+DlgReplaceWordChk              : "Esaldi osoa bilatu",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Zure web nabigatzailearen segurtasun ezarpenak testuak automatikoki moztea ez dute baimentzen. Mesedez teklatua erabili ezazu (Ctrl+X).",\r
+PasteErrorCopy : "Zure web nabigatzailearen segurtasun ezarpenak testuak automatikoki kopiatzea ez dute baimentzen. Mesedez teklatua erabili ezazu (Ctrl+C).",\r
+\r
+PasteAsText            : "Testu Arrunta bezala Itsatsi",\r
+PasteFromWord  : "Word-etik itsatsi",\r
+\r
+DlgPasteMsg2   : "Mesedez teklatua erabilita (<STRONG>Ctrl+V</STRONG>) ondorego eremuan testua itsatsi eta <STRONG>OK</STRONG> sakatu.",\r
+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\r
+DlgPasteIgnoreFont             : "Letra Motaren definizioa ezikusi",\r
+DlgPasteRemoveStyles   : "Estilo definizioak kendu",\r
+DlgPasteCleanBox               : "Testu-eremua Garbitu",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatikoa",\r
+ColorMoreColors        : "Kolore gehiago...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumentuaren Ezarpenak",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ainguraren Ezaugarriak",\r
+DlgAnchorName          : "Ainguraren Izena",\r
+DlgAnchorErrorName     : "Idatzi ainguraren izena",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Ez dago hiztegian",\r
+DlgSpellChangeTo               : "Honekin ordezkatu",\r
+DlgSpellBtnIgnore              : "Ezikusi",\r
+DlgSpellBtnIgnoreAll   : "Denak Ezikusi",\r
+DlgSpellBtnReplace             : "Ordezkatu",\r
+DlgSpellBtnReplaceAll  : "Denak Ordezkatu",\r
+DlgSpellBtnUndo                        : "Desegin",\r
+DlgSpellNoSuggestions  : "- Iradokizunik ez -",\r
+DlgSpellProgress               : "Zuzenketa ortografikoa martxan...",\r
+DlgSpellNoMispell              : "Zuzenketa ortografikoa bukatuta: Akatsik ez",\r
+DlgSpellNoChanges              : "Zuzenketa ortografikoa bukatuta: Ez da ezer aldatu",\r
+DlgSpellOneChange              : "Zuzenketa ortografikoa bukatuta: Hitz bat aldatu da",\r
+DlgSpellManyChanges            : "Zuzenketa ortografikoa bukatuta: %1 hitz aldatu dira",\r
+\r
+IeSpellDownload                        : "Zuzentzaile ortografikoa ez dago instalatuta. Deskargatu nahi duzu?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Testua (Balorea)",\r
+DlgButtonType          : "Mota",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Izena",\r
+DlgCheckboxValue       : "Balorea",\r
+DlgCheckboxSelected    : "Hautatuta",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Izena",\r
+DlgFormAction  : "Ekintza",\r
+DlgFormMethod  : "Method",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Izena",\r
+DlgSelectValue         : "Balorea",\r
+DlgSelectSize          : "Tamaina",\r
+DlgSelectLines         : "lerro kopurura",\r
+DlgSelectChkMulti      : "Hautaketa anitzak baimendu",\r
+DlgSelectOpAvail       : "Aukera Eskuragarriak",\r
+DlgSelectOpText                : "Testua",\r
+DlgSelectOpValue       : "Balorea",\r
+DlgSelectBtnAdd                : "Gehitu",\r
+DlgSelectBtnModify     : "Aldatu",\r
+DlgSelectBtnUp         : "Gora",\r
+DlgSelectBtnDown       : "Behera",\r
+DlgSelectBtnSetValue : "Aukeratutako balorea ezarri",\r
+DlgSelectBtnDelete     : "Ezabatu",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Izena",\r
+DlgTextareaCols        : "Zutabeak",\r
+DlgTextareaRows        : "Lerroak",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Izena",\r
+DlgTextValue           : "Balorea",\r
+DlgTextCharWidth       : "Zabalera",\r
+DlgTextMaxChars                : "Zenbat karaktere gehienez",\r
+DlgTextType                    : "Mota",\r
+DlgTextTypeText                : "Testua",\r
+DlgTextTypePass                : "Pasahitza",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Izena",\r
+DlgHiddenValue : "Balorea",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Buletdun Zerrendaren Ezarpenak",\r
+NumberedListProp       : "Zenbakidun Zerrendaren Ezarpenak",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Mota",\r
+DlgLstTypeCircle       : "Zirkulua",\r
+DlgLstTypeDisc         : "Diskoa",\r
+DlgLstTypeSquare       : "Karratua",\r
+DlgLstTypeNumbers      : "Zenbakiak (1, 2, 3)",\r
+DlgLstTypeLCase                : "Letra xeheak (a, b, c)",\r
+DlgLstTypeUCase                : "Letra larriak (A, B, C)",\r
+DlgLstTypeSRoman       : "Erromatar zenbaki zeheak (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Erromatar zenbaki larriak (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Orokorra",\r
+DlgDocBackTab          : "Atzekaldea",\r
+DlgDocColorsTab                : "Koloreak eta Marjinak",\r
+DlgDocMetaTab          : "Meta Informazioa",\r
+\r
+DlgDocPageTitle                : "Orriaren Izenburua",\r
+DlgDocLangDir          : "Hizkuntzaren Norabidea",\r
+DlgDocLangDirLTR       : "Ezkerretik eskumara (LTR)",\r
+DlgDocLangDirRTL       : "Eskumatik ezkerrera (RTL)",\r
+DlgDocLangCode         : "Hizkuntzaren Kodea",\r
+DlgDocCharSet          : "Karaktere Multzoaren Kodeketa",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Beste Karaktere Multzoaren Kodeketa",\r
+\r
+DlgDocDocType          : "Document Type Goiburua",\r
+DlgDocDocTypeOther     : "Beste Document Type Goiburua",\r
+DlgDocIncXHTML         : "XHTML Ezarpenak",\r
+DlgDocBgColor          : "Atzeko Kolorea",\r
+DlgDocBgImage          : "Atzeko Irudiaren URL-a",\r
+DlgDocBgNoScroll       : "Korritze gabeko Atzekaldea",\r
+DlgDocCText                    : "Testua",\r
+DlgDocCLink                    : "Estekak",\r
+DlgDocCVisited         : "Bisitatutako Estekak",\r
+DlgDocCActive          : "Esteka Aktiboa",\r
+DlgDocMargins          : "Orrialdearen marjinak",\r
+DlgDocMaTop                    : "Goian",\r
+DlgDocMaLeft           : "Ezkerrean",\r
+DlgDocMaRight          : "Eskuman",\r
+DlgDocMaBottom         : "Behean",\r
+DlgDocMeIndex          : "Dokumentuaren Gako-hitzak (komarekin bananduta)",\r
+DlgDocMeDescr          : "Dokumentuaren Deskribapena",\r
+DlgDocMeAuthor         : "Egilea",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Aurrebista",\r
+\r
+// Templates Dialog\r
+Templates                      : "Txantiloiak",\r
+DlgTemplatesTitle      : "Eduki Txantiloiak",\r
+DlgTemplatesSelMsg     : "Mesedez txantiloia aukeratu editorean kargatzeko<br>(orain dauden edukiak galduko dira):",\r
+DlgTemplatesLoading    : "Txantiloiak kargatzen. Itxaron mesedez...",\r
+DlgTemplatesNoTpl      : "(Ez dago definitutako txantiloirik)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Honi buruz",\r
+DlgAboutBrowserInfoTab : "Nabigatzailearen Informazioa",\r
+DlgAboutLicenseTab     : "Lizentzia",\r
+DlgAboutVersion                : "bertsioa",\r
+DlgAboutInfo           : "Informazio gehiago eskuratzeko hona joan"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/fa.js b/httemplate/elements/fckeditor/editor/lang/fa.js
new file mode 100644 (file)
index 0000000..e1bc973
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Persian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "rtl",\r
+\r
+ToolbarCollapse                : "برچیدن نوارابزار",\r
+ToolbarExpand          : "گستردن نوارابزار",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "ذخیره",\r
+NewPage                                : "برگهٴ تازه",\r
+Preview                                : "پیش‌نمایش",\r
+Cut                                    : "برش",\r
+Copy                           : "کپی",\r
+Paste                          : "چسباندن",\r
+PasteText                      : "چسباندن به عنوان متن ِساده",\r
+PasteWord                      : "چسباندن از Word",\r
+Print                          : "چاپ",\r
+SelectAll                      : "گزینش همه",\r
+RemoveFormat           : "برداشتن فرمت",\r
+InsertLinkLbl          : "پیوند",\r
+InsertLink                     : "گنجاندن/ویرایش ِپیوند",\r
+RemoveLink                     : "برداشتن پیوند",\r
+Anchor                         : "گنجاندن/ویرایش ِلنگر",\r
+InsertImageLbl         : "تصویر",\r
+InsertImage                    : "گنجاندن/ویرایش ِتصویر",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "گنجاندن/ویرایش ِFlash",\r
+InsertTableLbl         : "جدول",\r
+InsertTable                    : "گنجاندن/ویرایش ِجدول",\r
+InsertLineLbl          : "خط",\r
+InsertLine                     : "گنجاندن خط ِافقی",\r
+InsertSpecialCharLbl: "نویسهٴ ویژه",\r
+InsertSpecialChar      : "گنجاندن نویسهٴ ویژه",\r
+InsertSmileyLbl                : "خندانک",\r
+InsertSmiley           : "گنجاندن خندانک",\r
+About                          : "دربارهٴ FCKeditor",\r
+Bold                           : "درشت",\r
+Italic                         : "خمیده",\r
+Underline                      : "خط‌زیردار",\r
+StrikeThrough          : "میان‌خط",\r
+Subscript                      : "زیرنویس",\r
+Superscript                    : "بالانویس",\r
+LeftJustify                    : "چپ‌چین",\r
+CenterJustify          : "میان‌چین",\r
+RightJustify           : "راست‌چین",\r
+BlockJustify           : "بلوک‌چین",\r
+DecreaseIndent         : "کاهش تورفتگی",\r
+IncreaseIndent         : "افزایش تورفتگی",\r
+Undo                           : "واچیدن",\r
+Redo                           : "بازچیدن",\r
+NumberedListLbl                : "فهرست شماره‌دار",\r
+NumberedList           : "گنجاندن/برداشتن فهرست شماره‌دار",\r
+BulletedListLbl                : "فهرست نقطه‌ای",\r
+BulletedList           : "گنجاندن/برداشتن فهرست نقطه‌ای",\r
+ShowTableBorders       : "نمایش لبهٴ جدول",\r
+ShowDetails                    : "نمایش جزئیات",\r
+Style                          : "سبک",\r
+FontFormat                     : "فرمت",\r
+Font                           : "قلم",\r
+FontSize                       : "اندازه",\r
+TextColor                      : "رنگ متن",\r
+BGColor                                : "رنگ پس‌زمینه",\r
+Source                         : "منبع",\r
+Find                           : "جستجو",\r
+Replace                                : "جایگزینی",\r
+SpellCheck                     : "بررسی املا",\r
+UniversalKeyboard      : "صفحه‌کلید جهانی",\r
+PageBreakLbl           : "شکستگی ِپایان ِبرگه",\r
+PageBreak                      : "گنجاندن شکستگی ِپایان ِبرگه",\r
+\r
+Form                   : "فرم",\r
+Checkbox               : "خانهٴ گزینه‌ای",\r
+RadioButton            : "دکمهٴ رادیویی",\r
+TextField              : "فیلد متنی",\r
+Textarea               : "ناحیهٴ متنی",\r
+HiddenField            : "فیلد پنهان",\r
+Button                 : "دکمه",\r
+SelectionField : "فیلد چندگزینه‌ای",\r
+ImageButton            : "دکمهٴ تصویری",\r
+\r
+FitWindow              : "بیشینه‌سازی ِاندازهٴ ویرایشگر",\r
+\r
+// Context Menu\r
+EditLink                       : "ویرایش پیوند",\r
+CellCM                         : "سلول",\r
+RowCM                          : "سطر",\r
+ColumnCM                       : "ستون",\r
+InsertRow                      : "گنجاندن سطر",\r
+DeleteRows                     : "حذف سطرها",\r
+InsertColumn           : "گنجاندن ستون",\r
+DeleteColumns          : "حذف ستونها",\r
+InsertCell                     : "گنجاندن سلول",\r
+DeleteCells                    : "حذف سلولها",\r
+MergeCells                     : "ادغام سلولها",\r
+SplitCell                      : "جداسازی سلول",\r
+TableDelete                    : "پاک‌کردن جدول",\r
+CellProperties         : "ویژگیهای سلول",\r
+TableProperties                : "ویژگیهای جدول",\r
+ImageProperties                : "ویژگیهای تصویر",\r
+FlashProperties                : "ویژگیهای Flash",\r
+\r
+AnchorProp                     : "ویژگیهای لنگر",\r
+ButtonProp                     : "ویژگیهای دکمه",\r
+CheckboxProp           : "ویژگیهای خانهٴ گزینه‌ای",\r
+HiddenFieldProp                : "ویژگیهای فیلد پنهان",\r
+RadioButtonProp                : "ویژگیهای دکمهٴ رادیویی",\r
+ImageButtonProp                : "ویژگیهای دکمهٴ تصویری",\r
+TextFieldProp          : "ویژگیهای فیلد متنی",\r
+SelectionFieldProp     : "ویژگیهای فیلد چندگزینه‌ای",\r
+TextareaProp           : "ویژگیهای ناحیهٴ متنی",\r
+FormProp                       : "ویژگیهای فرم",\r
+\r
+FontFormats                    : "نرمال;فرمت‌شده;آدرس;سرنویس 1;سرنویس 2;سرنویس 3;سرنویس 4;سرنویس 5;سرنویس 6;بند;(DIV)",               //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "پردازش XHTML. لطفا صبر کنید...",\r
+Done                           : "انجام شد",\r
+PasteWordConfirm       : "متنی که می‌خواهید بچسبانید به نظر می‌رسد از Word کپی شده است. آیا می‌خواهید قبل از چسباندن آن را پاک‌سازی کنید؟",\r
+NotCompatiblePaste     : "این فرمان برای مرورگر Internet Explorer از نگارش 5.5 یا بالاتر در دسترس است. آیا می‌خواهید بدون پاک‌سازی، متن را بچسبانید؟",\r
+UnknownToolbarItem     : "فقرهٴ نوارابزار ناشناخته \"%1\"",\r
+UnknownCommand         : "نام دستور ناشناخته \"%1\"",\r
+NotImplemented         : "دستور پیاده‌سازی‌نشده",\r
+UnknownToolbarSet      : "مجموعهٴ نوارابزار \"%1\" وجود ندارد",\r
+NoActiveX                      : "تنظیمات امنیتی مرورگر شما ممکن است در بعضی از ویژگیهای مرورگر محدودیت ایجاد کند. شما باید گزینهٴ \"Run ActiveX controls and plug-ins\" را فعال کنید. ممکن است شما با خطاهایی روبرو باشید و متوجه کمبود ویژگیهایی شوید.",\r
+BrowseServerBlocked : "توانایی بازگشایی مرورگر منابع فراهم نیست. اطمینان حاصل کنید که تمامی برنامه‌های پیشگیری از نمایش popup را از کار بازداشته‌اید.",\r
+DialogBlocked          : "توانایی بازگشایی پنجرهٴ کوچک ِگفتگو فراهم نیست. اطمینان حاصل کنید که تمامی برنامه‌های پیشگیری از نمایش popup را از کار بازداشته‌اید.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "پذیرش",\r
+DlgBtnCancel           : "انصراف",\r
+DlgBtnClose                    : "بستن",\r
+DlgBtnBrowseServer     : "فهرست‌نمایی سرور",\r
+DlgAdvancedTag         : "پیشرفته",\r
+DlgOpOther                     : "<غیره>",\r
+DlgInfoTab                     : "اطلاعات",\r
+DlgAlertUrl                    : "لطفاً URL را بنویسید",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<تعین‌نشده>",\r
+DlgGenId                       : "شناسه",\r
+DlgGenLangDir          : "جهت‌نمای زبان",\r
+DlgGenLangDirLtr       : "چپ به راست (LTR)",\r
+DlgGenLangDirRtl       : "راست به چپ (RTL)",\r
+DlgGenLangCode         : "کد زبان",\r
+DlgGenAccessKey                : "کلید دستیابی",\r
+DlgGenName                     : "نام",\r
+DlgGenTabIndex         : "نمایهٴ دسترسی با Tab",\r
+DlgGenLongDescr                : "URL توصیف طولانی",\r
+DlgGenClass                    : "کلاسهای شیوه‌نامه(Stylesheet)",\r
+DlgGenTitle                    : "عنوان کمکی",\r
+DlgGenContType         : "نوع محتوای کمکی",\r
+DlgGenLinkCharset      : "نویسه‌گان منبع ِپیوندشده",\r
+DlgGenStyle                    : "شیوه(style)",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "ویژگیهای تصویر",\r
+DlgImgInfoTab          : "اطلاعات تصویر",\r
+DlgImgBtnUpload                : "به سرور بفرست",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "انتقال به سرور",\r
+DlgImgAlt                      : "متن جایگزین",\r
+DlgImgWidth                    : "پهنا",\r
+DlgImgHeight           : "درازا",\r
+DlgImgLockRatio                : "قفل‌کردن ِنسبت",\r
+DlgBtnResetSize                : "بازنشانی اندازه",\r
+DlgImgBorder           : "لبه",\r
+DlgImgHSpace           : "فاصلهٴ افقی",\r
+DlgImgVSpace           : "فاصلهٴ عمودی",\r
+DlgImgAlign                    : "چینش",\r
+DlgImgAlignLeft                : "چپ",\r
+DlgImgAlignAbsBottom: "پائین مطلق",\r
+DlgImgAlignAbsMiddle: "وسط مطلق",\r
+DlgImgAlignBaseline    : "خط‌پایه",\r
+DlgImgAlignBottom      : "پائین",\r
+DlgImgAlignMiddle      : "وسط",\r
+DlgImgAlignRight       : "راست",\r
+DlgImgAlignTextTop     : "متن بالا",\r
+DlgImgAlignTop         : "بالا",\r
+DlgImgPreview          : "پیش‌نمایش",\r
+DlgImgAlertUrl         : "لطفا URL تصویر را بنویسید",\r
+DlgImgLinkTab          : "پیوند",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "ویژگیهای Flash",\r
+DlgFlashChkPlay                : "آغاز ِخودکار",\r
+DlgFlashChkLoop                : "اجرای پیاپی",\r
+DlgFlashChkMenu                : "دردسترس‌بودن منوی Flash",\r
+DlgFlashScale          : "مقیاس",\r
+DlgFlashScaleAll       : "نمایش همه",\r
+DlgFlashScaleNoBorder  : "بدون کران",\r
+DlgFlashScaleFit       : "جایگیری کامل",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "پیوند",\r
+DlgLnkInfoTab          : "اطلاعات پیوند",\r
+DlgLnkTargetTab                : "مقصد",\r
+\r
+DlgLnkType                     : "نوع پیوند",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "لنگر در همین صفحه",\r
+DlgLnkTypeEMail                : "پست الکترونیکی",\r
+DlgLnkProto                    : "پروتکل",\r
+DlgLnkProtoOther       : "<دیگر>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "یک لنگر برگزینید",\r
+DlgLnkAnchorByName     : "با نام لنگر",\r
+DlgLnkAnchorById       : "با شناسهٴ المان",\r
+DlgLnkNoAnchors                : "(در این سند لنگری دردسترس نیست)",            //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "نشانی پست الکترونیکی",\r
+DlgLnkEMailSubject     : "موضوع پیام",\r
+DlgLnkEMailBody                : "متن پیام",\r
+DlgLnkUpload           : "انتقال به سرور",\r
+DlgLnkBtnUpload                : "به سرور بفرست",\r
+\r
+DlgLnkTarget           : "مقصد",\r
+DlgLnkTargetFrame      : "<فریم>",\r
+DlgLnkTargetPopup      : "<پنجرهٴ پاپاپ>",\r
+DlgLnkTargetBlank      : "پنجرهٴ دیگر (_blank)",\r
+DlgLnkTargetParent     : "پنجرهٴ والد (_parent)",\r
+DlgLnkTargetSelf       : "همان پنجره (_self)",\r
+DlgLnkTargetTop                : "بالاترین پنجره (_top)",\r
+DlgLnkTargetFrameName  : "نام فریم مقصد",\r
+DlgLnkPopWinName       : "نام پنجرهٴ پاپاپ",\r
+DlgLnkPopWinFeat       : "ویژگیهای پنجرهٴ پاپاپ",\r
+DlgLnkPopResize                : "قابل تغییر اندازه",\r
+DlgLnkPopLocation      : "نوار موقعیت",\r
+DlgLnkPopMenu          : "نوار منو",\r
+DlgLnkPopScroll                : "میله‌های پیمایش",\r
+DlgLnkPopStatus                : "نوار وضعیت",\r
+DlgLnkPopToolbar       : "نوارابزار",\r
+DlgLnkPopFullScrn      : "تمام‌صفحه (IE)",\r
+DlgLnkPopDependent     : "وابسته (Netscape)",\r
+DlgLnkPopWidth         : "پهنا",\r
+DlgLnkPopHeight                : "درازا",\r
+DlgLnkPopLeft          : "موقعیت ِچپ",\r
+DlgLnkPopTop           : "موقعیت ِبالا",\r
+\r
+DlnLnkMsgNoUrl         : "لطفا URL پیوند را بنویسید",\r
+DlnLnkMsgNoEMail       : "لطفا نشانی پست الکترونیکی را بنویسید",\r
+DlnLnkMsgNoAnchor      : "لطفا لنگری را برگزینید",\r
+DlnLnkMsgInvPopName    : "نام پنجرهٴ پاپاپ باید با یک نویسهٴ الفبایی آغاز گردد و نباید فاصله‌های خالی در آن باشند",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "گزینش رنگ",\r
+DlgColorBtnClear       : "پاک‌کردن",\r
+DlgColorHighlight      : "نمونه",\r
+DlgColorSelected       : "برگزیده",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "گنجاندن خندانک",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "گزینش نویسهٴ‌ویژه",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "ویژگیهای جدول",\r
+DlgTableRows           : "سطرها",\r
+DlgTableColumns                : "ستونها",\r
+DlgTableBorder         : "اندازهٴ لبه",\r
+DlgTableAlign          : "چینش",\r
+DlgTableAlignNotSet    : "<تعین‌نشده>",\r
+DlgTableAlignLeft      : "چپ",\r
+DlgTableAlignCenter    : "وسط",\r
+DlgTableAlignRight     : "راست",\r
+DlgTableWidth          : "پهنا",\r
+DlgTableWidthPx                : "پیکسل",\r
+DlgTableWidthPc                : "درصد",\r
+DlgTableHeight         : "درازا",\r
+DlgTableCellSpace      : "فاصلهٴ میان سلولها",\r
+DlgTableCellPad                : "فاصلهٴ پرشده در سلول",\r
+DlgTableCaption                : "عنوان",\r
+DlgTableSummary                : "خلاصه",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "ویژگیهای سلول",\r
+DlgCellWidth           : "پهنا",\r
+DlgCellWidthPx         : "پیکسل",\r
+DlgCellWidthPc         : "درصد",\r
+DlgCellHeight          : "درازا",\r
+DlgCellWordWrap                : "شکستن واژه‌ها",\r
+DlgCellWordWrapNotSet  : "<تعین‌نشده>",\r
+DlgCellWordWrapYes     : "بله",\r
+DlgCellWordWrapNo      : "خیر",\r
+DlgCellHorAlign                : "چینش ِافقی",\r
+DlgCellHorAlignNotSet  : "<تعین‌نشده>",\r
+DlgCellHorAlignLeft    : "چپ",\r
+DlgCellHorAlignCenter  : "وسط",\r
+DlgCellHorAlignRight: "راست",\r
+DlgCellVerAlign                : "چینش ِعمودی",\r
+DlgCellVerAlignNotSet  : "<تعین‌نشده>",\r
+DlgCellVerAlignTop     : "بالا",\r
+DlgCellVerAlignMiddle  : "میان",\r
+DlgCellVerAlignBottom  : "پائین",\r
+DlgCellVerAlignBaseline        : "خط‌پایه",\r
+DlgCellRowSpan         : "گستردگی سطرها",\r
+DlgCellCollSpan                : "گستردگی ستونها",\r
+DlgCellBackColor       : "رنگ پس‌زمینه",\r
+DlgCellBorderColor     : "رنگ لبه",\r
+DlgCellBtnSelect       : "برگزینید...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "یافتن",\r
+DlgFindFindBtn         : "یافتن",\r
+DlgFindNotFoundMsg     : "متن موردنظر یافت نشد.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "جایگزینی",\r
+DlgReplaceFindLbl              : "چه‌چیز را می‌یابید:",\r
+DlgReplaceReplaceLbl   : "جایگزینی با:",\r
+DlgReplaceCaseChk              : "همسانی در بزرگی و کوچکی نویسه‌ها",\r
+DlgReplaceReplaceBtn   : "جایگزینی",\r
+DlgReplaceReplAllBtn   : "جایگزینی همهٴ یافته‌ها",\r
+DlgReplaceWordChk              : "همسانی با واژهٴ کامل",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "تنظیمات امنیتی مرورگر شما اجازه نمی‌دهد که ویرایشگر به طور خودکار عملکردهای برش را انجام دهد. لطفا با دکمه‌های صفحه‌کلید این کار را انجام دهید (Ctrl+X).",\r
+PasteErrorCopy : "تنظیمات امنیتی مرورگر شما اجازه نمی‌دهد که ویرایشگر به طور خودکار عملکردهای کپی‌کردن را انجام دهد. لطفا با دکمه‌های صفحه‌کلید این کار را انجام دهید (Ctrl+C).",\r
+\r
+PasteAsText            : "چسباندن به عنوان متن ِساده",\r
+PasteFromWord  : "چسباندن از Word",\r
+\r
+DlgPasteMsg2   : "لطفا متن را با کلیدهای (<STRONG>Ctrl+V</STRONG>) در این جعبهٴ متنی بچسبانید و <STRONG>پذیرش</STRONG> را بزنید.",\r
+DlgPasteSec            : "به خاطر تنظیمات امنیتی مرورگر شما، ویرایشگر نمی‌تواند دسترسی مستقیم به داده‌های clipboard داشته باشد. شما باید دوباره آنرا در این پنجره بچسبانید.",\r
+DlgPasteIgnoreFont             : "چشم‌پوشی از تعاریف نوع قلم",\r
+DlgPasteRemoveStyles   : "چشم‌پوشی از تعاریف سبک (style)",\r
+DlgPasteCleanBox               : "پاک‌کردن ناحیه",\r
+\r
+// Color Picker\r
+ColorAutomatic : "خودکار",\r
+ColorMoreColors        : "رنگهای بیشتر...",\r
+\r
+// Document Properties\r
+DocProps               : "ویژگیهای سند",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "ویژگیهای لنگر",\r
+DlgAnchorName          : "نام لنگر",\r
+DlgAnchorErrorName     : "لطفا نام لنگر را بنویسید",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "در واژه‌نامه یافت نشد",\r
+DlgSpellChangeTo               : "تغییر به",\r
+DlgSpellBtnIgnore              : "چشم‌پوشی",\r
+DlgSpellBtnIgnoreAll   : "چشم‌پوشی همه",\r
+DlgSpellBtnReplace             : "جایگزینی",\r
+DlgSpellBtnReplaceAll  : "جایگزینی همه",\r
+DlgSpellBtnUndo                        : "واچینش",\r
+DlgSpellNoSuggestions  : "- پیشنهادی نیست -",\r
+DlgSpellProgress               : "بررسی املا در حال انجام...",\r
+DlgSpellNoMispell              : "بررسی املا انجام شد. هیچ غلط‌املائی یافت نشد",\r
+DlgSpellNoChanges              : "بررسی املا انجام شد. هیچ واژه‌ای تغییر نیافت",\r
+DlgSpellOneChange              : "بررسی املا انجام شد. یک واژه تغییر یافت",\r
+DlgSpellManyChanges            : "بررسی املا انجام شد. %1 واژه تغییر یافت",\r
+\r
+IeSpellDownload                        : "بررسی‌کنندهٴ املا نصب نشده است. آیا می‌خواهید آن را هم‌اکنون دریافت کنید؟",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "متن (مقدار)",\r
+DlgButtonType          : "نوع",\r
+DlgButtonTypeBtn       : "دکمه",\r
+DlgButtonTypeSbm       : "Submit",\r
+DlgButtonTypeRst       : "بازنشانی (Reset)",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "نام",\r
+DlgCheckboxValue       : "مقدار",\r
+DlgCheckboxSelected    : "برگزیده",\r
+\r
+// Form Dialog\r
+DlgFormName            : "نام",\r
+DlgFormAction  : "رویداد",\r
+DlgFormMethod  : "متد",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "نام",\r
+DlgSelectValue         : "مقدار",\r
+DlgSelectSize          : "اندازه",\r
+DlgSelectLines         : "خطوط",\r
+DlgSelectChkMulti      : "گزینش چندگانه فراهم باشد",\r
+DlgSelectOpAvail       : "گزینه‌های دردسترس",\r
+DlgSelectOpText                : "متن",\r
+DlgSelectOpValue       : "مقدار",\r
+DlgSelectBtnAdd                : "افزودن",\r
+DlgSelectBtnModify     : "ویرایش",\r
+DlgSelectBtnUp         : "بالا",\r
+DlgSelectBtnDown       : "پائین",\r
+DlgSelectBtnSetValue : "تنظیم به عنوان مقدار ِبرگزیده",\r
+DlgSelectBtnDelete     : "پاک‌کردن",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "نام",\r
+DlgTextareaCols        : "ستونها",\r
+DlgTextareaRows        : "سطرها",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "نام",\r
+DlgTextValue           : "مقدار",\r
+DlgTextCharWidth       : "پهنای نویسه",\r
+DlgTextMaxChars                : "بیشینهٴ نویسه‌ها",\r
+DlgTextType                    : "نوع",\r
+DlgTextTypeText                : "متن",\r
+DlgTextTypePass                : "گذرواژه",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "نام",\r
+DlgHiddenValue : "مقدار",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "ویژگیهای فهرست نقطه‌ای",\r
+NumberedListProp       : "ویژگیهای فهرست شماره‌دار",\r
+DlgLstStart                    : "آغاز",\r
+DlgLstType                     : "نوع",\r
+DlgLstTypeCircle       : "دایره",\r
+DlgLstTypeDisc         : "قرص",\r
+DlgLstTypeSquare       : "چهارگوش",\r
+DlgLstTypeNumbers      : "شماره‌ها (1، 2، 3)",\r
+DlgLstTypeLCase                : "نویسه‌های کوچک (a، b، c)",\r
+DlgLstTypeUCase                : "نویسه‌های بزرگ (A، B، C)",\r
+DlgLstTypeSRoman       : "شمارگان رومی کوچک (i، ii، iii)",\r
+DlgLstTypeLRoman       : "شمارگان رومی بزرگ (I، II، III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "عمومی",\r
+DlgDocBackTab          : "پس‌زمینه",\r
+DlgDocColorsTab                : "رنگها و حاشیه‌ها",\r
+DlgDocMetaTab          : "فراداده",\r
+\r
+DlgDocPageTitle                : "عنوان صفحه",\r
+DlgDocLangDir          : "جهت زبان",\r
+DlgDocLangDirLTR       : "چپ به راست (LTR(",\r
+DlgDocLangDirRTL       : "راست به چپ (RTL(",\r
+DlgDocLangCode         : "کد زبان",\r
+DlgDocCharSet          : "رمزگذاری نویسه‌گان",\r
+DlgDocCharSetCE                : "اروپای مرکزی",\r
+DlgDocCharSetCT                : "چینی رسمی (Big5)",\r
+DlgDocCharSetCR                : "سیریلیک",\r
+DlgDocCharSetGR                : "یونانی",\r
+DlgDocCharSetJP                : "ژاپنی",\r
+DlgDocCharSetKR                : "کره‌ای",\r
+DlgDocCharSetTR                : "ترکی",\r
+DlgDocCharSetUN                : "یونیکُد (UTF-8)",\r
+DlgDocCharSetWE                : "اروپای غربی",\r
+DlgDocCharSetOther     : "رمزگذاری نویسه‌گان دیگر",\r
+\r
+DlgDocDocType          : "عنوان نوع سند",\r
+DlgDocDocTypeOther     : "عنوان نوع سند دیگر",\r
+DlgDocIncXHTML         : "شامل تعاریف XHTML",\r
+DlgDocBgColor          : "رنگ پس‌زمینه",\r
+DlgDocBgImage          : "URL تصویر پس‌زمینه",\r
+DlgDocBgNoScroll       : "پس‌زمینهٴ پیمایش‌ناپذیر",\r
+DlgDocCText                    : "متن",\r
+DlgDocCLink                    : "پیوند",\r
+DlgDocCVisited         : "پیوند مشاهده‌شده",\r
+DlgDocCActive          : "پیوند فعال",\r
+DlgDocMargins          : "حاشیه‌های صفحه",\r
+DlgDocMaTop                    : "بالا",\r
+DlgDocMaLeft           : "چپ",\r
+DlgDocMaRight          : "راست",\r
+DlgDocMaBottom         : "پایین",\r
+DlgDocMeIndex          : "کلیدواژگان نمایه‌گذاری سند (با کاما جدا شوند)",\r
+DlgDocMeDescr          : "توصیف سند",\r
+DlgDocMeAuthor         : "نویسنده",\r
+DlgDocMeCopy           : "کپی‌رایت",\r
+DlgDocPreview          : "پیش‌نمایش",\r
+\r
+// Templates Dialog\r
+Templates                      : "الگوها",\r
+DlgTemplatesTitle      : "الگوهای محتویات",\r
+DlgTemplatesSelMsg     : "لطفا الگوی موردنظر را برای بازکردن در ویرایشگر برگزینید<br>(محتویات کنونی از دست خواهند رفت):",\r
+DlgTemplatesLoading    : "بارگذاری فهرست الگوها. لطفا صبر کنید...",\r
+DlgTemplatesNoTpl      : "(الگوئی تعریف نشده است)",\r
+DlgTemplatesReplace    : "محتویات کنونی جایگزین شوند",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "درباره",\r
+DlgAboutBrowserInfoTab : "اطلاعات مرورگر",\r
+DlgAboutLicenseTab     : "گواهینامه",\r
+DlgAboutVersion                : "نگارش",\r
+DlgAboutInfo           : "برای آگاهی بیشتر به این نشانی بروید"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/fi.js b/httemplate/elements/fckeditor/editor/lang/fi.js
new file mode 100644 (file)
index 0000000..7e7986a
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Finnish language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Piilota työkalurivi",\r
+ToolbarExpand          : "Näytä työkalurivi",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Tallenna",\r
+NewPage                                : "Tyhjennä",\r
+Preview                                : "Esikatsele",\r
+Cut                                    : "Leikkaa",\r
+Copy                           : "Kopioi",\r
+Paste                          : "Liitä",\r
+PasteText                      : "Liitä tekstinä",\r
+PasteWord                      : "Liitä Wordista",\r
+Print                          : "Tulosta",\r
+SelectAll                      : "Valitse kaikki",\r
+RemoveFormat           : "Poista muotoilu",\r
+InsertLinkLbl          : "Linkki",\r
+InsertLink                     : "Lisää linkki/muokkaa linkkiä",\r
+RemoveLink                     : "Poista linkki",\r
+Anchor                         : "Lisää ankkuri/muokkaa ankkuria",\r
+InsertImageLbl         : "Kuva",\r
+InsertImage                    : "Lisää kuva/muokkaa kuvaa",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Lisää/muokkaa Flashia",\r
+InsertTableLbl         : "Taulu",\r
+InsertTable                    : "Lisää taulu/muokkaa taulua",\r
+InsertLineLbl          : "Murtoviiva",\r
+InsertLine                     : "Lisää murtoviiva",\r
+InsertSpecialCharLbl: "Erikoismerkki",\r
+InsertSpecialChar      : "Lisää erikoismerkki",\r
+InsertSmileyLbl                : "Hymiö",\r
+InsertSmiley           : "Lisää hymiö",\r
+About                          : "FCKeditorista",\r
+Bold                           : "Lihavoitu",\r
+Italic                         : "Kursivoitu",\r
+Underline                      : "Alleviivattu",\r
+StrikeThrough          : "Yliviivattu",\r
+Subscript                      : "Alaindeksi",\r
+Superscript                    : "Yläindeksi",\r
+LeftJustify                    : "Tasaa vasemmat reunat",\r
+CenterJustify          : "Keskitä",\r
+RightJustify           : "Tasaa oikeat reunat",\r
+BlockJustify           : "Tasaa molemmat reunat",\r
+DecreaseIndent         : "Pienennä sisennystä",\r
+IncreaseIndent         : "Suurenna sisennystä",\r
+Undo                           : "Kumoa",\r
+Redo                           : "Toista",\r
+NumberedListLbl                : "Numerointi",\r
+NumberedList           : "Lisää/poista numerointi",\r
+BulletedListLbl                : "Luottelomerkit",\r
+BulletedList           : "Lisää/poista luottelomerkit",\r
+ShowTableBorders       : "Näytä taulun rajat",\r
+ShowDetails                    : "Näytä muotoilu",\r
+Style                          : "Tyyli",\r
+FontFormat                     : "Muotoilu",\r
+Font                           : "Fontti",\r
+FontSize                       : "Koko",\r
+TextColor                      : "Tekstiväri",\r
+BGColor                                : "Taustaväri",\r
+Source                         : "Koodi",\r
+Find                           : "Etsi",\r
+Replace                                : "Korvaa",\r
+SpellCheck                     : "Tarkista oikeinkirjoitus",\r
+UniversalKeyboard      : "Universaali näppäimistö",\r
+PageBreakLbl           : "Sivun vaihto",\r
+PageBreak                      : "Lisää sivun vaihto",\r
+\r
+Form                   : "Lomake",\r
+Checkbox               : "Valintaruutu",\r
+RadioButton            : "Radiopainike",\r
+TextField              : "Tekstikenttä",\r
+Textarea               : "Tekstilaatikko",\r
+HiddenField            : "Piilokenttä",\r
+Button                 : "Painike",\r
+SelectionField : "Valintakenttä",\r
+ImageButton            : "Kuvapainike",\r
+\r
+FitWindow              : "Suurenna editori koko ikkunaan",\r
+\r
+// Context Menu\r
+EditLink                       : "Muokkaa linkkiä",\r
+CellCM                         : "Solu",\r
+RowCM                          : "Rivi",\r
+ColumnCM                       : "Sarake",\r
+InsertRow                      : "Lisää rivi",\r
+DeleteRows                     : "Poista rivit",\r
+InsertColumn           : "Lisää sarake",\r
+DeleteColumns          : "Poista sarakkeet",\r
+InsertCell                     : "Lisää solu",\r
+DeleteCells                    : "Poista solut",\r
+MergeCells                     : "Yhdistä solut",\r
+SplitCell                      : "Jaa solu",\r
+TableDelete                    : "Poista taulu",\r
+CellProperties         : "Solun ominaisuudet",\r
+TableProperties                : "Taulun ominaisuudet",\r
+ImageProperties                : "Kuvan ominaisuudet",\r
+FlashProperties                : "Flash ominaisuudet",\r
+\r
+AnchorProp                     : "Ankkurin ominaisuudet",\r
+ButtonProp                     : "Painikkeen ominaisuudet",\r
+CheckboxProp           : "Valintaruudun ominaisuudet",\r
+HiddenFieldProp                : "Piilokentän ominaisuudet",\r
+RadioButtonProp                : "Radiopainikkeen ominaisuudet",\r
+ImageButtonProp                : "Kuvapainikkeen ominaisuudet",\r
+TextFieldProp          : "Tekstikentän ominaisuudet",\r
+SelectionFieldProp     : "Valintakentän ominaisuudet",\r
+TextareaProp           : "Tekstilaatikon ominaisuudet",\r
+FormProp                       : "Lomakkeen ominaisuudet",\r
+\r
+FontFormats                    : "Normaali;Muotoiltu;Osoite;Otsikko 1;Otsikko 2;Otsikko 3;Otsikko 4;Otsikko 5;Otsikko 6",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Prosessoidaan XHTML:ää. Odota hetki...",\r
+Done                           : "Valmis",\r
+PasteWordConfirm       : "Teksti, jonka haluat liittää, näyttää olevan kopioitu Wordista. Haluatko puhdistaa sen ennen liittämistä?",\r
+NotCompatiblePaste     : "Tämä komento toimii vain Internet Explorer 5.5:ssa tai uudemmassa. Haluatko liittää ilman puhdistusta?",\r
+UnknownToolbarItem     : "Tuntemanton työkalu \"%1\"",\r
+UnknownCommand         : "Tuntematon komento \"%1\"",\r
+NotImplemented         : "Komentoa ei ole liitetty sovellukseen",\r
+UnknownToolbarSet      : "Työkalukokonaisuus \"%1\" ei ole olemassa",\r
+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.",\r
+BrowseServerBlocked : "Resurssiselainta ei voitu avata. Varmista, että ponnahdusikkunoiden estäjät eivät ole päällä.",\r
+DialogBlocked          : "Apuikkunaa ei voitu avaata. Varmista, että ponnahdusikkunoiden estäjät eivät ole päällä.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Peruuta",\r
+DlgBtnClose                    : "Sulje",\r
+DlgBtnBrowseServer     : "Selaa palvelinta",\r
+DlgAdvancedTag         : "Lisäominaisuudet",\r
+DlgOpOther                     : "Muut",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Lisää URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<ei asetettu>",\r
+DlgGenId                       : "Tunniste",\r
+DlgGenLangDir          : "Kielen suunta",\r
+DlgGenLangDirLtr       : "Vasemmalta oikealle (LTR)",\r
+DlgGenLangDirRtl       : "Oikealta vasemmalle (RTL)",\r
+DlgGenLangCode         : "Kielikoodi",\r
+DlgGenAccessKey                : "Pikanäppäin",\r
+DlgGenName                     : "Nimi",\r
+DlgGenTabIndex         : "Tabulaattori indeksi",\r
+DlgGenLongDescr                : "Pitkän kuvauksen URL",\r
+DlgGenClass                    : "Tyyliluokat",\r
+DlgGenTitle                    : "Avustava otsikko",\r
+DlgGenContType         : "Avustava sisällön tyyppi",\r
+DlgGenLinkCharset      : "Linkitetty kirjaimisto",\r
+DlgGenStyle                    : "Tyyli",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Kuvan ominaisuudet",\r
+DlgImgInfoTab          : "Kuvan tiedot",\r
+DlgImgBtnUpload                : "Lähetä palvelimelle",\r
+DlgImgURL                      : "Osoite",\r
+DlgImgUpload           : "Lisää kuva",\r
+DlgImgAlt                      : "Vaihtoehtoinen teksti",\r
+DlgImgWidth                    : "Leveys",\r
+DlgImgHeight           : "Korkeus",\r
+DlgImgLockRatio                : "Lukitse suhteet",\r
+DlgBtnResetSize                : "Alkuperäinen koko",\r
+DlgImgBorder           : "Raja",\r
+DlgImgHSpace           : "Vaakatila",\r
+DlgImgVSpace           : "Pystytila",\r
+DlgImgAlign                    : "Kohdistus",\r
+DlgImgAlignLeft                : "Vasemmalle",\r
+DlgImgAlignAbsBottom: "Aivan alas",\r
+DlgImgAlignAbsMiddle: "Aivan keskelle",\r
+DlgImgAlignBaseline    : "Alas (teksti)",\r
+DlgImgAlignBottom      : "Alas",\r
+DlgImgAlignMiddle      : "Keskelle",\r
+DlgImgAlignRight       : "Oikealle",\r
+DlgImgAlignTextTop     : "Ylös (teksti)",\r
+DlgImgAlignTop         : "Ylös",\r
+DlgImgPreview          : "Esikatselu",\r
+DlgImgAlertUrl         : "Kirjoita kuvan osoite (URL)",\r
+DlgImgLinkTab          : "Linkki",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash ominaisuudet",\r
+DlgFlashChkPlay                : "Automaattinen käynnistys",\r
+DlgFlashChkLoop                : "Toisto",\r
+DlgFlashChkMenu                : "Näytä Flash-valikko",\r
+DlgFlashScale          : "Levitä",\r
+DlgFlashScaleAll       : "Näytä kaikki",\r
+DlgFlashScaleNoBorder  : "Ei rajaa",\r
+DlgFlashScaleFit       : "Tarkka koko",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Linkki",\r
+DlgLnkInfoTab          : "Linkin tiedot",\r
+DlgLnkTargetTab                : "Kohde",\r
+\r
+DlgLnkType                     : "Linkkityyppi",\r
+DlgLnkTypeURL          : "Osoite",\r
+DlgLnkTypeAnchor       : "Ankkuri tässä sivussa",\r
+DlgLnkTypeEMail                : "Sähköposti",\r
+DlgLnkProto                    : "Protokolla",\r
+DlgLnkProtoOther       : "<muu>",\r
+DlgLnkURL                      : "Osoite",\r
+DlgLnkAnchorSel                : "Valitse ankkuri",\r
+DlgLnkAnchorByName     : "Ankkurin nimen mukaan",\r
+DlgLnkAnchorById       : "Ankkurin ID:n mukaan",\r
+DlgLnkNoAnchors                : "<Ei ankkureita tässä dokumentissa>",               //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Sähköpostiosoite",\r
+DlgLnkEMailSubject     : "Aihe",\r
+DlgLnkEMailBody                : "Viesti",\r
+DlgLnkUpload           : "Lisää tiedosto",\r
+DlgLnkBtnUpload                : "Lähetä palvelimelle",\r
+\r
+DlgLnkTarget           : "Kohde",\r
+DlgLnkTargetFrame      : "<kehys>",\r
+DlgLnkTargetPopup      : "<popup ikkuna>",\r
+DlgLnkTargetBlank      : "Uusi ikkuna (_blank)",\r
+DlgLnkTargetParent     : "Emoikkuna (_parent)",\r
+DlgLnkTargetSelf       : "Sama ikkuna (_self)",\r
+DlgLnkTargetTop                : "Päällimmäisin ikkuna (_top)",\r
+DlgLnkTargetFrameName  : "Kohdekehyksen nimi",\r
+DlgLnkPopWinName       : "Popup ikkunan nimi",\r
+DlgLnkPopWinFeat       : "Popup ikkunan ominaisuudet",\r
+DlgLnkPopResize                : "Venytettävä",\r
+DlgLnkPopLocation      : "Osoiterivi",\r
+DlgLnkPopMenu          : "Valikkorivi",\r
+DlgLnkPopScroll                : "Vierityspalkit",\r
+DlgLnkPopStatus                : "Tilarivi",\r
+DlgLnkPopToolbar       : "Vakiopainikkeet",\r
+DlgLnkPopFullScrn      : "Täysi ikkuna (IE)",\r
+DlgLnkPopDependent     : "Riippuva (Netscape)",\r
+DlgLnkPopWidth         : "Leveys",\r
+DlgLnkPopHeight                : "Korkeus",\r
+DlgLnkPopLeft          : "Vasemmalta (px)",\r
+DlgLnkPopTop           : "Ylhäältä (px)",\r
+\r
+DlnLnkMsgNoUrl         : "Linkille on kirjoitettava URL",\r
+DlnLnkMsgNoEMail       : "Kirjoita sähköpostiosoite",\r
+DlnLnkMsgNoAnchor      : "Valitse ankkuri",\r
+DlnLnkMsgInvPopName    : "Popup-ikkunan nimi pitää alkaa aakkosella ja ei saa sisältää välejä",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Valitse väri",\r
+DlgColorBtnClear       : "Tyhjennä",\r
+DlgColorHighlight      : "Kohdalla",\r
+DlgColorSelected       : "Valittu",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Lisää hymiö",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Valitse erikoismerkki",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Taulun ominaisuudet",\r
+DlgTableRows           : "Rivit",\r
+DlgTableColumns                : "Sarakkeet",\r
+DlgTableBorder         : "Rajan paksuus",\r
+DlgTableAlign          : "Kohdistus",\r
+DlgTableAlignNotSet    : "<ei asetettu>",\r
+DlgTableAlignLeft      : "Vasemmalle",\r
+DlgTableAlignCenter    : "Keskelle",\r
+DlgTableAlignRight     : "Oikealle",\r
+DlgTableWidth          : "Leveys",\r
+DlgTableWidthPx                : "pikseliä",\r
+DlgTableWidthPc                : "prosenttia",\r
+DlgTableHeight         : "Korkeus",\r
+DlgTableCellSpace      : "Solujen väli",\r
+DlgTableCellPad                : "Solujen sisennys",\r
+DlgTableCaption                : "Otsikko",\r
+DlgTableSummary                : "Yhteenveto",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Solun ominaisuudet",\r
+DlgCellWidth           : "Leveys",\r
+DlgCellWidthPx         : "pikseliä",\r
+DlgCellWidthPc         : "prosenttia",\r
+DlgCellHeight          : "Korkeus",\r
+DlgCellWordWrap                : "Tekstikierrätys",\r
+DlgCellWordWrapNotSet  : "<Ei asetettu>",\r
+DlgCellWordWrapYes     : "Kyllä",\r
+DlgCellWordWrapNo      : "Ei",\r
+DlgCellHorAlign                : "Vaakakohdistus",\r
+DlgCellHorAlignNotSet  : "<Ei asetettu>",\r
+DlgCellHorAlignLeft    : "Vasemmalle",\r
+DlgCellHorAlignCenter  : "Keskelle",\r
+DlgCellHorAlignRight: "Oikealle",\r
+DlgCellVerAlign                : "Pystykohdistus",\r
+DlgCellVerAlignNotSet  : "<Ei asetettu>",\r
+DlgCellVerAlignTop     : "Ylös",\r
+DlgCellVerAlignMiddle  : "Keskelle",\r
+DlgCellVerAlignBottom  : "Alas",\r
+DlgCellVerAlignBaseline        : "Tekstin alas",\r
+DlgCellRowSpan         : "Rivin jatkuvuus",\r
+DlgCellCollSpan                : "Sarakkeen jatkuvuus",\r
+DlgCellBackColor       : "Taustaväri",\r
+DlgCellBorderColor     : "Rajan väri",\r
+DlgCellBtnSelect       : "Valitse...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Etsi",\r
+DlgFindFindBtn         : "Etsi",\r
+DlgFindNotFoundMsg     : "Etsittyä tekstiä ei löytynyt.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Korvaa",\r
+DlgReplaceFindLbl              : "Etsi mitä:",\r
+DlgReplaceReplaceLbl   : "Korvaa tällä:",\r
+DlgReplaceCaseChk              : "Sama kirjainkoko",\r
+DlgReplaceReplaceBtn   : "Korvaa",\r
+DlgReplaceReplAllBtn   : "Korvaa kaikki",\r
+DlgReplaceWordChk              : "Koko sana",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Selaimesi turva-asetukset eivät salli editorin toteuttaa leikkaamista. Käytä näppäimistöä leikkaamiseen (Ctrl+X).",\r
+PasteErrorCopy : "Selaimesi turva-asetukset eivät salli editorin toteuttaa kopioimista. Käytä näppäimistöä kopioimiseen (Ctrl+C).",\r
+\r
+PasteAsText            : "Liitä tekstinä",\r
+PasteFromWord  : "Liitä Wordista",\r
+\r
+DlgPasteMsg2   : "Liitä painamalla (<STRONG>Ctrl+V</STRONG>) ja painamalla <STRONG>OK</STRONG>.",\r
+DlgPasteSec            : "Selaimesi turva-asetukset eivät salli editorin käyttää leikepöytää suoraan. Sinun pitää suorittaa liittäminen tässä ikkunassa.",\r
+DlgPasteIgnoreFont             : "Jätä huomioimatta fonttimääritykset",\r
+DlgPasteRemoveStyles   : "Poista tyylimääritykset",\r
+DlgPasteCleanBox               : "Tyhjennä",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automaattinen",\r
+ColorMoreColors        : "Lisää värejä...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumentin ominaisuudet",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ankkurin ominaisuudet",\r
+DlgAnchorName          : "Nimi",\r
+DlgAnchorErrorName     : "Ankkurille on kirjoitettava nimi",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Ei sanakirjassa",\r
+DlgSpellChangeTo               : "Vaihda",\r
+DlgSpellBtnIgnore              : "Jätä huomioimatta",\r
+DlgSpellBtnIgnoreAll   : "Jätä kaikki huomioimatta",\r
+DlgSpellBtnReplace             : "Korvaa",\r
+DlgSpellBtnReplaceAll  : "Korvaa kaikki",\r
+DlgSpellBtnUndo                        : "Kumoa",\r
+DlgSpellNoSuggestions  : "Ei ehdotuksia",\r
+DlgSpellProgress               : "Tarkistus käynnissä...",\r
+DlgSpellNoMispell              : "Tarkistus valmis: Ei virheitä",\r
+DlgSpellNoChanges              : "Tarkistus valmis: Yhtään sanaa ei muutettu",\r
+DlgSpellOneChange              : "Tarkistus valmis: Yksi sana muutettiin",\r
+DlgSpellManyChanges            : "Tarkistus valmis: %1 sanaa muutettiin",\r
+\r
+IeSpellDownload                        : "Oikeinkirjoituksen tarkistusta ei ole asennettu. Haluatko ladata sen nyt?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Teksti (arvo)",\r
+DlgButtonType          : "Tyyppi",\r
+DlgButtonTypeBtn       : "Painike",\r
+DlgButtonTypeSbm       : "Lähetä",\r
+DlgButtonTypeRst       : "Tyhjennä",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nimi",\r
+DlgCheckboxValue       : "Arvo",\r
+DlgCheckboxSelected    : "Valittu",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nimi",\r
+DlgFormAction  : "Toiminto",\r
+DlgFormMethod  : "Tapa",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nimi",\r
+DlgSelectValue         : "Arvo",\r
+DlgSelectSize          : "Koko",\r
+DlgSelectLines         : "Rivit",\r
+DlgSelectChkMulti      : "Salli usea valinta",\r
+DlgSelectOpAvail       : "Ominaisuudet",\r
+DlgSelectOpText                : "Teksti",\r
+DlgSelectOpValue       : "Arvo",\r
+DlgSelectBtnAdd                : "Lisää",\r
+DlgSelectBtnModify     : "Muuta",\r
+DlgSelectBtnUp         : "Ylös",\r
+DlgSelectBtnDown       : "Alas",\r
+DlgSelectBtnSetValue : "Aseta valituksi",\r
+DlgSelectBtnDelete     : "Poista",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nimi",\r
+DlgTextareaCols        : "Sarakkeita",\r
+DlgTextareaRows        : "Rivejä",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nimi",\r
+DlgTextValue           : "Arvo",\r
+DlgTextCharWidth       : "Leveys",\r
+DlgTextMaxChars                : "Maksimi merkkimäärä",\r
+DlgTextType                    : "Tyyppi",\r
+DlgTextTypeText                : "Teksti",\r
+DlgTextTypePass                : "Salasana",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nimi",\r
+DlgHiddenValue : "Arvo",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Luettelon ominaisuudet",\r
+NumberedListProp       : "Numeroinnin ominaisuudet",\r
+DlgLstStart                    : "Alku",\r
+DlgLstType                     : "Tyyppi",\r
+DlgLstTypeCircle       : "Kehä",\r
+DlgLstTypeDisc         : "Ympyrä",\r
+DlgLstTypeSquare       : "Neliö",\r
+DlgLstTypeNumbers      : "Numerot (1, 2, 3)",\r
+DlgLstTypeLCase                : "Pienet kirjaimet (a, b, c)",\r
+DlgLstTypeUCase                : "Isot kirjaimet (A, B, C)",\r
+DlgLstTypeSRoman       : "Pienet roomalaiset numerot (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Isot roomalaiset numerot (Ii, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Yleiset",\r
+DlgDocBackTab          : "Tausta",\r
+DlgDocColorsTab                : "Värit ja marginaalit",\r
+DlgDocMetaTab          : "Meta-tieto",\r
+\r
+DlgDocPageTitle                : "Sivun nimi",\r
+DlgDocLangDir          : "Kielen suunta",\r
+DlgDocLangDirLTR       : "Vasemmalta oikealle (LTR)",\r
+DlgDocLangDirRTL       : "Oikealta vasemmalle (RTL)",\r
+DlgDocLangCode         : "Kielikoodi",\r
+DlgDocCharSet          : "Merkistökoodaus",\r
+DlgDocCharSetCE                : "Keskieurooppalainen",\r
+DlgDocCharSetCT                : "Kiina, perinteinen (Big5)",\r
+DlgDocCharSetCR                : "Kyrillinen",\r
+DlgDocCharSetGR                : "Kreikka",\r
+DlgDocCharSetJP                : "Japani",\r
+DlgDocCharSetKR                : "Korealainen",\r
+DlgDocCharSetTR                : "Turkkilainen",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Länsieurooppalainen",\r
+DlgDocCharSetOther     : "Muu merkistökoodaus",\r
+\r
+DlgDocDocType          : "Dokumentin tyyppi",\r
+DlgDocDocTypeOther     : "Muu dokumentin tyyppi",\r
+DlgDocIncXHTML         : "Lisää XHTML julistukset",\r
+DlgDocBgColor          : "Taustaväri",\r
+DlgDocBgImage          : "Taustakuva",\r
+DlgDocBgNoScroll       : "Paikallaanpysyvä tausta",\r
+DlgDocCText                    : "Teksti",\r
+DlgDocCLink                    : "Linkki",\r
+DlgDocCVisited         : "Vierailtu linkki",\r
+DlgDocCActive          : "Aktiivinen linkki",\r
+DlgDocMargins          : "Sivun marginaalit",\r
+DlgDocMaTop                    : "Ylä",\r
+DlgDocMaLeft           : "Vasen",\r
+DlgDocMaRight          : "Oikea",\r
+DlgDocMaBottom         : "Ala",\r
+DlgDocMeIndex          : "Hakusanat (pilkulla erotettuna)",\r
+DlgDocMeDescr          : "Kuvaus",\r
+DlgDocMeAuthor         : "Tekijä",\r
+DlgDocMeCopy           : "Tekijänoikeudet",\r
+DlgDocPreview          : "Esikatselu",\r
+\r
+// Templates Dialog\r
+Templates                      : "Pohjat",\r
+DlgTemplatesTitle      : "Sisältöpohjat",\r
+DlgTemplatesSelMsg     : "Valitse pohja editoriin<br>(aiempi sisältö menetetään):",\r
+DlgTemplatesLoading    : "Ladataan listaa pohjista. Hetkinen...",\r
+DlgTemplatesNoTpl      : "(Ei määriteltyjä pohjia)",\r
+DlgTemplatesReplace    : "Korvaa editorin koko sisältö",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Editorista",\r
+DlgAboutBrowserInfoTab : "Selaimen tiedot",\r
+DlgAboutLicenseTab     : "Lisenssi",\r
+DlgAboutVersion                : "versio",\r
+DlgAboutInfo           : "Lisää tietoa osoitteesta"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/fo.js b/httemplate/elements/fckeditor/editor/lang/fo.js
new file mode 100644 (file)
index 0000000..830c43e
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Faroese language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Fjal amboðsbjálkan",\r
+ToolbarExpand          : "Vís amboðsbjálkan",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Goym",\r
+NewPage                                : "Nýggj síða",\r
+Preview                                : "Frumsýning",\r
+Cut                                    : "Kvett",\r
+Copy                           : "Avrita",\r
+Paste                          : "Innrita",\r
+PasteText                      : "Innrita reinan tekst",\r
+PasteWord                      : "Innrita frá Word",\r
+Print                          : "Prenta",\r
+SelectAll                      : "Markera alt",\r
+RemoveFormat           : "Strika sniðgeving",\r
+InsertLinkLbl          : "Tilknýti",\r
+InsertLink                     : "Ger/broyt tilknýti",\r
+RemoveLink                     : "Strika tilknýti",\r
+Anchor                         : "Ger/broyt marknastein",\r
+InsertImageLbl         : "Myndir",\r
+InsertImage                    : "Set inn/broyt mynd",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Set inn/broyt Flash",\r
+InsertTableLbl         : "Tabell",\r
+InsertTable                    : "Set inn/broyt tabell",\r
+InsertLineLbl          : "Linja",\r
+InsertLine                     : "Ger vatnrætta linju",\r
+InsertSpecialCharLbl: "Sertekn",\r
+InsertSpecialChar      : "Set inn sertekn",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Set inn Smiley",\r
+About                          : "Um FCKeditor",\r
+Bold                           : "Feit skrift",\r
+Italic                         : "Skráskrift",\r
+Underline                      : "Undirstrikað",\r
+StrikeThrough          : "Yvirstrikað",\r
+Subscript                      : "Lækkað skrift",\r
+Superscript                    : "Hækkað skrift",\r
+LeftJustify                    : "Vinstrasett",\r
+CenterJustify          : "Miðsett",\r
+RightJustify           : "Høgrasett",\r
+BlockJustify           : "Javnir tekstkantar",\r
+DecreaseIndent         : "Minka reglubrotarinntriv",\r
+IncreaseIndent         : "Økja reglubrotarinntriv",\r
+Undo                           : "Angra",\r
+Redo                           : "Vend aftur",\r
+NumberedListLbl                : "Talmerktur listi",\r
+NumberedList           : "Ger/strika talmerktan lista",\r
+BulletedListLbl                : "Punktmerktur listi",\r
+BulletedList           : "Ger/strika punktmerktan lista",\r
+ShowTableBorders       : "Vís tabellbordar",\r
+ShowDetails                    : "Vís í smálutum",\r
+Style                          : "Typografi",\r
+FontFormat                     : "Skriftsnið",\r
+Font                           : "Skrift",\r
+FontSize                       : "Skriftstødd",\r
+TextColor                      : "Tekstlitur",\r
+BGColor                                : "Bakgrundslitur",\r
+Source                         : "Kelda",\r
+Find                           : "Leita",\r
+Replace                                : "Yvirskriva",\r
+SpellCheck                     : "Kanna stavseting",\r
+UniversalKeyboard      : "Knappaborð",\r
+PageBreakLbl           : "Síðuskift",\r
+PageBreak                      : "Ger síðuskift",\r
+\r
+Form                   : "Formur",\r
+Checkbox               : "Flugubein",\r
+RadioButton            : "Radioknøttur",\r
+TextField              : "Tekstteigur",\r
+Textarea               : "Tekstumráði",\r
+HiddenField            : "Fjaldur teigur",\r
+Button                 : "Knøttur",\r
+SelectionField : "Valskrá",\r
+ImageButton            : "Myndaknøttur",\r
+\r
+FitWindow              : "Set tekstviðgera til fulla stødd",\r
+\r
+// Context Menu\r
+EditLink                       : "Broyt tilknýti",\r
+CellCM                         : "Meski",\r
+RowCM                          : "Rað",\r
+ColumnCM                       : "Kolonna",\r
+InsertRow                      : "Nýtt rað",\r
+DeleteRows                     : "Strika røðir",\r
+InsertColumn           : "Nýggj kolonna",\r
+DeleteColumns          : "Strika kolonnur",\r
+InsertCell                     : "Nýggjur meski",\r
+DeleteCells                    : "Strika meskar",\r
+MergeCells                     : "Flætta meskar",\r
+SplitCell                      : "Být sundur meskar",\r
+TableDelete                    : "Strika tabell",\r
+CellProperties         : "Meskueginleikar",\r
+TableProperties                : "Tabelleginleikar",\r
+ImageProperties                : "Myndaeginleikar",\r
+FlashProperties                : "Flash eginleikar",\r
+\r
+AnchorProp                     : "Eginleikar fyri marknastein",\r
+ButtonProp                     : "Eginleikar fyri knøtt",\r
+CheckboxProp           : "Eginleikar fyri flugubein",\r
+HiddenFieldProp                : "Eginleikar fyri fjaldan teig",\r
+RadioButtonProp                : "Eginleikar fyri radioknøtt",\r
+ImageButtonProp                : "Eginleikar fyri myndaknøtt",\r
+TextFieldProp          : "Eginleikar fyri tekstteig",\r
+SelectionFieldProp     : "Eginleikar fyri valskrá",\r
+TextareaProp           : "Eginleikar fyri tekstumráði",\r
+FormProp                       : "Eginleikar fyri Form",\r
+\r
+FontFormats                    : "Vanligt;Sniðgivið;Adressa;Yvirskrift 1;Yvirskrift 2;Yvirskrift 3;Yvirskrift 4;Yvirskrift 5;Yvirskrift 6",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML verður viðgjørt. Bíða við...",\r
+Done                           : "Liðugt",\r
+PasteWordConfirm       : "Teksturin, royndur verður at seta inn, tykist at stava frá Word. Vilt tú reinsa tekstin, áðrenn hann verður settur inn?",\r
+NotCompatiblePaste     : "Hetta er bert tøkt í Internet Explorer 5.5 og nýggjari. Vilt tú seta tekstin inn kortini - óreinsaðan?",\r
+UnknownToolbarItem     : "Ókendur lutur í amboðsbjálkanum \"%1\"",\r
+UnknownCommand         : "Ókend kommando \"%1\"",\r
+NotImplemented         : "Hetta er ikki tøkt í hesi útgávuni",\r
+UnknownToolbarSet      : "Amboðsbjálkin \"%1\" finst ikki",\r
+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.",\r
+BrowseServerBlocked : "Ambætarakagin kundi ikki opnast. Tryggja tær, at allar pop-up forðingar eru óvirknar.",\r
+DialogBlocked          : "Tað eyðnaðist ikki at opna samskiftisrútin. Tryggja tær, at allar pop-up forðingar eru óvirknar.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "Góðkent",\r
+DlgBtnCancel           : "Avlýst",\r
+DlgBtnClose                    : "Lat aftur",\r
+DlgBtnBrowseServer     : "Ambætarakagi",\r
+DlgAdvancedTag         : "Fjølbroytt",\r
+DlgOpOther                     : "<Annað>",\r
+DlgInfoTab                     : "Upplýsingar",\r
+DlgAlertUrl                    : "Vinarliga veit ein URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<ikki sett>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Tekstkós",\r
+DlgGenLangDirLtr       : "Frá vinstru til høgru (LTR)",\r
+DlgGenLangDirRtl       : "Frá høgru til vinstru (RTL)",\r
+DlgGenLangCode         : "Málkoda",\r
+DlgGenAccessKey                : "Snarvegisknappur",\r
+DlgGenName                     : "Navn",\r
+DlgGenTabIndex         : "Inntriv indeks",\r
+DlgGenLongDescr                : "Víðkað URL frágreiðing",\r
+DlgGenClass                    : "Typografi klassar",\r
+DlgGenTitle                    : "Vegleiðandi heiti",\r
+DlgGenContType         : "Vegleiðandi innihaldsslag",\r
+DlgGenLinkCharset      : "Atknýtt teknsett",\r
+DlgGenStyle                    : "Typografi",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Myndaeginleikar",\r
+DlgImgInfoTab          : "Myndaupplýsingar",\r
+DlgImgBtnUpload                : "Send til ambætaran",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Send",\r
+DlgImgAlt                      : "Alternativur tekstur",\r
+DlgImgWidth                    : "Breidd",\r
+DlgImgHeight           : "Hædd",\r
+DlgImgLockRatio                : "Læs lutfallið",\r
+DlgBtnResetSize                : "Upprunastødd",\r
+DlgImgBorder           : "Bordi",\r
+DlgImgHSpace           : "Høgri breddi",\r
+DlgImgVSpace           : "Vinstri breddi",\r
+DlgImgAlign                    : "Justering",\r
+DlgImgAlignLeft                : "Vinstra",\r
+DlgImgAlignAbsBottom: "Abs botnur",\r
+DlgImgAlignAbsMiddle: "Abs miðja",\r
+DlgImgAlignBaseline    : "Basislinja",\r
+DlgImgAlignBottom      : "Botnur",\r
+DlgImgAlignMiddle      : "Miðja",\r
+DlgImgAlignRight       : "Høgra",\r
+DlgImgAlignTextTop     : "Tekst toppur",\r
+DlgImgAlignTop         : "Ovast",\r
+DlgImgPreview          : "Frumsýning",\r
+DlgImgAlertUrl         : "Rita slóðina til myndina",\r
+DlgImgLinkTab          : "Tilknýti",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash eginleikar",\r
+DlgFlashChkPlay                : "Avspælingin byrjar sjálv",\r
+DlgFlashChkLoop                : "Endurspæl",\r
+DlgFlashChkMenu                : "Ger Flash skrá virkna",\r
+DlgFlashScale          : "Skalering",\r
+DlgFlashScaleAll       : "Vís alt",\r
+DlgFlashScaleNoBorder  : "Eingin bordi",\r
+DlgFlashScaleFit       : "Neyv skalering",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Tilknýti",\r
+DlgLnkInfoTab          : "Tilknýtis upplýsingar",\r
+DlgLnkTargetTab                : "Mál",\r
+\r
+DlgLnkType                     : "Tilknýtisslag",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Tilknýti til marknastein í tekstinum",\r
+DlgLnkTypeEMail                : "Teldupostur",\r
+DlgLnkProto                    : "Protokoll",\r
+DlgLnkProtoOther       : "<Annað>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Vel ein marknastein",\r
+DlgLnkAnchorByName     : "Eftir navni á marknasteini",\r
+DlgLnkAnchorById       : "Eftir element Id",\r
+DlgLnkNoAnchors                : "(Eingir marknasteinar eru í hesum dokumentið)",            //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Teldupost-adressa",\r
+DlgLnkEMailSubject     : "Evni",\r
+DlgLnkEMailBody                : "Breyðtekstur",\r
+DlgLnkUpload           : "Send til ambætaran",\r
+DlgLnkBtnUpload                : "Send til ambætaran",\r
+\r
+DlgLnkTarget           : "Mál",\r
+DlgLnkTargetFrame      : "<ramma>",\r
+DlgLnkTargetPopup      : "<popup vindeyga>",\r
+DlgLnkTargetBlank      : "Nýtt vindeyga (_blank)",\r
+DlgLnkTargetParent     : "Upphavliga vindeygað (_parent)",\r
+DlgLnkTargetSelf       : "Sama vindeygað (_self)",\r
+DlgLnkTargetTop                : "Alt vindeygað (_top)",\r
+DlgLnkTargetFrameName  : "Vís navn vindeygans",\r
+DlgLnkPopWinName       : "Popup vindeygans navn",\r
+DlgLnkPopWinFeat       : "Popup vindeygans víðkaðu eginleikar",\r
+DlgLnkPopResize                : "Kann broyta stødd",\r
+DlgLnkPopLocation      : "Adressulinja",\r
+DlgLnkPopMenu          : "Skrábjálki",\r
+DlgLnkPopScroll                : "Rullibjálki",\r
+DlgLnkPopStatus                : "Støðufrágreiðingarbjálki",\r
+DlgLnkPopToolbar       : "Amboðsbjálki",\r
+DlgLnkPopFullScrn      : "Fullur skermur (IE)",\r
+DlgLnkPopDependent     : "Bundið (Netscape)",\r
+DlgLnkPopWidth         : "Breidd",\r
+DlgLnkPopHeight                : "Hædd",\r
+DlgLnkPopLeft          : "Frástøða frá vinstru",\r
+DlgLnkPopTop           : "Frástøða frá íerva",\r
+\r
+DlnLnkMsgNoUrl         : "Vinarliga skriva tilknýti (URL)",\r
+DlnLnkMsgNoEMail       : "Vinarliga skriva teldupost-adressu",\r
+DlnLnkMsgNoAnchor      : "Vinarliga vel marknastein",\r
+DlnLnkMsgInvPopName    : "Popup navnið má byrja við bókstavi og má ikki hava millumrúm",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Vel lit",\r
+DlgColorBtnClear       : "Strika alt",\r
+DlgColorHighlight      : "Framhevja",\r
+DlgColorSelected       : "Valt",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Vel Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Vel sertekn",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Eginleikar fyri tabell",\r
+DlgTableRows           : "Røðir",\r
+DlgTableColumns                : "Kolonnur",\r
+DlgTableBorder         : "Bordabreidd",\r
+DlgTableAlign          : "Justering",\r
+DlgTableAlignNotSet    : "<Einki valt>",\r
+DlgTableAlignLeft      : "Vinstrasett",\r
+DlgTableAlignCenter    : "Miðsett",\r
+DlgTableAlignRight     : "Høgrasett",\r
+DlgTableWidth          : "Breidd",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "prosent",\r
+DlgTableHeight         : "Hædd",\r
+DlgTableCellSpace      : "Fjarstøða millum meskar",\r
+DlgTableCellPad                : "Meskubreddi",\r
+DlgTableCaption                : "Tabellfrágreiðing",\r
+DlgTableSummary                : "Samandráttur",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Mesku eginleikar",\r
+DlgCellWidth           : "Breidd",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "prosent",\r
+DlgCellHeight          : "Hædd",\r
+DlgCellWordWrap                : "Orðkloyving",\r
+DlgCellWordWrapNotSet  : "<Einki valt>",\r
+DlgCellWordWrapYes     : "Ja",\r
+DlgCellWordWrapNo      : "Nei",\r
+DlgCellHorAlign                : "Vatnrøtt justering",\r
+DlgCellHorAlignNotSet  : "<Einki valt>",\r
+DlgCellHorAlignLeft    : "Vinstrasett",\r
+DlgCellHorAlignCenter  : "Miðsett",\r
+DlgCellHorAlignRight: "Høgrasett",\r
+DlgCellVerAlign                : "Lodrøtt justering",\r
+DlgCellVerAlignNotSet  : "<Ikki sett>",\r
+DlgCellVerAlignTop     : "Ovast",\r
+DlgCellVerAlignMiddle  : "Miðjan",\r
+DlgCellVerAlignBottom  : "Niðast",\r
+DlgCellVerAlignBaseline        : "Basislinja",\r
+DlgCellRowSpan         : "Røðir, meskin fevnir um",\r
+DlgCellCollSpan                : "Kolonnur, meskin fevnir um",\r
+DlgCellBackColor       : "Bakgrundslitur",\r
+DlgCellBorderColor     : "Litur á borda",\r
+DlgCellBtnSelect       : "Vel...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Finn",\r
+DlgFindFindBtn         : "Finn",\r
+DlgFindNotFoundMsg     : "Leititeksturin varð ikki funnin",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Yvirskriva",\r
+DlgReplaceFindLbl              : "Finn:",\r
+DlgReplaceReplaceLbl   : "Yvirskriva við:",\r
+DlgReplaceCaseChk              : "Munur á stórum og smáðum bókstavum",\r
+DlgReplaceReplaceBtn   : "Yvirskriva",\r
+DlgReplaceReplAllBtn   : "Yvirskriva alt",\r
+DlgReplaceWordChk              : "Bert heil orð",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Trygdaruppseting alnótskagans forðar tekstviðgeranum í at kvetta tekstin. vinarliga nýt knappaborðið til at kvetta tekstin (CTRL+X).",\r
+PasteErrorCopy : "Trygdaruppseting alnótskagans forðar tekstviðgeranum í at avrita tekstin. Vinarliga nýt knappaborðið til at avrita tekstin (CTRL+C).",\r
+\r
+PasteAsText            : "Innrita som reinan tekst",\r
+PasteFromWord  : "Innrita fra Word",\r
+\r
+DlgPasteMsg2   : "Vinarliga koyr tekstin í hendan rútin við knappaborðinum (<strong>CTRL+V</strong>) og klikk á <strong>Góðtak</strong>.",\r
+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\r
+DlgPasteIgnoreFont             : "Forfjóna Font definitiónirnar",\r
+DlgPasteRemoveStyles   : "Strika Styles definitiónir",\r
+DlgPasteCleanBox               : "Reinskanarkassi",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Av sær sjálvum",\r
+ColorMoreColors        : "Fleiri litir...",\r
+\r
+// Document Properties\r
+DocProps               : "Eginleikar fyri dokument",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Eginleikar fyri marknastein",\r
+DlgAnchorName          : "Heiti marknasteinsins",\r
+DlgAnchorErrorName     : "Vinarliga rita marknasteinsins heiti",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Finst ikki í orðabókini",\r
+DlgSpellChangeTo               : "Broyt til",\r
+DlgSpellBtnIgnore              : "Forfjóna",\r
+DlgSpellBtnIgnoreAll   : "Forfjóna alt",\r
+DlgSpellBtnReplace             : "Yvirskriva",\r
+DlgSpellBtnReplaceAll  : "Yvirskriva alt",\r
+DlgSpellBtnUndo                        : "Angra",\r
+DlgSpellNoSuggestions  : "- Einki uppskot -",\r
+DlgSpellProgress               : "Rættstavarin arbeiðir...",\r
+DlgSpellNoMispell              : "Rættstavarain liðugur: Eingin feilur funnin",\r
+DlgSpellNoChanges              : "Rættstavarain liðugur: Einki orð varð broytt",\r
+DlgSpellOneChange              : "Rættstavarain liðugur: Eitt orð er broytt",\r
+DlgSpellManyChanges            : "Rættstavarain liðugur: %1 orð broytt",\r
+\r
+IeSpellDownload                        : "Rættstavarin er ikki tøkur í tekstviðgeranum. Vilt tú heinta hann nú?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekstur",\r
+DlgButtonType          : "Slag",\r
+DlgButtonTypeBtn       : "Knøttur",\r
+DlgButtonTypeSbm       : "Send",\r
+DlgButtonTypeRst       : "Nullstilla",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Navn",\r
+DlgCheckboxValue       : "Virði",\r
+DlgCheckboxSelected    : "Valt",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Navn",\r
+DlgFormAction  : "Hending",\r
+DlgFormMethod  : "Háttur",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Navn",\r
+DlgSelectValue         : "Virði",\r
+DlgSelectSize          : "Stødd",\r
+DlgSelectLines         : "Linjur",\r
+DlgSelectChkMulti      : "Loyv fleiri valmøguleikum samstundis",\r
+DlgSelectOpAvail       : "Tøkir møguleikar",\r
+DlgSelectOpText                : "Tekstur",\r
+DlgSelectOpValue       : "Virði",\r
+DlgSelectBtnAdd                : "Legg afturat",\r
+DlgSelectBtnModify     : "Broyt",\r
+DlgSelectBtnUp         : "Upp",\r
+DlgSelectBtnDown       : "Niður",\r
+DlgSelectBtnSetValue : "Set sum valt virði",\r
+DlgSelectBtnDelete     : "Strika",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Navn",\r
+DlgTextareaCols        : "kolonnur",\r
+DlgTextareaRows        : "røðir",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Navn",\r
+DlgTextValue           : "Virði",\r
+DlgTextCharWidth       : "Breidd (sjónlig tekn)",\r
+DlgTextMaxChars                : "Mest loyvdu tekn",\r
+DlgTextType                    : "Slag",\r
+DlgTextTypeText                : "Tekstur",\r
+DlgTextTypePass                : "Loyniorð",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Navn",\r
+DlgHiddenValue : "Virði",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Eginleikar fyri punktmerktan lista",\r
+NumberedListProp       : "Eginleikar fyri talmerktan lista",\r
+DlgLstStart                    : "Byrjan",\r
+DlgLstType                     : "Slag",\r
+DlgLstTypeCircle       : "Sirkul",\r
+DlgLstTypeDisc         : "Fyltur sirkul",\r
+DlgLstTypeSquare       : "Fjórhyrningur",\r
+DlgLstTypeNumbers      : "Talmerkt (1, 2, 3)",\r
+DlgLstTypeLCase                : "Smáir bókstavir (a, b, c)",\r
+DlgLstTypeUCase                : "Stórir bókstavir (A, B, C)",\r
+DlgLstTypeSRoman       : "Smá rómaratøl (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Stór rómaratøl (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Generelt",\r
+DlgDocBackTab          : "Bakgrund",\r
+DlgDocColorsTab                : "Litir og breddar",\r
+DlgDocMetaTab          : "META-upplýsingar",\r
+\r
+DlgDocPageTitle                : "Síðuheiti",\r
+DlgDocLangDir          : "Tekstkós",\r
+DlgDocLangDirLTR       : "Frá vinstru móti høgru (LTR)",\r
+DlgDocLangDirRTL       : "Frá høgru móti vinstru (RTL)",\r
+DlgDocLangCode         : "Málkoda",\r
+DlgDocCharSet          : "Teknsett koda",\r
+DlgDocCharSetCE                : "Miðeuropa",\r
+DlgDocCharSetCT                : "Kinesiskt traditionelt (Big5)",\r
+DlgDocCharSetCR                : "Cyrilliskt",\r
+DlgDocCharSetGR                : "Grikst",\r
+DlgDocCharSetJP                : "Japanskt",\r
+DlgDocCharSetKR                : "Koreanskt",\r
+DlgDocCharSetTR                : "Turkiskt",\r
+DlgDocCharSetUN                : "UNICODE (UTF-8)",\r
+DlgDocCharSetWE                : "Vestureuropa",\r
+DlgDocCharSetOther     : "Onnur teknsett koda",\r
+\r
+DlgDocDocType          : "Dokumentslag yvirskrift",\r
+DlgDocDocTypeOther     : "Annað dokumentslag yvirskrift",\r
+DlgDocIncXHTML         : "Viðfest XHTML deklaratiónir",\r
+DlgDocBgColor          : "Bakgrundslitur",\r
+DlgDocBgImage          : "Leið til bakgrundsmynd (URL)",\r
+DlgDocBgNoScroll       : "Læst bakgrund (rullar ikki)",\r
+DlgDocCText                    : "Tekstur",\r
+DlgDocCLink                    : "Tilknýti",\r
+DlgDocCVisited         : "Vitjaði tilknýti",\r
+DlgDocCActive          : "Virkin tilknýti",\r
+DlgDocMargins          : "Síðubreddar",\r
+DlgDocMaTop                    : "Ovast",\r
+DlgDocMaLeft           : "Vinstra",\r
+DlgDocMaRight          : "Høgra",\r
+DlgDocMaBottom         : "Niðast",\r
+DlgDocMeIndex          : "Dokument index lyklaorð (sundurbýtt við komma)",\r
+DlgDocMeDescr          : "Dokumentlýsing",\r
+DlgDocMeAuthor         : "Høvundur",\r
+DlgDocMeCopy           : "Upphavsrættindi",\r
+DlgDocPreview          : "Frumsýning",\r
+\r
+// Templates Dialog\r
+Templates                      : "Skabelónir",\r
+DlgTemplatesTitle      : "Innihaldsskabelónir",\r
+DlgTemplatesSelMsg     : "Vinarliga vel ta skabelón, ið skal opnast í tekstviðgeranum<br>(Hetta yvirskrivar núverandi innihald):",\r
+DlgTemplatesLoading    : "Heinti yvirlit yvir skabelónir. Vinarliga bíða við...",\r
+DlgTemplatesNoTpl      : "(Ongar skabelónir tøkar)",\r
+DlgTemplatesReplace    : "Yvirskriva núverandi innihald",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Um",\r
+DlgAboutBrowserInfoTab : "Upplýsingar um alnótskagan",\r
+DlgAboutLicenseTab     : "License",\r
+DlgAboutVersion                : "version",\r
+DlgAboutInfo           : "Fyri fleiri upplýsingar, far til"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/fr.js b/httemplate/elements/fckeditor/editor/lang/fr.js
new file mode 100644 (file)
index 0000000..6d46fa0
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * French language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Masquer Outils",\r
+ToolbarExpand          : "Afficher Outils",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Enregistrer",\r
+NewPage                                : "Nouvelle page",\r
+Preview                                : "Prévisualisation",\r
+Cut                                    : "Couper",\r
+Copy                           : "Copier",\r
+Paste                          : "Coller",\r
+PasteText                      : "Coller comme texte",\r
+PasteWord                      : "Coller de Word",\r
+Print                          : "Imprimer",\r
+SelectAll                      : "Tout sélectionner",\r
+RemoveFormat           : "Supprimer le format",\r
+InsertLinkLbl          : "Lien",\r
+InsertLink                     : "Insérer/modifier le lien",\r
+RemoveLink                     : "Supprimer le lien",\r
+Anchor                         : "Insérer/modifier l'ancre",\r
+InsertImageLbl         : "Image",\r
+InsertImage                    : "Insérer/modifier l'image",\r
+InsertFlashLbl         : "Animation Flash",\r
+InsertFlash                    : "Insérer/modifier l'animation Flash",\r
+InsertTableLbl         : "Tableau",\r
+InsertTable                    : "Insérer/modifier le tableau",\r
+InsertLineLbl          : "Séparateur",\r
+InsertLine                     : "Insérer un séparateur",\r
+InsertSpecialCharLbl: "Caractères spéciaux",\r
+InsertSpecialChar      : "Insérer un caractère spécial",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Insérer un Smiley",\r
+About                          : "A propos de FCKeditor",\r
+Bold                           : "Gras",\r
+Italic                         : "Italique",\r
+Underline                      : "Souligné",\r
+StrikeThrough          : "Barré",\r
+Subscript                      : "Indice",\r
+Superscript                    : "Exposant",\r
+LeftJustify                    : "Aligné à gauche",\r
+CenterJustify          : "Centré",\r
+RightJustify           : "Aligné à Droite",\r
+BlockJustify           : "Texte justifié",\r
+DecreaseIndent         : "Diminuer le retrait",\r
+IncreaseIndent         : "Augmenter le retrait",\r
+Undo                           : "Annuler",\r
+Redo                           : "Refaire",\r
+NumberedListLbl                : "Liste numérotée",\r
+NumberedList           : "Insérer/supprimer la liste numérotée",\r
+BulletedListLbl                : "Liste à puces",\r
+BulletedList           : "Insérer/supprimer la liste à puces",\r
+ShowTableBorders       : "Afficher les bordures du tableau",\r
+ShowDetails                    : "Afficher les caractères invisibles",\r
+Style                          : "Style",\r
+FontFormat                     : "Format",\r
+Font                           : "Police",\r
+FontSize                       : "Taille",\r
+TextColor                      : "Couleur de caractère",\r
+BGColor                                : "Couleur de fond",\r
+Source                         : "Source",\r
+Find                           : "Chercher",\r
+Replace                                : "Remplacer",\r
+SpellCheck                     : "Orthographe",\r
+UniversalKeyboard      : "Clavier universel",\r
+PageBreakLbl           : "Saut de page",\r
+PageBreak                      : "Insérer un saut de page",\r
+\r
+Form                   : "Formulaire",\r
+Checkbox               : "Case à cocher",\r
+RadioButton            : "Bouton radio",\r
+TextField              : "Champ texte",\r
+Textarea               : "Zone de texte",\r
+HiddenField            : "Champ caché",\r
+Button                 : "Bouton",\r
+SelectionField : "Liste/menu",\r
+ImageButton            : "Bouton image",\r
+\r
+FitWindow              : "Edition pleine page",\r
+\r
+// Context Menu\r
+EditLink                       : "Modifier le lien",\r
+CellCM                         : "Cellule",\r
+RowCM                          : "Ligne",\r
+ColumnCM                       : "Colonne",\r
+InsertRow                      : "Insérer une ligne",\r
+DeleteRows                     : "Supprimer des lignes",\r
+InsertColumn           : "Insérer une colonne",\r
+DeleteColumns          : "Supprimer des colonnes",\r
+InsertCell                     : "Insérer une cellule",\r
+DeleteCells                    : "Supprimer des cellules",\r
+MergeCells                     : "Fusionner les cellules",\r
+SplitCell                      : "Scinder les cellules",\r
+TableDelete                    : "Supprimer le tableau",\r
+CellProperties         : "Propriétés de cellule",\r
+TableProperties                : "Propriétés du tableau",\r
+ImageProperties                : "Propriétés de l'image",\r
+FlashProperties                : "Propriétés de l'animation Flash",\r
+\r
+AnchorProp                     : "Propriétés de l'ancre",\r
+ButtonProp                     : "Propriétés du bouton",\r
+CheckboxProp           : "Propriétés de la case à cocher",\r
+HiddenFieldProp                : "Propriétés du champ caché",\r
+RadioButtonProp                : "Propriétés du bouton radio",\r
+ImageButtonProp                : "Propriétés du bouton image",\r
+TextFieldProp          : "Propriétés du champ texte",\r
+SelectionFieldProp     : "Propriétés de la liste/du menu",\r
+TextareaProp           : "Propriétés de la zone de texte",\r
+FormProp                       : "Propriétés du formulaire",\r
+\r
+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)",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Calcul XHTML. Veuillez patienter...",\r
+Done                           : "Terminé",\r
+PasteWordConfirm       : "Le texte à coller semble provenir de Word. Désirez-vous le nettoyer avant de coller?",\r
+NotCompatiblePaste     : "Cette commande nécessite Internet Explorer version 5.5 minimum. Souhaitez-vous coller sans nettoyage?",\r
+UnknownToolbarItem     : "Elément de barre d'outil inconnu \"%1\"",\r
+UnknownCommand         : "Nom de commande inconnu \"%1\"",\r
+NotImplemented         : "Commande non encore écrite",\r
+UnknownToolbarSet      : "La barre d'outils \"%1\" n'existe pas",\r
+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.",\r
+BrowseServerBlocked : "Le navigateur n'a pas pu être ouvert. Assurez-vous que les bloqueurs de popups soient désactivés.",\r
+DialogBlocked          : "La fenêtre de dialogue n'a pas pu s'ouvrir. Assurez-vous que les bloqueurs de popups soient désactivés.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Annuler",\r
+DlgBtnClose                    : "Fermer",\r
+DlgBtnBrowseServer     : "Parcourir le serveur",\r
+DlgAdvancedTag         : "Avancé",\r
+DlgOpOther                     : "<Autre>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Veuillez saisir l'URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<Par défaut>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Sens d'écriture",\r
+DlgGenLangDirLtr       : "De gauche à droite (LTR)",\r
+DlgGenLangDirRtl       : "De droite à gauche (RTL)",\r
+DlgGenLangCode         : "Code langue",\r
+DlgGenAccessKey                : "Equivalent clavier",\r
+DlgGenName                     : "Nom",\r
+DlgGenTabIndex         : "Ordre de tabulation",\r
+DlgGenLongDescr                : "URL de description longue",\r
+DlgGenClass                    : "Classes de feuilles de style",\r
+DlgGenTitle                    : "Titre",\r
+DlgGenContType         : "Type de contenu",\r
+DlgGenLinkCharset      : "Encodage de caractère",\r
+DlgGenStyle                    : "Style",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Propriétés de l'image",\r
+DlgImgInfoTab          : "Informations sur l'image",\r
+DlgImgBtnUpload                : "Envoyer sur le serveur",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Télécharger",\r
+DlgImgAlt                      : "Texte de remplacement",\r
+DlgImgWidth                    : "Largeur",\r
+DlgImgHeight           : "Hauteur",\r
+DlgImgLockRatio                : "Garder les proportions",\r
+DlgBtnResetSize                : "Taille originale",\r
+DlgImgBorder           : "Bordure",\r
+DlgImgHSpace           : "Espacement horizontal",\r
+DlgImgVSpace           : "Espacement vertical",\r
+DlgImgAlign                    : "Alignement",\r
+DlgImgAlignLeft                : "Gauche",\r
+DlgImgAlignAbsBottom: "Abs Bas",\r
+DlgImgAlignAbsMiddle: "Abs Milieu",\r
+DlgImgAlignBaseline    : "Bas du texte",\r
+DlgImgAlignBottom      : "Bas",\r
+DlgImgAlignMiddle      : "Milieu",\r
+DlgImgAlignRight       : "Droite",\r
+DlgImgAlignTextTop     : "Haut du texte",\r
+DlgImgAlignTop         : "Haut",\r
+DlgImgPreview          : "Prévisualisation",\r
+DlgImgAlertUrl         : "Veuillez saisir l'URL de l'image",\r
+DlgImgLinkTab          : "Lien",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Propriétés de l'animation Flash",\r
+DlgFlashChkPlay                : "Lecture automatique",\r
+DlgFlashChkLoop                : "Boucle",\r
+DlgFlashChkMenu                : "Activer le menu Flash",\r
+DlgFlashScale          : "Affichage",\r
+DlgFlashScaleAll       : "Par défaut (tout montrer)",\r
+DlgFlashScaleNoBorder  : "Sans bordure",\r
+DlgFlashScaleFit       : "Ajuster aux dimensions",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Propriétés du lien",\r
+DlgLnkInfoTab          : "Informations sur le lien",\r
+DlgLnkTargetTab                : "Destination",\r
+\r
+DlgLnkType                     : "Type de lien",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Ancre dans cette page",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocole",\r
+DlgLnkProtoOther       : "<autre>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Sélectionner une ancre",\r
+DlgLnkAnchorByName     : "Par nom",\r
+DlgLnkAnchorById       : "Par id",\r
+DlgLnkNoAnchors                : "<Pas d'ancre disponible dans le document>",          //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Adresse E-Mail",\r
+DlgLnkEMailSubject     : "Sujet du message",\r
+DlgLnkEMailBody                : "Corps du message",\r
+DlgLnkUpload           : "Télécharger",\r
+DlgLnkBtnUpload                : "Envoyer sur le serveur",\r
+\r
+DlgLnkTarget           : "Destination",\r
+DlgLnkTargetFrame      : "<cadre>",\r
+DlgLnkTargetPopup      : "<fenêtre popup>",\r
+DlgLnkTargetBlank      : "Nouvelle fenêtre (_blank)",\r
+DlgLnkTargetParent     : "Fenêtre mère (_parent)",\r
+DlgLnkTargetSelf       : "Même fenêtre (_self)",\r
+DlgLnkTargetTop                : "Fenêtre supérieure (_top)",\r
+DlgLnkTargetFrameName  : "Nom du cadre de destination",\r
+DlgLnkPopWinName       : "Nom de la fenêtre popup",\r
+DlgLnkPopWinFeat       : "Caractéristiques de la fenêtre popup",\r
+DlgLnkPopResize                : "Taille modifiable",\r
+DlgLnkPopLocation      : "Barre d'adresses",\r
+DlgLnkPopMenu          : "Barre de menu",\r
+DlgLnkPopScroll                : "Barres de défilement",\r
+DlgLnkPopStatus                : "Barre d'état",\r
+DlgLnkPopToolbar       : "Barre d'outils",\r
+DlgLnkPopFullScrn      : "Plein écran (IE)",\r
+DlgLnkPopDependent     : "Dépendante (Netscape)",\r
+DlgLnkPopWidth         : "Largeur",\r
+DlgLnkPopHeight                : "Hauteur",\r
+DlgLnkPopLeft          : "Position à partir de la gauche",\r
+DlgLnkPopTop           : "Position à partir du haut",\r
+\r
+DlnLnkMsgNoUrl         : "Veuillez saisir l'URL",\r
+DlnLnkMsgNoEMail       : "Veuillez saisir l'adresse e-mail",\r
+DlnLnkMsgNoAnchor      : "Veuillez sélectionner une ancre",\r
+DlnLnkMsgInvPopName    : "Le nom de la fenêtre popup doit commencer par une lettre et ne doit pas contenir d'espace",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Sélectionner",\r
+DlgColorBtnClear       : "Effacer",\r
+DlgColorHighlight      : "Prévisualisation",\r
+DlgColorSelected       : "Sélectionné",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Insérer un Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Insérer un caractère spécial",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Propriétés du tableau",\r
+DlgTableRows           : "Lignes",\r
+DlgTableColumns                : "Colonnes",\r
+DlgTableBorder         : "Bordure",\r
+DlgTableAlign          : "Alignement",\r
+DlgTableAlignNotSet    : "<Par défaut>",\r
+DlgTableAlignLeft      : "Gauche",\r
+DlgTableAlignCenter    : "Centré",\r
+DlgTableAlignRight     : "Droite",\r
+DlgTableWidth          : "Largeur",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "pourcentage",\r
+DlgTableHeight         : "Hauteur",\r
+DlgTableCellSpace      : "Espacement",\r
+DlgTableCellPad                : "Contour",\r
+DlgTableCaption                : "Titre",\r
+DlgTableSummary                : "Résumé",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Propriétés de la cellule",\r
+DlgCellWidth           : "Largeur",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "pourcentage",\r
+DlgCellHeight          : "Hauteur",\r
+DlgCellWordWrap                : "Retour à la ligne",\r
+DlgCellWordWrapNotSet  : "<Par défaut>",\r
+DlgCellWordWrapYes     : "Oui",\r
+DlgCellWordWrapNo      : "Non",\r
+DlgCellHorAlign                : "Alignement horizontal",\r
+DlgCellHorAlignNotSet  : "<Par défaut>",\r
+DlgCellHorAlignLeft    : "Gauche",\r
+DlgCellHorAlignCenter  : "Centré",\r
+DlgCellHorAlignRight: "Droite",\r
+DlgCellVerAlign                : "Alignement vertical",\r
+DlgCellVerAlignNotSet  : "<Par défaut>",\r
+DlgCellVerAlignTop     : "Haut",\r
+DlgCellVerAlignMiddle  : "Milieu",\r
+DlgCellVerAlignBottom  : "Bas",\r
+DlgCellVerAlignBaseline        : "Bas du texte",\r
+DlgCellRowSpan         : "Lignes fusionnées",\r
+DlgCellCollSpan                : "Colonnes fusionnées",\r
+DlgCellBackColor       : "Fond",\r
+DlgCellBorderColor     : "Bordure",\r
+DlgCellBtnSelect       : "Choisir...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Chercher",\r
+DlgFindFindBtn         : "Chercher",\r
+DlgFindNotFoundMsg     : "Le texte indiqué est introuvable.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Remplacer",\r
+DlgReplaceFindLbl              : "Rechercher:",\r
+DlgReplaceReplaceLbl   : "Remplacer par:",\r
+DlgReplaceCaseChk              : "Respecter la casse",\r
+DlgReplaceReplaceBtn   : "Remplacer",\r
+DlgReplaceReplAllBtn   : "Tout remplacer",\r
+DlgReplaceWordChk              : "Mot entier",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Coller comme texte",\r
+PasteFromWord  : "Coller à partir de Word",\r
+\r
+DlgPasteMsg2   : "Veuillez coller dans la zone ci-dessous en utilisant le clavier (<STRONG>Ctrl+V</STRONG>) et cliquez sur <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorer les polices de caractères",\r
+DlgPasteRemoveStyles   : "Supprimer les styles",\r
+DlgPasteCleanBox               : "Effacer le contenu",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatique",\r
+ColorMoreColors        : "Plus de couleurs...",\r
+\r
+// Document Properties\r
+DocProps               : "Propriétés du document",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Propriétés de l'ancre",\r
+DlgAnchorName          : "Nom de l'ancre",\r
+DlgAnchorErrorName     : "Veuillez saisir le nom de l'ancre",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Pas dans le dictionnaire",\r
+DlgSpellChangeTo               : "Changer en",\r
+DlgSpellBtnIgnore              : "Ignorer",\r
+DlgSpellBtnIgnoreAll   : "Ignorer tout",\r
+DlgSpellBtnReplace             : "Remplacer",\r
+DlgSpellBtnReplaceAll  : "Remplacer tout",\r
+DlgSpellBtnUndo                        : "Annuler",\r
+DlgSpellNoSuggestions  : "- Aucune suggestion -",\r
+DlgSpellProgress               : "Vérification d'orthographe en cours...",\r
+DlgSpellNoMispell              : "Vérification d'orthographe terminée: Aucune erreur trouvée",\r
+DlgSpellNoChanges              : "Vérification d'orthographe terminée: Pas de modifications",\r
+DlgSpellOneChange              : "Vérification d'orthographe terminée: Un mot modifié",\r
+DlgSpellManyChanges            : "Vérification d'orthographe terminée: %1 mots modifiés",\r
+\r
+IeSpellDownload                        : "Le Correcteur n'est pas installé. Souhaitez-vous le télécharger maintenant?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Texte (valeur)",\r
+DlgButtonType          : "Type",\r
+DlgButtonTypeBtn       : "Bouton",\r
+DlgButtonTypeSbm       : "Envoyer",\r
+DlgButtonTypeRst       : "Réinitialiser",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nom",\r
+DlgCheckboxValue       : "Valeur",\r
+DlgCheckboxSelected    : "Sélectionné",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nom",\r
+DlgFormAction  : "Action",\r
+DlgFormMethod  : "Méthode",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nom",\r
+DlgSelectValue         : "Valeur",\r
+DlgSelectSize          : "Taille",\r
+DlgSelectLines         : "lignes",\r
+DlgSelectChkMulti      : "Sélection multiple",\r
+DlgSelectOpAvail       : "Options disponibles",\r
+DlgSelectOpText                : "Texte",\r
+DlgSelectOpValue       : "Valeur",\r
+DlgSelectBtnAdd                : "Ajouter",\r
+DlgSelectBtnModify     : "Modifier",\r
+DlgSelectBtnUp         : "Monter",\r
+DlgSelectBtnDown       : "Descendre",\r
+DlgSelectBtnSetValue : "Valeur sélectionnée",\r
+DlgSelectBtnDelete     : "Supprimer",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nom",\r
+DlgTextareaCols        : "Colonnes",\r
+DlgTextareaRows        : "Lignes",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nom",\r
+DlgTextValue           : "Valeur",\r
+DlgTextCharWidth       : "Largeur en caractères",\r
+DlgTextMaxChars                : "Nombre maximum de caractères",\r
+DlgTextType                    : "Type",\r
+DlgTextTypeText                : "Texte",\r
+DlgTextTypePass                : "Mot de passe",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nom",\r
+DlgHiddenValue : "Valeur",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Propriétés de liste à puces",\r
+NumberedListProp       : "Propriétés de liste numérotée",\r
+DlgLstStart                    : "Début",\r
+DlgLstType                     : "Type",\r
+DlgLstTypeCircle       : "Cercle",\r
+DlgLstTypeDisc         : "Disque",\r
+DlgLstTypeSquare       : "Carré",\r
+DlgLstTypeNumbers      : "Nombres (1, 2, 3)",\r
+DlgLstTypeLCase                : "Lettres minuscules (a, b, c)",\r
+DlgLstTypeUCase                : "Lettres majuscules (A, B, C)",\r
+DlgLstTypeSRoman       : "Chiffres romains minuscules (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Chiffres romains majuscules (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Général",\r
+DlgDocBackTab          : "Fond",\r
+DlgDocColorsTab                : "Couleurs et marges",\r
+DlgDocMetaTab          : "Métadonnées",\r
+\r
+DlgDocPageTitle                : "Titre de la page",\r
+DlgDocLangDir          : "Sens d'écriture",\r
+DlgDocLangDirLTR       : "De la gauche vers la droite (LTR)",\r
+DlgDocLangDirRTL       : "De la droite vers la gauche (RTL)",\r
+DlgDocLangCode         : "Code langue",\r
+DlgDocCharSet          : "Encodage de caractère",\r
+DlgDocCharSetCE                : "Europe Centrale",\r
+DlgDocCharSetCT                : "Chinois Traditionnel (Big5)",\r
+DlgDocCharSetCR                : "Cyrillique",\r
+DlgDocCharSetGR                : "Grec",\r
+DlgDocCharSetJP                : "Japanais",\r
+DlgDocCharSetKR                : "Coréen",\r
+DlgDocCharSetTR                : "Turc",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Occidental",\r
+DlgDocCharSetOther     : "Autre encodage de caractère",\r
+\r
+DlgDocDocType          : "Type de document",\r
+DlgDocDocTypeOther     : "Autre type de document",\r
+DlgDocIncXHTML         : "Inclure les déclarations XHTML",\r
+DlgDocBgColor          : "Couleur de fond",\r
+DlgDocBgImage          : "Image de fond",\r
+DlgDocBgNoScroll       : "Image fixe sans défilement",\r
+DlgDocCText                    : "Texte",\r
+DlgDocCLink                    : "Lien",\r
+DlgDocCVisited         : "Lien visité",\r
+DlgDocCActive          : "Lien activé",\r
+DlgDocMargins          : "Marges",\r
+DlgDocMaTop                    : "Haut",\r
+DlgDocMaLeft           : "Gauche",\r
+DlgDocMaRight          : "Droite",\r
+DlgDocMaBottom         : "Bas",\r
+DlgDocMeIndex          : "Mots-clés (séparés par des virgules)",\r
+DlgDocMeDescr          : "Description",\r
+DlgDocMeAuthor         : "Auteur",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Prévisualisation",\r
+\r
+// Templates Dialog\r
+Templates                      : "Modèles",\r
+DlgTemplatesTitle      : "Modèles de contenu",\r
+DlgTemplatesSelMsg     : "Veuillez sélectionner le modèle à ouvrir dans l'éditeur<br>(le contenu actuel sera remplacé):",\r
+DlgTemplatesLoading    : "Chargement de la liste des modèles. Veuillez patienter...",\r
+DlgTemplatesNoTpl      : "(Aucun modèle disponible)",\r
+DlgTemplatesReplace    : "Remplacer tout le contenu",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "A propos de",\r
+DlgAboutBrowserInfoTab : "Navigateur",\r
+DlgAboutLicenseTab     : "License",\r
+DlgAboutVersion                : "version",\r
+DlgAboutInfo           : "Pour plus d'informations, aller à"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/gl.js b/httemplate/elements/fckeditor/editor/lang/gl.js
new file mode 100644 (file)
index 0000000..238d108
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Galician language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Ocultar Ferramentas",\r
+ToolbarExpand          : "Mostrar Ferramentas",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Gardar",\r
+NewPage                                : "Nova Páxina",\r
+Preview                                : "Vista Previa",\r
+Cut                                    : "Cortar",\r
+Copy                           : "Copiar",\r
+Paste                          : "Pegar",\r
+PasteText                      : "Pegar como texto plano",\r
+PasteWord                      : "Pegar dende Word",\r
+Print                          : "Imprimir",\r
+SelectAll                      : "Seleccionar todo",\r
+RemoveFormat           : "Eliminar Formato",\r
+InsertLinkLbl          : "Ligazón",\r
+InsertLink                     : "Inserir/Editar Ligazón",\r
+RemoveLink                     : "Eliminar Ligazón",\r
+Anchor                         : "Inserir/Editar Referencia",\r
+InsertImageLbl         : "Imaxe",\r
+InsertImage                    : "Inserir/Editar Imaxe",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Inserir/Editar Flash",\r
+InsertTableLbl         : "Tabla",\r
+InsertTable                    : "Inserir/Editar Tabla",\r
+InsertLineLbl          : "Liña",\r
+InsertLine                     : "Inserir Liña Horizontal",\r
+InsertSpecialCharLbl: "Carácter Special",\r
+InsertSpecialChar      : "Inserir Carácter Especial",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Inserir Smiley",\r
+About                          : "Acerca de FCKeditor",\r
+Bold                           : "Negrita",\r
+Italic                         : "Cursiva",\r
+Underline                      : "Sub-raiado",\r
+StrikeThrough          : "Tachado",\r
+Subscript                      : "Subíndice",\r
+Superscript                    : "Superíndice",\r
+LeftJustify                    : "Aliñar á Esquerda",\r
+CenterJustify          : "Centrado",\r
+RightJustify           : "Aliñar á Dereita",\r
+BlockJustify           : "Xustificado",\r
+DecreaseIndent         : "Disminuir Sangría",\r
+IncreaseIndent         : "Aumentar Sangría",\r
+Undo                           : "Desfacer",\r
+Redo                           : "Refacer",\r
+NumberedListLbl                : "Lista Numerada",\r
+NumberedList           : "Inserir/Eliminar Lista Numerada",\r
+BulletedListLbl                : "Marcas",\r
+BulletedList           : "Inserir/Eliminar Marcas",\r
+ShowTableBorders       : "Mostrar Bordes das Táboas",\r
+ShowDetails                    : "Mostrar Marcas Parágrafo",\r
+Style                          : "Estilo",\r
+FontFormat                     : "Formato",\r
+Font                           : "Tipo",\r
+FontSize                       : "Tamaño",\r
+TextColor                      : "Cor do Texto",\r
+BGColor                                : "Cor do Fondo",\r
+Source                         : "Código Fonte",\r
+Find                           : "Procurar",\r
+Replace                                : "Substituir",\r
+SpellCheck                     : "Corrección Ortográfica",\r
+UniversalKeyboard      : "Teclado Universal",\r
+PageBreakLbl           : "Salto de Páxina",\r
+PageBreak                      : "Inserir Salto de Páxina",\r
+\r
+Form                   : "Formulario",\r
+Checkbox               : "Cadro de Verificación",\r
+RadioButton            : "Botón de Radio",\r
+TextField              : "Campo de Texto",\r
+Textarea               : "Área de Texto",\r
+HiddenField            : "Campo Oculto",\r
+Button                 : "Botón",\r
+SelectionField : "Campo de Selección",\r
+ImageButton            : "Botón de Imaxe",\r
+\r
+FitWindow              : "Maximizar o tamaño do editor",\r
+\r
+// Context Menu\r
+EditLink                       : "Editar Ligazón",\r
+CellCM                         : "Cela",\r
+RowCM                          : "Fila",\r
+ColumnCM                       : "Columna",\r
+InsertRow                      : "Inserir Fila",\r
+DeleteRows                     : "Borrar Filas",\r
+InsertColumn           : "Inserir Columna",\r
+DeleteColumns          : "Borrar Columnas",\r
+InsertCell                     : "Inserir Cela",\r
+DeleteCells                    : "Borrar Cela",\r
+MergeCells                     : "Unir Celas",\r
+SplitCell                      : "Partir Celas",\r
+TableDelete                    : "Borrar Táboa",\r
+CellProperties         : "Propriedades da Cela",\r
+TableProperties                : "Propriedades da Táboa",\r
+ImageProperties                : "Propriedades Imaxe",\r
+FlashProperties                : "Propriedades Flash",\r
+\r
+AnchorProp                     : "Propriedades da Referencia",\r
+ButtonProp                     : "Propriedades do Botón",\r
+CheckboxProp           : "Propriedades do Cadro de Verificación",\r
+HiddenFieldProp                : "Propriedades do Campo Oculto",\r
+RadioButtonProp                : "Propriedades do Botón de Radio",\r
+ImageButtonProp                : "Propriedades do Botón de Imaxe",\r
+TextFieldProp          : "Propriedades do Campo de Texto",\r
+SelectionFieldProp     : "Propriedades do Campo de Selección",\r
+TextareaProp           : "Propriedades da Área de Texto",\r
+FormProp                       : "Propriedades do Formulario",\r
+\r
+FontFormats                    : "Normal;Formateado;Enderezo;Enacabezado 1;Encabezado 2;Encabezado 3;Encabezado 4;Encabezado 5;Encabezado 6;Paragraph (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Procesando XHTML. Por facor, agarde...",\r
+Done                           : "Feiro",\r
+PasteWordConfirm       : "Parece que o texto que quere pegar está copiado do Word.¿Quere limpar o formato antes de pegalo?",\r
+NotCompatiblePaste     : "Este comando está disponible para Internet Explorer versión 5.5 ou superior. ¿Quere pegalo sen limpar o formato?",\r
+UnknownToolbarItem     : "Ítem de ferramentas descoñecido \"%1\"",\r
+UnknownCommand         : "Nome de comando descoñecido \"%1\"",\r
+NotImplemented         : "Comando non implementado",\r
+UnknownToolbarSet      : "O conxunto de ferramentas \"%1\" non existe",\r
+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",\r
+BrowseServerBlocked : "Non se poido abrir o navegador de recursos. Asegúrese de que están desactivados os bloqueadores de xanelas emerxentes",\r
+DialogBlocked          : "Non foi posible abrir a xanela de diálogo. Asegúrese de que están desactivados os bloqueadores de xanelas emerxentes",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Cancelar",\r
+DlgBtnClose                    : "Pechar",\r
+DlgBtnBrowseServer     : "Navegar no Servidor",\r
+DlgAdvancedTag         : "Advanzado",\r
+DlgOpOther                     : "<Outro>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Por favor, insira a URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<non definido>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Orientación do Idioma",\r
+DlgGenLangDirLtr       : "Esquerda a Dereita (LTR)",\r
+DlgGenLangDirRtl       : "Dereita a Esquerda (RTL)",\r
+DlgGenLangCode         : "Código do Idioma",\r
+DlgGenAccessKey                : "Chave de Acceso",\r
+DlgGenName                     : "Nome",\r
+DlgGenTabIndex         : "Índice de Tabulación",\r
+DlgGenLongDescr                : "Descrición Completa da URL",\r
+DlgGenClass                    : "Clases da Folla de Estilos",\r
+DlgGenTitle                    : "Título",\r
+DlgGenContType         : "Tipo de Contido",\r
+DlgGenLinkCharset      : "Fonte de Caracteres Vinculado",\r
+DlgGenStyle                    : "Estilo",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Propriedades da Imaxe",\r
+DlgImgInfoTab          : "Información da Imaxe",\r
+DlgImgBtnUpload                : "Enviar ó Servidor",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Carregar",\r
+DlgImgAlt                      : "Texto Alternativo",\r
+DlgImgWidth                    : "Largura",\r
+DlgImgHeight           : "Altura",\r
+DlgImgLockRatio                : "Proporcional",\r
+DlgBtnResetSize                : "Tamaño Orixinal",\r
+DlgImgBorder           : "Límite",\r
+DlgImgHSpace           : "Esp. Horiz.",\r
+DlgImgVSpace           : "Esp. Vert.",\r
+DlgImgAlign                    : "Aliñamento",\r
+DlgImgAlignLeft                : "Esquerda",\r
+DlgImgAlignAbsBottom: "Abs Inferior",\r
+DlgImgAlignAbsMiddle: "Abs Centro",\r
+DlgImgAlignBaseline    : "Liña Base",\r
+DlgImgAlignBottom      : "Pé",\r
+DlgImgAlignMiddle      : "Centro",\r
+DlgImgAlignRight       : "Dereita",\r
+DlgImgAlignTextTop     : "Tope do Texto",\r
+DlgImgAlignTop         : "Tope",\r
+DlgImgPreview          : "Vista Previa",\r
+DlgImgAlertUrl         : "Por favor, escriba a URL da imaxe",\r
+DlgImgLinkTab          : "Ligazón",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Propriedades Flash",\r
+DlgFlashChkPlay                : "Auto Execución",\r
+DlgFlashChkLoop                : "Bucle",\r
+DlgFlashChkMenu                : "Activar Menú Flash",\r
+DlgFlashScale          : "Escalar",\r
+DlgFlashScaleAll       : "Amosar Todo",\r
+DlgFlashScaleNoBorder  : "Sen Borde",\r
+DlgFlashScaleFit       : "Encaixar axustando",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Ligazón",\r
+DlgLnkInfoTab          : "Información da Ligazón",\r
+DlgLnkTargetTab                : "Referencia a esta páxina",\r
+\r
+DlgLnkType                     : "Tipo de Ligazón",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Referencia nesta páxina",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocolo",\r
+DlgLnkProtoOther       : "<outro>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Seleccionar unha Referencia",\r
+DlgLnkAnchorByName     : "Por Nome de Referencia",\r
+DlgLnkAnchorById       : "Por Element Id",\r
+DlgLnkNoAnchors                : "<Non hai referencias disponibles no documento>",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Enderezo de E-Mail",\r
+DlgLnkEMailSubject     : "Asunto do Mensaxe",\r
+DlgLnkEMailBody                : "Corpo do Mensaxe",\r
+DlgLnkUpload           : "Carregar",\r
+DlgLnkBtnUpload                : "Enviar ó servidor",\r
+\r
+DlgLnkTarget           : "Destino",\r
+DlgLnkTargetFrame      : "<frame>",\r
+DlgLnkTargetPopup      : "<Xanela Emerxente>",\r
+DlgLnkTargetBlank      : "Nova Xanela (_blank)",\r
+DlgLnkTargetParent     : "Xanela Pai (_parent)",\r
+DlgLnkTargetSelf       : "Mesma Xanela (_self)",\r
+DlgLnkTargetTop                : "Xanela Primaria (_top)",\r
+DlgLnkTargetFrameName  : "Nome do Marco Destino",\r
+DlgLnkPopWinName       : "Nome da Xanela Emerxente",\r
+DlgLnkPopWinFeat       : "Características da Xanela Emerxente",\r
+DlgLnkPopResize                : "Axustable",\r
+DlgLnkPopLocation      : "Barra de Localización",\r
+DlgLnkPopMenu          : "Barra de Menú",\r
+DlgLnkPopScroll                : "Barras de Desplazamento",\r
+DlgLnkPopStatus                : "Barra de Estado",\r
+DlgLnkPopToolbar       : "Barra de Ferramentas",\r
+DlgLnkPopFullScrn      : "A Toda Pantalla (IE)",\r
+DlgLnkPopDependent     : "Dependente (Netscape)",\r
+DlgLnkPopWidth         : "Largura",\r
+DlgLnkPopHeight                : "Altura",\r
+DlgLnkPopLeft          : "Posición Esquerda",\r
+DlgLnkPopTop           : "Posición dende Arriba",\r
+\r
+DlnLnkMsgNoUrl         : "Por favor, escriba a ligazón URL",\r
+DlnLnkMsgNoEMail       : "Por favor, escriba o enderezo de e-mail",\r
+DlnLnkMsgNoAnchor      : "Por favor, seleccione un destino",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Seleccionar Color",\r
+DlgColorBtnClear       : "Nengunha",\r
+DlgColorHighlight      : "Destacado",\r
+DlgColorSelected       : "Seleccionado",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Inserte un Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Seleccione Caracter Especial",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Propiedades da Táboa",\r
+DlgTableRows           : "Filas",\r
+DlgTableColumns                : "Columnas",\r
+DlgTableBorder         : "Tamaño do Borde",\r
+DlgTableAlign          : "Aliñamento",\r
+DlgTableAlignNotSet    : "<Non Definido>",\r
+DlgTableAlignLeft      : "Esquerda",\r
+DlgTableAlignCenter    : "Centro",\r
+DlgTableAlignRight     : "Ereita",\r
+DlgTableWidth          : "Largura",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "percent",\r
+DlgTableHeight         : "Altura",\r
+DlgTableCellSpace      : "Marxe entre Celas",\r
+DlgTableCellPad                : "Marxe interior",\r
+DlgTableCaption                : "Título",\r
+DlgTableSummary                : "Sumario",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Propriedades da Cela",\r
+DlgCellWidth           : "Largura",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "percent",\r
+DlgCellHeight          : "Altura",\r
+DlgCellWordWrap                : "Axustar Liñas",\r
+DlgCellWordWrapNotSet  : "<Non Definido>",\r
+DlgCellWordWrapYes     : "Si",\r
+DlgCellWordWrapNo      : "Non",\r
+DlgCellHorAlign                : "Aliñamento Horizontal",\r
+DlgCellHorAlignNotSet  : "<Non definido>",\r
+DlgCellHorAlignLeft    : "Esquerda",\r
+DlgCellHorAlignCenter  : "Centro",\r
+DlgCellHorAlignRight: "Dereita",\r
+DlgCellVerAlign                : "Aliñamento Vertical",\r
+DlgCellVerAlignNotSet  : "<Non definido>",\r
+DlgCellVerAlignTop     : "Arriba",\r
+DlgCellVerAlignMiddle  : "Medio",\r
+DlgCellVerAlignBottom  : "Abaixo",\r
+DlgCellVerAlignBaseline        : "Liña de Base",\r
+DlgCellRowSpan         : "Ocupar Filas",\r
+DlgCellCollSpan                : "Ocupar Columnas",\r
+DlgCellBackColor       : "Color de Fondo",\r
+DlgCellBorderColor     : "Color de Borde",\r
+DlgCellBtnSelect       : "Seleccionar...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Procurar",\r
+DlgFindFindBtn         : "Procurar",\r
+DlgFindNotFoundMsg     : "Non te atopou o texto indicado.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Substituir",\r
+DlgReplaceFindLbl              : "Texto a procurar:",\r
+DlgReplaceReplaceLbl   : "Substituir con:",\r
+DlgReplaceCaseChk              : "Coincidir Mai./min.",\r
+DlgReplaceReplaceBtn   : "Substituir",\r
+DlgReplaceReplAllBtn   : "Substitiur Todo",\r
+DlgReplaceWordChk              : "Coincidir con toda a palabra",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Pegar como texto plano",\r
+PasteFromWord  : "Pegar dende Word",\r
+\r
+DlgPasteMsg2   : "Por favor, pegue dentro do seguinte cadro usando o teclado (<STRONG>Ctrl+V</STRONG>) e pulse <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorar as definicións de Tipografía",\r
+DlgPasteRemoveStyles   : "Eliminar as definicións de Estilos",\r
+DlgPasteCleanBox               : "Limpar o Cadro",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automático",\r
+ColorMoreColors        : "Máis Cores...",\r
+\r
+// Document Properties\r
+DocProps               : "Propriedades do Documento",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Propriedades da Referencia",\r
+DlgAnchorName          : "Nome da Referencia",\r
+DlgAnchorErrorName     : "Por favor, escriba o nome da referencia",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Non está no diccionario",\r
+DlgSpellChangeTo               : "Cambiar a",\r
+DlgSpellBtnIgnore              : "Ignorar",\r
+DlgSpellBtnIgnoreAll   : "Ignorar Todas",\r
+DlgSpellBtnReplace             : "Substituir",\r
+DlgSpellBtnReplaceAll  : "Substituir Todas",\r
+DlgSpellBtnUndo                        : "Desfacer",\r
+DlgSpellNoSuggestions  : "- Sen candidatos -",\r
+DlgSpellProgress               : "Corrección ortográfica en progreso...",\r
+DlgSpellNoMispell              : "Corrección ortográfica rematada: Non se atoparon erros",\r
+DlgSpellNoChanges              : "Corrección ortográfica rematada: Non se substituiu nengunha verba",\r
+DlgSpellOneChange              : "Corrección ortográfica rematada: Unha verba substituida",\r
+DlgSpellManyChanges            : "Corrección ortográfica rematada: %1 verbas substituidas",\r
+\r
+IeSpellDownload                        : "O corrector ortográfico non está instalado. ¿Quere descargalo agora?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Texto (Valor)",\r
+DlgButtonType          : "Tipo",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nome",\r
+DlgCheckboxValue       : "Valor",\r
+DlgCheckboxSelected    : "Seleccionado",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nome",\r
+DlgFormAction  : "Acción",\r
+DlgFormMethod  : "Método",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nome",\r
+DlgSelectValue         : "Valor",\r
+DlgSelectSize          : "Tamaño",\r
+DlgSelectLines         : "liñas",\r
+DlgSelectChkMulti      : "Permitir múltiples seleccións",\r
+DlgSelectOpAvail       : "Opcións Disponibles",\r
+DlgSelectOpText                : "Texto",\r
+DlgSelectOpValue       : "Valor",\r
+DlgSelectBtnAdd                : "Engadir",\r
+DlgSelectBtnModify     : "Modificar",\r
+DlgSelectBtnUp         : "Subir",\r
+DlgSelectBtnDown       : "Baixar",\r
+DlgSelectBtnSetValue : "Definir como valor por defecto",\r
+DlgSelectBtnDelete     : "Borrar",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nome",\r
+DlgTextareaCols        : "Columnas",\r
+DlgTextareaRows        : "Filas",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nome",\r
+DlgTextValue           : "Valor",\r
+DlgTextCharWidth       : "Tamaño do Caracter",\r
+DlgTextMaxChars                : "Máximo de Caracteres",\r
+DlgTextType                    : "Tipo",\r
+DlgTextTypeText                : "Texto",\r
+DlgTextTypePass                : "Chave",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nome",\r
+DlgHiddenValue : "Valor",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Propriedades das Marcas",\r
+NumberedListProp       : "Propriedades da Lista de Numeración",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Tipo",\r
+DlgLstTypeCircle       : "Círculo",\r
+DlgLstTypeDisc         : "Disco",\r
+DlgLstTypeSquare       : "Cuadrado",\r
+DlgLstTypeNumbers      : "Números (1, 2, 3)",\r
+DlgLstTypeLCase                : "Letras Minúsculas (a, b, c)",\r
+DlgLstTypeUCase                : "Letras Maiúsculas (A, B, C)",\r
+DlgLstTypeSRoman       : "Números Romanos en minúscula (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Números Romanos en Maiúscula (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Xeral",\r
+DlgDocBackTab          : "Fondo",\r
+DlgDocColorsTab                : "Cores e Marxes",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Título da Páxina",\r
+DlgDocLangDir          : "Orientación do Idioma",\r
+DlgDocLangDirLTR       : "Esquerda a Dereita (LTR)",\r
+DlgDocLangDirRTL       : "Dereita a Esquerda (RTL)",\r
+DlgDocLangCode         : "Código de Idioma",\r
+DlgDocCharSet          : "Codificación do Xogo de Caracteres",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Outra Codificación do Xogo de Caracteres",\r
+\r
+DlgDocDocType          : "Encabezado do Tipo de Documento",\r
+DlgDocDocTypeOther     : "Outro Encabezado do Tipo de Documento",\r
+DlgDocIncXHTML         : "Incluir Declaracións XHTML",\r
+DlgDocBgColor          : "Cor de Fondo",\r
+DlgDocBgImage          : "URL da Imaxe de Fondo",\r
+DlgDocBgNoScroll       : "Fondo Fixo",\r
+DlgDocCText                    : "Texto",\r
+DlgDocCLink                    : "Ligazóns",\r
+DlgDocCVisited         : "Ligazón Visitada",\r
+DlgDocCActive          : "Ligazón Activa",\r
+DlgDocMargins          : "Marxes da Páxina",\r
+DlgDocMaTop                    : "Arriba",\r
+DlgDocMaLeft           : "Esquerda",\r
+DlgDocMaRight          : "Dereita",\r
+DlgDocMaBottom         : "Abaixo",\r
+DlgDocMeIndex          : "Palabras Chave de Indexación do Documento (separadas por comas)",\r
+DlgDocMeDescr          : "Descripción do Documento",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Vista Previa",\r
+\r
+// Templates Dialog\r
+Templates                      : "Plantillas",\r
+DlgTemplatesTitle      : "Plantillas de Contido",\r
+DlgTemplatesSelMsg     : "Por favor, seleccione a plantilla a abrir no editor<br>(o contido actual perderase):",\r
+DlgTemplatesLoading    : "Cargando listado de plantillas. Por favor, espere...",\r
+DlgTemplatesNoTpl      : "(Non hai plantillas definidas)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Acerca de",\r
+DlgAboutBrowserInfoTab : "Información do Navegador",\r
+DlgAboutLicenseTab     : "Licencia",\r
+DlgAboutVersion                : "versión",\r
+DlgAboutInfo           : "Para máis información visitar:"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/he.js b/httemplate/elements/fckeditor/editor/lang/he.js
new file mode 100644 (file)
index 0000000..ef2b979
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Hebrew language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "rtl",\r
+\r
+ToolbarCollapse                : "כיווץ סרגל הכלים",\r
+ToolbarExpand          : "פתיחת סרגל הכלים",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "שמירה",\r
+NewPage                                : "דף חדש",\r
+Preview                                : "תצוגה מקדימה",\r
+Cut                                    : "גזירה",\r
+Copy                           : "העתקה",\r
+Paste                          : "הדבקה",\r
+PasteText                      : "הדבקה כטקסט פשוט",\r
+PasteWord                      : "הדבקה מ-וורד",\r
+Print                          : "הדפסה",\r
+SelectAll                      : "בחירת הכל",\r
+RemoveFormat           : "הסרת העיצוב",\r
+InsertLinkLbl          : "קישור",\r
+InsertLink                     : "הוספת/עריכת קישור",\r
+RemoveLink                     : "הסרת הקישור",\r
+Anchor                         : "הוספת/עריכת נקודת עיגון",\r
+InsertImageLbl         : "תמונה",\r
+InsertImage                    : "הוספת/עריכת תמונה",\r
+InsertFlashLbl         : "פלאש",\r
+InsertFlash                    : "הוסף/ערוך פלאש",\r
+InsertTableLbl         : "טבלה",\r
+InsertTable                    : "הוספת/עריכת טבלה",\r
+InsertLineLbl          : "קו",\r
+InsertLine                     : "הוספת קו אופקי",\r
+InsertSpecialCharLbl: "תו מיוחד",\r
+InsertSpecialChar      : "הוספת תו מיוחד",\r
+InsertSmileyLbl                : "סמיילי",\r
+InsertSmiley           : "הוספת סמיילי",\r
+About                          : "אודות FCKeditor",\r
+Bold                           : "מודגש",\r
+Italic                         : "נטוי",\r
+Underline                      : "קו תחתון",\r
+StrikeThrough          : "כתיב מחוק",\r
+Subscript                      : "כתיב תחתון",\r
+Superscript                    : "כתיב עליון",\r
+LeftJustify                    : "יישור לשמאל",\r
+CenterJustify          : "מרכוז",\r
+RightJustify           : "יישור לימין",\r
+BlockJustify           : "יישור לשוליים",\r
+DecreaseIndent         : "הקטנת אינדנטציה",\r
+IncreaseIndent         : "הגדלת אינדנטציה",\r
+Undo                           : "ביטול צעד אחרון",\r
+Redo                           : "חזרה על צעד אחרון",\r
+NumberedListLbl                : "רשימה ממוספרת",\r
+NumberedList           : "הוספת/הסרת רשימה ממוספרת",\r
+BulletedListLbl                : "רשימת נקודות",\r
+BulletedList           : "הוספת/הסרת רשימת נקודות",\r
+ShowTableBorders       : "הצגת מסגרת הטבלה",\r
+ShowDetails                    : "הצגת פרטים",\r
+Style                          : "סגנון",\r
+FontFormat                     : "עיצוב",\r
+Font                           : "גופן",\r
+FontSize                       : "גודל",\r
+TextColor                      : "צבע טקסט",\r
+BGColor                                : "צבע רקע",\r
+Source                         : "מקור",\r
+Find                           : "חיפוש",\r
+Replace                                : "החלפה",\r
+SpellCheck                     : "בדיקת איות",\r
+UniversalKeyboard      : "מקלדת אוניברסלית",\r
+PageBreakLbl           : "שבירת דף",\r
+PageBreak                      : "הוסף שבירת דף",\r
+\r
+Form                   : "טופס",\r
+Checkbox               : "תיבת סימון",\r
+RadioButton            : "לחצן אפשרויות",\r
+TextField              : "שדה טקסט",\r
+Textarea               : "איזור טקסט",\r
+HiddenField            : "שדה חבוי",\r
+Button                 : "כפתור",\r
+SelectionField : "שדה בחירה",\r
+ImageButton            : "כפתור תמונה",\r
+\r
+FitWindow              : "הגדל את גודל העורך",\r
+\r
+// Context Menu\r
+EditLink                       : "עריכת קישור",\r
+CellCM                         : "תא",\r
+RowCM                          : "שורה",\r
+ColumnCM                       : "עמודה",\r
+InsertRow                      : "הוספת שורה",\r
+DeleteRows                     : "מחיקת שורות",\r
+InsertColumn           : "הוספת עמודה",\r
+DeleteColumns          : "מחיקת עמודות",\r
+InsertCell                     : "הוספת תא",\r
+DeleteCells                    : "מחיקת תאים",\r
+MergeCells                     : "מיזוג תאים",\r
+SplitCell                      : "פיצול תאים",\r
+TableDelete                    : "מחק טבלה",\r
+CellProperties         : "תכונות התא",\r
+TableProperties                : "תכונות הטבלה",\r
+ImageProperties                : "תכונות התמונה",\r
+FlashProperties                : "מאפייני פלאש",\r
+\r
+AnchorProp                     : "מאפייני נקודת עיגון",\r
+ButtonProp                     : "מאפייני כפתור",\r
+CheckboxProp           : "מאפייני תיבת סימון",\r
+HiddenFieldProp                : "מאפיני שדה חבוי",\r
+RadioButtonProp                : "מאפייני לחצן אפשרויות",\r
+ImageButtonProp                : "מאפיני כפתור תמונה",\r
+TextFieldProp          : "מאפייני שדה טקסט",\r
+SelectionFieldProp     : "מאפייני שדה בחירה",\r
+TextareaProp           : "מאפיני איזור טקסט",\r
+FormProp                       : "מאפיני טופס",\r
+\r
+FontFormats                    : "נורמלי;קוד;כתובת;כותרת;כותרת 2;כותרת 3;כותרת 4;כותרת 5;כותרת 6",         //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "מעבד XHTML, נא להמתין...",\r
+Done                           : "המשימה הושלמה",\r
+PasteWordConfirm       : "נראה הטקסט שבכוונתך להדביק מקורו בקובץ וורד. האם ברצונך לנקות אותו טרם ההדבקה?",\r
+NotCompatiblePaste     : "פעולה זו זמינה לדפדפן אינטרנט אקספלורר מגירסא 5.5 ומעלה. האם להמשיך בהדבקה ללא הניקוי?",\r
+UnknownToolbarItem     : "פריט לא ידוע בסרגל הכלים \"%1\"",\r
+UnknownCommand         : "שם פעולה לא ידוע \"%1\"",\r
+NotImplemented         : "הפקודה לא מיושמת",\r
+UnknownToolbarSet      : "ערכת סרגל הכלים \"%1\" לא קיימת",\r
+NoActiveX                      : "הגדרות אבטחה של הדפדפן עלולות לגביל את אפשרויות העריכה.יש לאפשר את האופציה \"הרץ פקדים פעילים ותוספות\". תוכל לחוות טעויות וחיווים של אפשרויות שחסרים.",\r
+BrowseServerBlocked : "לא ניתן לגשת לדפדפן משאבים.אנא וודא שחוסם חלונות הקופצים לא פעיל.",\r
+DialogBlocked          : "לא היה ניתן לפתוח חלון דיאלוג. אנא וודא שחוסם חלונות קופצים לא פעיל.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "אישור",\r
+DlgBtnCancel           : "ביטול",\r
+DlgBtnClose                    : "סגירה",\r
+DlgBtnBrowseServer     : "סייר השרת",\r
+DlgAdvancedTag         : "אפשרויות מתקדמות",\r
+DlgOpOther                     : "<אחר>",\r
+DlgInfoTab                     : "מידע",\r
+DlgAlertUrl                    : "אנה הזן URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<לא נקבע>",\r
+DlgGenId                       : "זיהוי (Id)",\r
+DlgGenLangDir          : "כיוון שפה",\r
+DlgGenLangDirLtr       : "שמאל לימין (LTR)",\r
+DlgGenLangDirRtl       : "ימין לשמאל (RTL)",\r
+DlgGenLangCode         : "קוד שפה",\r
+DlgGenAccessKey                : "מקש גישה",\r
+DlgGenName                     : "שם",\r
+DlgGenTabIndex         : "מספר טאב",\r
+DlgGenLongDescr                : "קישור לתיאור מפורט",\r
+DlgGenClass                    : "גיליונות עיצוב קבוצות",\r
+DlgGenTitle                    : "כותרת מוצעת",\r
+DlgGenContType         : "Content Type מוצע",\r
+DlgGenLinkCharset      : "קידוד המשאב המקושר",\r
+DlgGenStyle                    : "סגנון",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "תכונות התמונה",\r
+DlgImgInfoTab          : "מידע על התמונה",\r
+DlgImgBtnUpload                : "שליחה לשרת",\r
+DlgImgURL                      : "כתובת (URL)",\r
+DlgImgUpload           : "העלאה",\r
+DlgImgAlt                      : "טקסט חלופי",\r
+DlgImgWidth                    : "רוחב",\r
+DlgImgHeight           : "גובה",\r
+DlgImgLockRatio                : "נעילת היחס",\r
+DlgBtnResetSize                : "איפוס הגודל",\r
+DlgImgBorder           : "מסגרת",\r
+DlgImgHSpace           : "מרווח אופקי",\r
+DlgImgVSpace           : "מרווח אנכי",\r
+DlgImgAlign                    : "יישור",\r
+DlgImgAlignLeft                : "לשמאל",\r
+DlgImgAlignAbsBottom: "לתחתית האבסולוטית",\r
+DlgImgAlignAbsMiddle: "מרכוז אבסולוטי",\r
+DlgImgAlignBaseline    : "לקו התחתית",\r
+DlgImgAlignBottom      : "לתחתית",\r
+DlgImgAlignMiddle      : "לאמצע",\r
+DlgImgAlignRight       : "לימין",\r
+DlgImgAlignTextTop     : "לראש הטקסט",\r
+DlgImgAlignTop         : "למעלה",\r
+DlgImgPreview          : "תצוגה מקדימה",\r
+DlgImgAlertUrl         : "נא להקליד את כתובת התמונה",\r
+DlgImgLinkTab          : "קישור",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "מאפיני פלאש",\r
+DlgFlashChkPlay                : "נגן אוטומטי",\r
+DlgFlashChkLoop                : "לולאה",\r
+DlgFlashChkMenu                : "אפשר תפריט פלאש",\r
+DlgFlashScale          : "גודל",\r
+DlgFlashScaleAll       : "הצג הכל",\r
+DlgFlashScaleNoBorder  : "ללא גבולות",\r
+DlgFlashScaleFit       : "התאמה מושלמת",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "קישור",\r
+DlgLnkInfoTab          : "מידע על הקישור",\r
+DlgLnkTargetTab                : "מטרה",\r
+\r
+DlgLnkType                     : "סוג קישור",\r
+DlgLnkTypeURL          : "כתובת (URL)",\r
+DlgLnkTypeAnchor       : "עוגן בעמוד זה",\r
+DlgLnkTypeEMail                : "דוא''ל",\r
+DlgLnkProto                    : "פרוטוקול",\r
+DlgLnkProtoOther       : "<אחר>",\r
+DlgLnkURL                      : "כתובת (URL)",\r
+DlgLnkAnchorSel                : "בחירת עוגן",\r
+DlgLnkAnchorByName     : "עפ''י שם העוגן",\r
+DlgLnkAnchorById       : "עפ''י זיהוי (Id) הרכיב",\r
+DlgLnkNoAnchors                : "(אין עוגנים זמינים בדף)",          //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "כתובת הדוא''ל",\r
+DlgLnkEMailSubject     : "נושא ההודעה",\r
+DlgLnkEMailBody                : "גוף ההודעה",\r
+DlgLnkUpload           : "העלאה",\r
+DlgLnkBtnUpload                : "שליחה לשרת",\r
+\r
+DlgLnkTarget           : "מטרה",\r
+DlgLnkTargetFrame      : "<מסגרת>",\r
+DlgLnkTargetPopup      : "<חלון קופץ>",\r
+DlgLnkTargetBlank      : "חלון חדש (_blank)",\r
+DlgLnkTargetParent     : "חלון האב (_parent)",\r
+DlgLnkTargetSelf       : "באותו החלון (_self)",\r
+DlgLnkTargetTop                : "חלון ראשי (_top)",\r
+DlgLnkTargetFrameName  : "שם מסגרת היעד",\r
+DlgLnkPopWinName       : "שם החלון הקופץ",\r
+DlgLnkPopWinFeat       : "תכונות החלון הקופץ",\r
+DlgLnkPopResize                : "בעל גודל ניתן לשינוי",\r
+DlgLnkPopLocation      : "סרגל כתובת",\r
+DlgLnkPopMenu          : "סרגל תפריט",\r
+DlgLnkPopScroll                : "ניתן לגלילה",\r
+DlgLnkPopStatus                : "סרגל חיווי",\r
+DlgLnkPopToolbar       : "סרגל הכלים",\r
+DlgLnkPopFullScrn      : "מסך מלא (IE)",\r
+DlgLnkPopDependent     : "תלוי (Netscape)",\r
+DlgLnkPopWidth         : "רוחב",\r
+DlgLnkPopHeight                : "גובה",\r
+DlgLnkPopLeft          : "מיקום צד שמאל",\r
+DlgLnkPopTop           : "מיקום צד עליון",\r
+\r
+DlnLnkMsgNoUrl         : "נא להקליד את כתובת הקישור (URL)",\r
+DlnLnkMsgNoEMail       : "נא להקליד את כתובת הדוא''ל",\r
+DlnLnkMsgNoAnchor      : "נא לבחור עוגן במסמך",\r
+DlnLnkMsgInvPopName    : "שם החלון הקופץ חייב להתחיל באותיות ואסור לכלול רווחים",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "בחירת צבע",\r
+DlgColorBtnClear       : "איפוס",\r
+DlgColorHighlight      : "נוכחי",\r
+DlgColorSelected       : "נבחר",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "הוספת סמיילי",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "בחירת תו מיוחד",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "תכונות טבלה",\r
+DlgTableRows           : "שורות",\r
+DlgTableColumns                : "עמודות",\r
+DlgTableBorder         : "גודל מסגרת",\r
+DlgTableAlign          : "יישור",\r
+DlgTableAlignNotSet    : "<לא נקבע>",\r
+DlgTableAlignLeft      : "שמאל",\r
+DlgTableAlignCenter    : "מרכז",\r
+DlgTableAlignRight     : "ימין",\r
+DlgTableWidth          : "רוחב",\r
+DlgTableWidthPx                : "פיקסלים",\r
+DlgTableWidthPc                : "אחוז",\r
+DlgTableHeight         : "גובה",\r
+DlgTableCellSpace      : "מרווח תא",\r
+DlgTableCellPad                : "ריפוד תא",\r
+DlgTableCaption                : "כיתוב",\r
+DlgTableSummary                : "סיכום",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "תכונות תא",\r
+DlgCellWidth           : "רוחב",\r
+DlgCellWidthPx         : "פיקסלים",\r
+DlgCellWidthPc         : "אחוז",\r
+DlgCellHeight          : "גובה",\r
+DlgCellWordWrap                : "גלילת שורות",\r
+DlgCellWordWrapNotSet  : "<לא נקבע>",\r
+DlgCellWordWrapYes     : "כן",\r
+DlgCellWordWrapNo      : "לא",\r
+DlgCellHorAlign                : "יישור אופקי",\r
+DlgCellHorAlignNotSet  : "<לא נקבע>",\r
+DlgCellHorAlignLeft    : "שמאל",\r
+DlgCellHorAlignCenter  : "מרכז",\r
+DlgCellHorAlignRight: "ימין",\r
+DlgCellVerAlign                : "יישור אנכי",\r
+DlgCellVerAlignNotSet  : "<לא נקבע>",\r
+DlgCellVerAlignTop     : "למעלה",\r
+DlgCellVerAlignMiddle  : "לאמצע",\r
+DlgCellVerAlignBottom  : "לתחתית",\r
+DlgCellVerAlignBaseline        : "קו תחתית",\r
+DlgCellRowSpan         : "טווח שורות",\r
+DlgCellCollSpan                : "טווח עמודות",\r
+DlgCellBackColor       : "צבע רקע",\r
+DlgCellBorderColor     : "צבע מסגרת",\r
+DlgCellBtnSelect       : "בחירה...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "חיפוש",\r
+DlgFindFindBtn         : "חיפוש",\r
+DlgFindNotFoundMsg     : "הטקסט המבוקש לא נמצא.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "החלפה",\r
+DlgReplaceFindLbl              : "חיפוש מחרוזת:",\r
+DlgReplaceReplaceLbl   : "החלפה במחרוזת:",\r
+DlgReplaceCaseChk              : "התאמת סוג אותיות (Case)",\r
+DlgReplaceReplaceBtn   : "החלפה",\r
+DlgReplaceReplAllBtn   : "החלפה בכל העמוד",\r
+DlgReplaceWordChk              : "התאמה למילה המלאה",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "הגדרות האבטחה בדפדפן שלך לא מאפשרות לעורך לבצע פעולות גזירה  אוטומטיות. יש להשתמש במקלדת לשם כך (Ctrl+X).",\r
+PasteErrorCopy : "הגדרות האבטחה בדפדפן שלך לא מאפשרות לעורך לבצע פעולות העתקה אוטומטיות. יש להשתמש במקלדת לשם כך (Ctrl+C).",\r
+\r
+PasteAsText            : "הדבקה כטקסט פשוט",\r
+PasteFromWord  : "הדבקה מ-וורד",\r
+\r
+DlgPasteMsg2   : "אנא הדבק בתוך הקופסה באמצעות  (<STRONG>Ctrl+V</STRONG>) ולחץ על  <STRONG>אישור</STRONG>.",\r
+DlgPasteSec            : "עקב הגדרות אבטחה בדפדפן, לא ניתן לגשת אל לוח הגזירים (clipboard) בצורה ישירה.אנא בצע הדבק שוב בחלון זה.",\r
+DlgPasteIgnoreFont             : "התעלם מהגדרות סוג פונט",\r
+DlgPasteRemoveStyles   : "הסר הגדרות סגנון",\r
+DlgPasteCleanBox               : "ניקוי קופסה",\r
+\r
+// Color Picker\r
+ColorAutomatic : "אוטומטי",\r
+ColorMoreColors        : "צבעים נוספים...",\r
+\r
+// Document Properties\r
+DocProps               : "מאפיני מסמך",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "מאפיני נקודת עיגון",\r
+DlgAnchorName          : "שם לנקודת עיגון",\r
+DlgAnchorErrorName     : "אנא הזן שם לנקודת עיגון",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "לא נמצא במילון",\r
+DlgSpellChangeTo               : "שנה ל",\r
+DlgSpellBtnIgnore              : "התעלם",\r
+DlgSpellBtnIgnoreAll   : "התעלם מהכל",\r
+DlgSpellBtnReplace             : "החלף",\r
+DlgSpellBtnReplaceAll  : "החלף הכל",\r
+DlgSpellBtnUndo                        : "החזר",\r
+DlgSpellNoSuggestions  : "- אין הצעות -",\r
+DlgSpellProgress               : "בדיקות איות בתהליך ....",\r
+DlgSpellNoMispell              : "בדיקות איות הסתיימה: לא נמצאו שגיעות כתיב",\r
+DlgSpellNoChanges              : "בדיקות איות הסתיימה: לא שונתה אף מילה",\r
+DlgSpellOneChange              : "בדיקות איות הסתיימה: שונתה מילה אחת",\r
+DlgSpellManyChanges            : "בדיקות איות הסתיימה: %1 מילים שונו",\r
+\r
+IeSpellDownload                        : "בודק האיות לא מותקן, האם אתה מעוניין להוריד?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "טקסט (ערך)",\r
+DlgButtonType          : "סוג",\r
+DlgButtonTypeBtn       : "כפתור",\r
+DlgButtonTypeSbm       : "שלח",\r
+DlgButtonTypeRst       : "אפס",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "שם",\r
+DlgCheckboxValue       : "ערך",\r
+DlgCheckboxSelected    : "בחור",\r
+\r
+// Form Dialog\r
+DlgFormName            : "שם",\r
+DlgFormAction  : "שלח אל",\r
+DlgFormMethod  : "סוג שליחה",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "שם",\r
+DlgSelectValue         : "ערך",\r
+DlgSelectSize          : "גודל",\r
+DlgSelectLines         : "שורות",\r
+DlgSelectChkMulti      : "אפשר בחירות מרובות",\r
+DlgSelectOpAvail       : "אפשרויות זמינות",\r
+DlgSelectOpText                : "טקסט",\r
+DlgSelectOpValue       : "ערך",\r
+DlgSelectBtnAdd                : "הוסף",\r
+DlgSelectBtnModify     : "שנה",\r
+DlgSelectBtnUp         : "למעלה",\r
+DlgSelectBtnDown       : "למטה",\r
+DlgSelectBtnSetValue : "קבע כברירת מחדל",\r
+DlgSelectBtnDelete     : "מחק",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "שם",\r
+DlgTextareaCols        : "עמודות",\r
+DlgTextareaRows        : "שורות",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "שם",\r
+DlgTextValue           : "ערך",\r
+DlgTextCharWidth       : "רוחב באותיות",\r
+DlgTextMaxChars                : "מקסימות אותיות",\r
+DlgTextType                    : "סוג",\r
+DlgTextTypeText                : "טקסט",\r
+DlgTextTypePass                : "סיסמה",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "שם",\r
+DlgHiddenValue : "ערך",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "מאפייני רשימה",\r
+NumberedListProp       : "מאפייני רשימה ממוספרת",\r
+DlgLstStart                    : "התחלה",\r
+DlgLstType                     : "סוג",\r
+DlgLstTypeCircle       : "עיגול",\r
+DlgLstTypeDisc         : "דיסק",\r
+DlgLstTypeSquare       : "מרובע",\r
+DlgLstTypeNumbers      : "מספרים (1, 2, 3)",\r
+DlgLstTypeLCase                : "אותיות קטנות (a, b, c)",\r
+DlgLstTypeUCase                : "אותיות גדולות (A, B, C)",\r
+DlgLstTypeSRoman       : "ספרות רומאיות קטנות (i, ii, iii)",\r
+DlgLstTypeLRoman       : "ספרות רומאיות גדולות (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "כללי",\r
+DlgDocBackTab          : "רקע",\r
+DlgDocColorsTab                : "צבעים וגבולות",\r
+DlgDocMetaTab          : "נתוני META",\r
+\r
+DlgDocPageTitle                : "כותרת דף",\r
+DlgDocLangDir          : "כיוון שפה",\r
+DlgDocLangDirLTR       : "שמאל לימין (LTR)",\r
+DlgDocLangDirRTL       : "ימין לשמאל (RTL)",\r
+DlgDocLangCode         : "קוד שפה",\r
+DlgDocCharSet          : "קידוד אותיות",\r
+DlgDocCharSetCE                : "מרכז אירופה",\r
+DlgDocCharSetCT                : "סיני מסורתי (Big5)",\r
+DlgDocCharSetCR                : "קירילי",\r
+DlgDocCharSetGR                : "יוונית",\r
+DlgDocCharSetJP                : "יפנית",\r
+DlgDocCharSetKR                : "קוראנית",\r
+DlgDocCharSetTR                : "טורקית",\r
+DlgDocCharSetUN                : "יוני קוד (UTF-8)",\r
+DlgDocCharSetWE                : "מערב אירופה",\r
+DlgDocCharSetOther     : "קידוד אותיות אחר",\r
+\r
+DlgDocDocType          : "הגדרות סוג מסמך",\r
+DlgDocDocTypeOther     : "הגדרות סוג מסמך אחרות",\r
+DlgDocIncXHTML         : "כלול הגדרות XHTML",\r
+DlgDocBgColor          : "צבע רקע",\r
+DlgDocBgImage          : "URL לתמונת רקע",\r
+DlgDocBgNoScroll       : "רגע ללא גלילה",\r
+DlgDocCText                    : "טקסט",\r
+DlgDocCLink                    : "קישור",\r
+DlgDocCVisited         : "קישור שבוקר",\r
+DlgDocCActive          : " קישור פעיל",\r
+DlgDocMargins          : "גבולות דף",\r
+DlgDocMaTop                    : "למעלה",\r
+DlgDocMaLeft           : "שמאלה",\r
+DlgDocMaRight          : "ימינה",\r
+DlgDocMaBottom         : "למטה",\r
+DlgDocMeIndex          : "מפתח עניינים של המסמך )מופרד בפסיק(",\r
+DlgDocMeDescr          : "תאור מסמך",\r
+DlgDocMeAuthor         : "מחבר",\r
+DlgDocMeCopy           : "זכויות יוצרים",\r
+DlgDocPreview          : "תצוגה מקדימה",\r
+\r
+// Templates Dialog\r
+Templates                      : "תבניות",\r
+DlgTemplatesTitle      : "תביות תוכן",\r
+DlgTemplatesSelMsg     : "אנא בחר תבנית לפתיחה בעורך <BR>התוכן המקורי ימחק:",\r
+DlgTemplatesLoading    : "מעלה רשימת תבניות אנא המתן",\r
+DlgTemplatesNoTpl      : "(לא הוגדרו תבניות)",\r
+DlgTemplatesReplace    : "החלפת תוכן ממשי",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "אודות",\r
+DlgAboutBrowserInfoTab : "גירסת דפדפן",\r
+DlgAboutLicenseTab     : "רשיון",\r
+DlgAboutVersion                : "גירסא",\r
+DlgAboutInfo           : "מידע נוסף ניתן למצוא כאן:"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/hi.js b/httemplate/elements/fckeditor/editor/lang/hi.js
new file mode 100644 (file)
index 0000000..fdc5e39
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Hindi language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "टूलबार सिमटायें",\r
+ToolbarExpand          : "टूलबार का विस्तार करें",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "सेव",\r
+NewPage                                : "नया पेज",\r
+Preview                                : "प्रीव्यू",\r
+Cut                                    : "कट",\r
+Copy                           : "कॉपी",\r
+Paste                          : "पेस्ट",\r
+PasteText                      : "पेस्ट (सादा टॅक्स्ट)",\r
+PasteWord                      : "पेस्ट (वर्ड से)",\r
+Print                          : "प्रिन्ट",\r
+SelectAll                      : "सब सॅलॅक्ट करें",\r
+RemoveFormat           : "फ़ॉर्मैट हटायें",\r
+InsertLinkLbl          : "लिंक",\r
+InsertLink                     : "लिंक इन्सर्ट/संपादन",\r
+RemoveLink                     : "लिंक हटायें",\r
+Anchor                         : "ऐंकर इन्सर्ट/संपादन",\r
+InsertImageLbl         : "तस्वीर",\r
+InsertImage                    : "तस्वीर इन्सर्ट/संपादन",\r
+InsertFlashLbl         : "फ़्लैश",\r
+InsertFlash                    : "फ़्लैश इन्सर्ट/संपादन",\r
+InsertTableLbl         : "टेबल",\r
+InsertTable                    : "टेबल इन्सर्ट/संपादन",\r
+InsertLineLbl          : "रेखा",\r
+InsertLine                     : "हॉरिज़ॉन्टल रेखा इन्सर्ट करें",\r
+InsertSpecialCharLbl: "विशेष करॅक्टर",\r
+InsertSpecialChar      : "विशेष करॅक्टर इन्सर्ट करें",\r
+InsertSmileyLbl                : "स्माइली",\r
+InsertSmiley           : "स्माइली इन्सर्ट करें",\r
+About                          : "FCKeditor के बारे में",\r
+Bold                           : "बोल्ड",\r
+Italic                         : "इटैलिक",\r
+Underline                      : "रेखांकण",\r
+StrikeThrough          : "स्ट्राइक थ्रू",\r
+Subscript                      : "अधोलेख",\r
+Superscript                    : "अभिलेख",\r
+LeftJustify                    : "बायीं तरफ",\r
+CenterJustify          : "बीच में",\r
+RightJustify           : "दायीं तरफ",\r
+BlockJustify           : "ब्लॉक जस्टीफ़ाई",\r
+DecreaseIndent         : "इन्डॅन्ट कम करें",\r
+IncreaseIndent         : "इन्डॅन्ट बढ़ायें",\r
+Undo                           : "अन्डू",\r
+Redo                           : "रीडू",\r
+NumberedListLbl                : "अंकीय सूची",\r
+NumberedList           : "अंकीय सूची इन्सर्ट/संपादन",\r
+BulletedListLbl                : "बुलॅट सूची",\r
+BulletedList           : "बुलॅट सूची इन्सर्ट/संपादन",\r
+ShowTableBorders       : "टेबल बॉर्डरयें दिखायें",\r
+ShowDetails                    : "ज्यादा   दिखायें",\r
+Style                          : "स्टाइल",\r
+FontFormat                     : "फ़ॉर्मैट",\r
+Font                           : "फ़ॉन्ट",\r
+FontSize                       : "साइज़",\r
+TextColor                      : "टेक्स्ट रंग",\r
+BGColor                                : "बैक्ग्राउन्ड रंग",\r
+Source                         : "सोर्स",\r
+Find                           : "खोजें",\r
+Replace                                : "रीप्लेस",\r
+SpellCheck                     : "वर्तनी (स्पेलिंग) जाँच",\r
+UniversalKeyboard      : "यूनीवर्सल कीबोर्ड",\r
+PageBreakLbl           : "पेज ब्रेक",\r
+PageBreak                      : "पेज ब्रेक इन्सर्ट् करें",\r
+\r
+Form                   : "फ़ॉर्म",\r
+Checkbox               : "चॅक बॉक्स",\r
+RadioButton            : "रेडिओ बटन",\r
+TextField              : "टेक्स्ट फ़ील्ड",\r
+Textarea               : "टेक्स्ट एरिया",\r
+HiddenField            : "गुप्त फ़ील्ड",\r
+Button                 : "बटन",\r
+SelectionField : "चुनाव फ़ील्ड",\r
+ImageButton            : "तस्वीर बटन",\r
+\r
+FitWindow              : "एडिटर साइज़ को चरम सीमा तक बढ़ायें",\r
+\r
+// Context Menu\r
+EditLink                       : "लिंक संपादन",\r
+CellCM                         : "खाना",\r
+RowCM                          : "पंक्ति",\r
+ColumnCM                       : "कालम",\r
+InsertRow                      : "पंक्ति इन्सर्ट करें",\r
+DeleteRows                     : "पंक्तियाँ डिलीट करें",\r
+InsertColumn           : "कॉलम इन्सर्ट करें",\r
+DeleteColumns          : "कॉलम डिलीट करें",\r
+InsertCell                     : "सॅल इन्सर्ट करें",\r
+DeleteCells                    : "सॅल डिलीट करें",\r
+MergeCells                     : "सॅल मिलायें",\r
+SplitCell                      : "सॅल अलग करें",\r
+TableDelete                    : "टेबल डिलीट करें",\r
+CellProperties         : "सॅल प्रॉपर्टीज़",\r
+TableProperties                : "टेबल प्रॉपर्टीज़",\r
+ImageProperties                : "तस्वीर प्रॉपर्टीज़",\r
+FlashProperties                : "फ़्लैश प्रॉपर्टीज़",\r
+\r
+AnchorProp                     : "ऐंकर प्रॉपर्टीज़",\r
+ButtonProp                     : "बटन प्रॉपर्टीज़",\r
+CheckboxProp           : "चॅक बॉक्स प्रॉपर्टीज़",\r
+HiddenFieldProp                : "गुप्त फ़ील्ड प्रॉपर्टीज़",\r
+RadioButtonProp                : "रेडिओ बटन प्रॉपर्टीज़",\r
+ImageButtonProp                : "तस्वीर बटन प्रॉपर्टीज़",\r
+TextFieldProp          : "टेक्स्ट फ़ील्ड प्रॉपर्टीज़",\r
+SelectionFieldProp     : "चुनाव फ़ील्ड प्रॉपर्टीज़",\r
+TextareaProp           : "टेक्स्त एरिया प्रॉपर्टीज़",\r
+FormProp                       : "फ़ॉर्म प्रॉपर्टीज़",\r
+\r
+FontFormats                    : "साधारण;फ़ॉर्मैटॅड;पता;शीर्षक 1;शीर्षक 2;शीर्षक 3;शीर्षक 4;शीर्षक 5;शीर्षक 6;शीर्षक (DIV)",            //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML प्रोसॅस हो रहा है। ज़रा ठहरें...",\r
+Done                           : "पूरा हुआ",\r
+PasteWordConfirm       : "आप जो टेक्स्ट पेस्ट करना चाहते हैं, वह वर्ड से कॉपी किया हुआ लग रहा है। क्या पेस्ट करने से पहले आप इसे साफ़ करना चाहेंगे?",\r
+NotCompatiblePaste     : "यह कमांड इन्टरनॅट एक्स्प्लोरर(Internet Explorer) 5.5 या उसके बाद के वर्ज़न के लिए ही उपलब्ध है। क्या आप बिना साफ़ किए पेस्ट करना चाहेंगे?",\r
+UnknownToolbarItem     : "अनजान टूलबार आइटम \"%1\"",\r
+UnknownCommand         : "अनजान कमान्ड \"%1\"",\r
+NotImplemented         : "कमान्ड इम्प्लीमॅन्ट नहीं किया गया है",\r
+UnknownToolbarSet      : "टूलबार सॅट \"%1\" उपलब्ध नहीं है",\r
+NoActiveX                      : "आपके ब्राउज़र् की सुरक्शा सेटिंग्स् एडिटर की कुछ् फ़ीचरों को सीमित कर् सकती हैं। क्रिपया \"Run ActiveX controls and plug-ins\" विकल्प को एनेबल करें. आपको एरर्स् और गायब फ़ीचर्स् का अनुभव हो सकता है।",\r
+BrowseServerBlocked : "रिसोर्सेज़ ब्राउज़र् नहीं खोला जा सका। क्रिपया सभी पॉप्-अप् ब्लॉकर्स् को डिसेबल करें।",\r
+DialogBlocked          : "डायलग विन्डो नहीं खोला जा सका। क्रिपया सभी पॉप्-अप् ब्लॉकर्स् को डिसेबल करें।",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "ठीक है",\r
+DlgBtnCancel           : "रद्द करें",\r
+DlgBtnClose                    : "बन्द करें",\r
+DlgBtnBrowseServer     : "सर्वर ब्राउज़ करें",\r
+DlgAdvancedTag         : "ऍड्वान्स्ड",\r
+DlgOpOther                     : "<अन्य>",\r
+DlgInfoTab                     : "सूचना",\r
+DlgAlertUrl                    : "URL इन्सर्ट करें",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<सॅट नहीं>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "भाषा लिखने की दिशा",\r
+DlgGenLangDirLtr       : "बायें से दायें (LTR)",\r
+DlgGenLangDirRtl       : "दायें से बायें (RTL)",\r
+DlgGenLangCode         : "भाषा कोड",\r
+DlgGenAccessKey                : "ऍक्सॅस की",\r
+DlgGenName                     : "नाम",\r
+DlgGenTabIndex         : "टैब इन्डॅक्स",\r
+DlgGenLongDescr                : "अधिक विवरण के लिए URL",\r
+DlgGenClass                    : "स्टाइल-शीट क्लास",\r
+DlgGenTitle                    : "परामर्श शीर्शक",\r
+DlgGenContType         : "परामर्श कन्टॅन्ट प्रकार",\r
+DlgGenLinkCharset      : "लिंक रिसोर्स करॅक्टर सॅट",\r
+DlgGenStyle                    : "स्टाइल",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "तस्वीर प्रॉपर्टीज़",\r
+DlgImgInfoTab          : "तस्वीर की जानकारी",\r
+DlgImgBtnUpload                : "इसे सर्वर को भेजें",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "अपलोड",\r
+DlgImgAlt                      : "वैकल्पिक टेक्स्ट",\r
+DlgImgWidth                    : "चौड़ाई",\r
+DlgImgHeight           : "ऊँचाई",\r
+DlgImgLockRatio                : "लॉक अनुपात",\r
+DlgBtnResetSize                : "रीसॅट साइज़",\r
+DlgImgBorder           : "बॉर्डर",\r
+DlgImgHSpace           : "हॉरिज़ॉन्टल स्पेस",\r
+DlgImgVSpace           : "वर्टिकल स्पेस",\r
+DlgImgAlign                    : "ऍलाइन",\r
+DlgImgAlignLeft                : "दायें",\r
+DlgImgAlignAbsBottom: "Abs नीचे",\r
+DlgImgAlignAbsMiddle: "Abs ऊपर",\r
+DlgImgAlignBaseline    : "मूल रेखा",\r
+DlgImgAlignBottom      : "नीचे",\r
+DlgImgAlignMiddle      : "मध्य",\r
+DlgImgAlignRight       : "दायें",\r
+DlgImgAlignTextTop     : "टेक्स्ट ऊपर",\r
+DlgImgAlignTop         : "ऊपर",\r
+DlgImgPreview          : "प्रीव्यू",\r
+DlgImgAlertUrl         : "तस्वीर का URL टाइप करें ",\r
+DlgImgLinkTab          : "लिंक",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "फ़्लैश प्रॉपर्टीज़",\r
+DlgFlashChkPlay                : "ऑटो प्ले",\r
+DlgFlashChkLoop                : "लूप",\r
+DlgFlashChkMenu                : "फ़्लैश मॅन्यू का प्रयोग करें",\r
+DlgFlashScale          : "स्केल",\r
+DlgFlashScaleAll       : "सभी दिखायें",\r
+DlgFlashScaleNoBorder  : "कोई बॉर्डर नहीं",\r
+DlgFlashScaleFit       : "बिल्कुल फ़िट",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "लिंक",\r
+DlgLnkInfoTab          : "लिंक  ",\r
+DlgLnkTargetTab                : "टार्गेट",\r
+\r
+DlgLnkType                     : "लिंक प्रकार",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "इस पेज का ऐंकर",\r
+DlgLnkTypeEMail                : "ई-मेल",\r
+DlgLnkProto                    : "प्रोटोकॉल",\r
+DlgLnkProtoOther       : "<अन्य>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "ऐंकर चुनें",\r
+DlgLnkAnchorByName     : "ऐंकर नाम से",\r
+DlgLnkAnchorById       : "ऍलीमॅन्ट Id से",\r
+DlgLnkNoAnchors                : "<डॉक्यूमॅन्ट में ऐंकर्स की संख्या>",         //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "ई-मेल पता",\r
+DlgLnkEMailSubject     : "संदेश विषय",\r
+DlgLnkEMailBody                : "संदेश",\r
+DlgLnkUpload           : "अपलोड",\r
+DlgLnkBtnUpload                : "इसे सर्वर को भेजें",\r
+\r
+DlgLnkTarget           : "टार्गेट",\r
+DlgLnkTargetFrame      : "<फ़्रेम>",\r
+DlgLnkTargetPopup      : "<पॉप-अप विन्डो>",\r
+DlgLnkTargetBlank      : "नया विन्डो (_blank)",\r
+DlgLnkTargetParent     : "मूल विन्डो (_parent)",\r
+DlgLnkTargetSelf       : "इसी विन्डो (_self)",\r
+DlgLnkTargetTop                : "शीर्ष विन्डो (_top)",\r
+DlgLnkTargetFrameName  : "टार्गेट फ़्रेम का नाम",\r
+DlgLnkPopWinName       : "पॉप-अप विन्डो का नाम",\r
+DlgLnkPopWinFeat       : "पॉप-अप विन्डो फ़ीचर्स",\r
+DlgLnkPopResize                : "साइज़ बदला जा सकता है",\r
+DlgLnkPopLocation      : "लोकेशन बार",\r
+DlgLnkPopMenu          : "मॅन्यू बार",\r
+DlgLnkPopScroll                : "स्क्रॉल बार",\r
+DlgLnkPopStatus                : "स्टेटस बार",\r
+DlgLnkPopToolbar       : "टूल बार",\r
+DlgLnkPopFullScrn      : "फ़ुल स्क्रीन (IE)",\r
+DlgLnkPopDependent     : "डिपेन्डॅन्ट (Netscape)",\r
+DlgLnkPopWidth         : "चौड़ाई",\r
+DlgLnkPopHeight                : "ऊँचाई",\r
+DlgLnkPopLeft          : "बायीं तरफ",\r
+DlgLnkPopTop           : "दायीं तरफ",\r
+\r
+DlnLnkMsgNoUrl         : "लिंक URL टाइप करें",\r
+DlnLnkMsgNoEMail       : "ई-मेल पता टाइप करें",\r
+DlnLnkMsgNoAnchor      : "ऐंकर चुनें",\r
+DlnLnkMsgInvPopName    : "पॉप-अप का नाम अल्फाबेट से शुरू होना चाहिये और उसमें स्पेस नहीं होने चाहिए",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "रंग चुनें",\r
+DlgColorBtnClear       : "साफ़ करें",\r
+DlgColorHighlight      : "हाइलाइट",\r
+DlgColorSelected       : "सॅलॅक्टॅड",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "स्माइली इन्सर्ट करें",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "विशेष करॅक्टर चुनें",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "टेबल प्रॉपर्टीज़",\r
+DlgTableRows           : "पंक्तियाँ",\r
+DlgTableColumns                : "कॉलम",\r
+DlgTableBorder         : "बॉर्डर साइज़",\r
+DlgTableAlign          : "ऍलाइन्मॅन्ट",\r
+DlgTableAlignNotSet    : "<सॅट नहीं>",\r
+DlgTableAlignLeft      : "दायें",\r
+DlgTableAlignCenter    : "बीच में",\r
+DlgTableAlignRight     : "बायें",\r
+DlgTableWidth          : "चौड़ाई",\r
+DlgTableWidthPx                : "पिक्सॅल",\r
+DlgTableWidthPc                : "प्रतिशत",\r
+DlgTableHeight         : "ऊँचाई",\r
+DlgTableCellSpace      : "सॅल अंतर",\r
+DlgTableCellPad                : "सॅल पैडिंग",\r
+DlgTableCaption                : "शीर्षक",\r
+DlgTableSummary                : "सारांश",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "सॅल प्रॉपर्टीज़",\r
+DlgCellWidth           : "चौड़ाई",\r
+DlgCellWidthPx         : "पिक्सॅल",\r
+DlgCellWidthPc         : "प्रतिशत",\r
+DlgCellHeight          : "ऊँचाई",\r
+DlgCellWordWrap                : "वर्ड रैप",\r
+DlgCellWordWrapNotSet  : "<सॅट नहीं>",\r
+DlgCellWordWrapYes     : "हाँ",\r
+DlgCellWordWrapNo      : "नहीं",\r
+DlgCellHorAlign                : "हॉरिज़ॉन्टल ऍलाइन्मॅन्ट",\r
+DlgCellHorAlignNotSet  : "<सॅट नहीं>",\r
+DlgCellHorAlignLeft    : "दायें",\r
+DlgCellHorAlignCenter  : "बीच में",\r
+DlgCellHorAlignRight: "बायें",\r
+DlgCellVerAlign                : "वर्टिकल ऍलाइन्मॅन्ट",\r
+DlgCellVerAlignNotSet  : "<सॅट नहीं>",\r
+DlgCellVerAlignTop     : "ऊपर",\r
+DlgCellVerAlignMiddle  : "मध्य",\r
+DlgCellVerAlignBottom  : "नीचे",\r
+DlgCellVerAlignBaseline        : "मूलरेखा",\r
+DlgCellRowSpan         : "पंक्ति स्पैन",\r
+DlgCellCollSpan                : "कॉलम स्पैन",\r
+DlgCellBackColor       : "बैक्ग्राउन्ड रंग",\r
+DlgCellBorderColor     : "बॉर्डर का रंग",\r
+DlgCellBtnSelect       : "चुनें...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "खोजें",\r
+DlgFindFindBtn         : "खोजें",\r
+DlgFindNotFoundMsg     : "आपके द्वारा दिया गया टेक्स्ट नहीं मिला",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "रिप्लेस",\r
+DlgReplaceFindLbl              : "यह खोजें:",\r
+DlgReplaceReplaceLbl   : "इससे रिप्लेस करें:",\r
+DlgReplaceCaseChk              : "केस मिलायें",\r
+DlgReplaceReplaceBtn   : "रिप्लेस",\r
+DlgReplaceReplAllBtn   : "सभी रिप्लेस करें",\r
+DlgReplaceWordChk              : "पूरा शब्द मिलायें",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "आपके ब्राउज़र की सुरक्षा सॅटिन्ग्स ने कट करने की अनुमति नहीं प्रदान की है। (Ctrl+X) का प्रयोग करें।",\r
+PasteErrorCopy : "आपके ब्राआउज़र की सुरक्षा सॅटिन्ग्स ने कॉपी करने की अनुमति नहीं प्रदान की है। (Ctrl+C) का प्रयोग करें।",\r
+\r
+PasteAsText            : "पेस्ट (सादा टॅक्स्ट)",\r
+PasteFromWord  : "पेस्ट (वर्ड से)",\r
+\r
+DlgPasteMsg2   : "Ctrl+V का प्रयोग करके पेस्ट करें और ठीक है करें.",\r
+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\r
+DlgPasteIgnoreFont             : "फ़ॉन्ट परिभाषा निकालें",\r
+DlgPasteRemoveStyles   : "स्टाइल परिभाषा निकालें",\r
+DlgPasteCleanBox               : "बॉक्स साफ़ करें",\r
+\r
+// Color Picker\r
+ColorAutomatic : "ऑटोमैटिक",\r
+ColorMoreColors        : "और रंग...",\r
+\r
+// Document Properties\r
+DocProps               : "डॉक्यूमॅन्ट प्रॉपर्टीज़",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "ऐंकर प्रॉपर्टीज़",\r
+DlgAnchorName          : "ऐंकर का नाम",\r
+DlgAnchorErrorName     : "ऐंकर का नाम टाइप करें",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "शब्दकोश में नहीं",\r
+DlgSpellChangeTo               : "इसमें बदलें",\r
+DlgSpellBtnIgnore              : "इग्नोर",\r
+DlgSpellBtnIgnoreAll   : "सभी इग्नोर करें",\r
+DlgSpellBtnReplace             : "रिप्लेस",\r
+DlgSpellBtnReplaceAll  : "सभी रिप्लेस करें",\r
+DlgSpellBtnUndo                        : "अन्डू",\r
+DlgSpellNoSuggestions  : "- कोई सुझाव नहीं -",\r
+DlgSpellProgress               : "वर्तनी की जाँच (स्पॅल-चॅक) जारी है...",\r
+DlgSpellNoMispell              : "वर्तनी की जाँच : कोई गलत वर्तनी (स्पॅलिंग) नहीं पाई गई",\r
+DlgSpellNoChanges              : "वर्तनी की जाँच :कोई शब्द नहीं बदला गया",\r
+DlgSpellOneChange              : "वर्तनी की जाँच : एक शब्द बदला गया",\r
+DlgSpellManyChanges            : "वर्तनी की जाँच : %1 शब्द बदले गये",\r
+\r
+IeSpellDownload                        : "स्पॅल-चॅकर इन्स्टाल नहीं किया गया है। क्या आप इसे डा‌उनलोड करना चाहेंगे?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "टेक्स्ट (वैल्यू)",\r
+DlgButtonType          : "प्रकार",\r
+DlgButtonTypeBtn       : "बटन",\r
+DlgButtonTypeSbm       : "सब्मिट",\r
+DlgButtonTypeRst       : "रिसेट",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "नाम",\r
+DlgCheckboxValue       : "वैल्यू",\r
+DlgCheckboxSelected    : "सॅलॅक्टॅड",\r
+\r
+// Form Dialog\r
+DlgFormName            : "नाम",\r
+DlgFormAction  : "ऍक्शन",\r
+DlgFormMethod  : "तरीका",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "नाम",\r
+DlgSelectValue         : "वैल्यू",\r
+DlgSelectSize          : "साइज़",\r
+DlgSelectLines         : "पंक्तियाँ",\r
+DlgSelectChkMulti      : "एक से ज्यादा विकल्प चुनने दें",\r
+DlgSelectOpAvail       : "उपलब्ध विकल्प",\r
+DlgSelectOpText                : "टेक्स्ट",\r
+DlgSelectOpValue       : "वैल्यू",\r
+DlgSelectBtnAdd                : "जोड़ें",\r
+DlgSelectBtnModify     : "बदलें",\r
+DlgSelectBtnUp         : "ऊपर",\r
+DlgSelectBtnDown       : "नीचे",\r
+DlgSelectBtnSetValue : "चुनी गई वैल्यू सॅट करें",\r
+DlgSelectBtnDelete     : "डिलीट",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "नाम",\r
+DlgTextareaCols        : "कॉलम",\r
+DlgTextareaRows        : "पंक्तियां",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "नाम",\r
+DlgTextValue           : "वैल्यू",\r
+DlgTextCharWidth       : "करॅक्टर की चौढ़ाई",\r
+DlgTextMaxChars                : "अधिकतम करॅक्टर",\r
+DlgTextType                    : "टाइप",\r
+DlgTextTypeText                : "टेक्स्ट",\r
+DlgTextTypePass                : "पास्वर्ड",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "नाम",\r
+DlgHiddenValue : "वैल्यू",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "बुलॅट सूची प्रॉपर्टीज़",\r
+NumberedListProp       : "अंकीय सूची प्रॉपर्टीज़",\r
+DlgLstStart                    : "प्रारम्भ",\r
+DlgLstType                     : "प्रकार",\r
+DlgLstTypeCircle       : "गोल",\r
+DlgLstTypeDisc         : "डिस्क",\r
+DlgLstTypeSquare       : "चौकॊण",\r
+DlgLstTypeNumbers      : "अंक (1, 2, 3)",\r
+DlgLstTypeLCase                : "छोटे अक्षर (a, b, c)",\r
+DlgLstTypeUCase                : "बड़े अक्षर (A, B, C)",\r
+DlgLstTypeSRoman       : "छोटे रोमन अंक (i, ii, iii)",\r
+DlgLstTypeLRoman       : "बड़े रोमन अंक (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "आम",\r
+DlgDocBackTab          : "बैक्ग्राउन्ड",\r
+DlgDocColorsTab                : "रंग और मार्जिन",\r
+DlgDocMetaTab          : "मॅटाडेटा",\r
+\r
+DlgDocPageTitle                : "पेज शीर्षक",\r
+DlgDocLangDir          : "भाषा लिखने की दिशा",\r
+DlgDocLangDirLTR       : "बायें से दायें (LTR)",\r
+DlgDocLangDirRTL       : "दायें से बायें (RTL)",\r
+DlgDocLangCode         : "भाषा कोड",\r
+DlgDocCharSet          : "करेक्टर सॅट ऍन्कोडिंग",\r
+DlgDocCharSetCE                : "मध्य यूरोपीय (Central European)",\r
+DlgDocCharSetCT                : "चीनी (Chinese Traditional Big5)",\r
+DlgDocCharSetCR                : "सिरीलिक (Cyrillic)",\r
+DlgDocCharSetGR                : "यवन (Greek)",\r
+DlgDocCharSetJP                : "जापानी (Japanese)",\r
+DlgDocCharSetKR                : "कोरीयन (Korean)",\r
+DlgDocCharSetTR                : "तुर्की (Turkish)",\r
+DlgDocCharSetUN                : "यूनीकोड (UTF-8)",\r
+DlgDocCharSetWE                : "पश्चिम यूरोपीय (Western European)",\r
+DlgDocCharSetOther     : "अन्य करेक्टर सॅट ऍन्कोडिंग",\r
+\r
+DlgDocDocType          : "डॉक्यूमॅन्ट प्रकार शीर्षक",\r
+DlgDocDocTypeOther     : "अन्य डॉक्यूमॅन्ट प्रकार शीर्षक",\r
+DlgDocIncXHTML         : "XHTML सूचना सम्मिलित करें",\r
+DlgDocBgColor          : "बैक्ग्राउन्ड रंग",\r
+DlgDocBgImage          : "बैक्ग्राउन्ड तस्वीर URL",\r
+DlgDocBgNoScroll       : "स्क्रॉल न करने वाला बैक्ग्राउन्ड",\r
+DlgDocCText                    : "टेक्स्ट",\r
+DlgDocCLink                    : "लिंक",\r
+DlgDocCVisited         : "विज़िट किया गया लिंक",\r
+DlgDocCActive          : "सक्रिय लिंक",\r
+DlgDocMargins          : "पेज मार्जिन",\r
+DlgDocMaTop                    : "ऊपर",\r
+DlgDocMaLeft           : "बायें",\r
+DlgDocMaRight          : "दायें",\r
+DlgDocMaBottom         : "नीचे",\r
+DlgDocMeIndex          : "डॉक्युमॅन्ट इन्डेक्स संकेतशब्द (अल्पविराम से अलग करें)",\r
+DlgDocMeDescr          : "डॉक्यूमॅन्ट करॅक्टरन",\r
+DlgDocMeAuthor         : "लेखक",\r
+DlgDocMeCopy           : "कॉपीराइट",\r
+DlgDocPreview          : "प्रीव्यू",\r
+\r
+// Templates Dialog\r
+Templates                      : "टॅम्प्लेट",\r
+DlgTemplatesTitle      : "कन्टेन्ट टॅम्प्लेट",\r
+DlgTemplatesSelMsg     : "ऍडिटर में ओपन करने हेतु टॅम्प्लेट चुनें(वर्तमान कन्टॅन्ट सेव नहीं होंगे):",\r
+DlgTemplatesLoading    : "टॅम्प्लेट सूची लोड की जा रही है। ज़रा ठहरें...",\r
+DlgTemplatesNoTpl      : "(कोई टॅम्प्लेट डिफ़ाइन नहीं किया गया है)",\r
+DlgTemplatesReplace    : "मूल शब्दों को बदलें",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "FCKEditor के बारे में",\r
+DlgAboutBrowserInfoTab : "ब्राउज़र के बारे में",\r
+DlgAboutLicenseTab     : "लाइसैन्स",\r
+DlgAboutVersion                : "वर्ज़न",\r
+DlgAboutInfo           : "अधिक जानकारी के लिये यहाँ जायें:"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/hr.js b/httemplate/elements/fckeditor/editor/lang/hr.js
new file mode 100644 (file)
index 0000000..f088b10
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Croatian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Smanji trake s alatima",\r
+ToolbarExpand          : "Proširi trake s alatima",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Snimi",\r
+NewPage                                : "Nova stranica",\r
+Preview                                : "Pregledaj",\r
+Cut                                    : "Izreži",\r
+Copy                           : "Kopiraj",\r
+Paste                          : "Zalijepi",\r
+PasteText                      : "Zalijepi kao čisti tekst",\r
+PasteWord                      : "Zalijepi iz Worda",\r
+Print                          : "Ispiši",\r
+SelectAll                      : "Odaberi sve",\r
+RemoveFormat           : "Ukloni formatiranje",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Ubaci/promijeni link",\r
+RemoveLink                     : "Ukloni link",\r
+Anchor                         : "Ubaci/promijeni sidro",\r
+InsertImageLbl         : "Slika",\r
+InsertImage                    : "Ubaci/promijeni sliku",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Ubaci/promijeni Flash",\r
+InsertTableLbl         : "Tablica",\r
+InsertTable                    : "Ubaci/promijeni tablicu",\r
+InsertLineLbl          : "Linija",\r
+InsertLine                     : "Ubaci vodoravnu liniju",\r
+InsertSpecialCharLbl: "Posebni karakteri",\r
+InsertSpecialChar      : "Ubaci posebne znakove",\r
+InsertSmileyLbl                : "Smješko",\r
+InsertSmiley           : "Ubaci smješka",\r
+About                          : "O FCKeditoru",\r
+Bold                           : "Podebljaj",\r
+Italic                         : "Ukosi",\r
+Underline                      : "Potcrtano",\r
+StrikeThrough          : "Precrtano",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Lijevo poravnanje",\r
+CenterJustify          : "Središnje poravnanje",\r
+RightJustify           : "Desno poravnanje",\r
+BlockJustify           : "Blok poravnanje",\r
+DecreaseIndent         : "Pomakni ulijevo",\r
+IncreaseIndent         : "Pomakni udesno",\r
+Undo                           : "Poništi",\r
+Redo                           : "Ponovi",\r
+NumberedListLbl                : "Brojčana lista",\r
+NumberedList           : "Ubaci/ukloni brojčanu listu",\r
+BulletedListLbl                : "Obična lista",\r
+BulletedList           : "Ubaci/ukloni običnu listu",\r
+ShowTableBorders       : "Prikaži okvir tablice",\r
+ShowDetails                    : "Prikaži detalje",\r
+Style                          : "Stil",\r
+FontFormat                     : "Format",\r
+Font                           : "Font",\r
+FontSize                       : "Veličina",\r
+TextColor                      : "Boja teksta",\r
+BGColor                                : "Boja pozadine",\r
+Source                         : "Kôd",\r
+Find                           : "Pronađi",\r
+Replace                                : "Zamijeni",\r
+SpellCheck                     : "Provjeri pravopis",\r
+UniversalKeyboard      : "Univerzalna tipkovnica",\r
+PageBreakLbl           : "Prijelom stranice",\r
+PageBreak                      : "Ubaci prijelom stranice",\r
+\r
+Form                   : "Form",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Radio Button",\r
+TextField              : "Text Field",\r
+Textarea               : "Textarea",\r
+HiddenField            : "Hidden Field",\r
+Button                 : "Button",\r
+SelectionField : "Selection Field",\r
+ImageButton            : "Image Button",\r
+\r
+FitWindow              : "Povećaj veličinu editora",\r
+\r
+// Context Menu\r
+EditLink                       : "Promijeni link",\r
+CellCM                         : "Ćelija",\r
+RowCM                          : "Red",\r
+ColumnCM                       : "Kolona",\r
+InsertRow                      : "Ubaci red",\r
+DeleteRows                     : "Izbriši redove",\r
+InsertColumn           : "Ubaci kolonu",\r
+DeleteColumns          : "Izbriši kolone",\r
+InsertCell                     : "Ubaci ćelije",\r
+DeleteCells                    : "Izbriši ćelije",\r
+MergeCells                     : "Spoji ćelije",\r
+SplitCell                      : "Razdvoji ćelije",\r
+TableDelete                    : "Izbriši tablicu",\r
+CellProperties         : "Svojstva ćelije",\r
+TableProperties                : "Svojstva tablice",\r
+ImageProperties                : "Svojstva slike",\r
+FlashProperties                : "Flash svojstva",\r
+\r
+AnchorProp                     : "Svojstva sidra",\r
+ButtonProp                     : "Image Button svojstva",\r
+CheckboxProp           : "Checkbox svojstva",\r
+HiddenFieldProp                : "Hidden Field svojstva",\r
+RadioButtonProp                : "Radio Button svojstva",\r
+ImageButtonProp                : "Image Button svojstva",\r
+TextFieldProp          : "Text Field svojstva",\r
+SelectionFieldProp     : "Selection svojstva",\r
+TextareaProp           : "Textarea svojstva",\r
+FormProp                       : "Form svojstva",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Obrađujem XHTML. Molimo pričekajte...",\r
+Done                           : "Završio",\r
+PasteWordConfirm       : "Tekst koji želite zalijepiti čini se da je kopiran iz Worda. Želite li prije očistiti tekst?",\r
+NotCompatiblePaste     : "Ova naredba je dostupna samo u Internet Exploreru 5.5 ili novijem. Želite li nastaviti bez čišćenja?",\r
+UnknownToolbarItem     : "Nepoznati član trake s alatima \"%1\"",\r
+UnknownCommand         : "Nepoznata naredba \"%1\"",\r
+NotImplemented         : "Naredba nije implementirana",\r
+UnknownToolbarSet      : "Traka s alatima \"%1\" ne postoji",\r
+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.",\r
+BrowseServerBlocked : "Pretraivač nije moguće otvoriti. Provjerite da li je uključeno blokiranje pop-up prozora.",\r
+DialogBlocked          : "Nije moguće otvoriti novi prozor. Provjerite da li je uključeno blokiranje pop-up prozora.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Poništi",\r
+DlgBtnClose                    : "Zatvori",\r
+DlgBtnBrowseServer     : "Pretraži server",\r
+DlgAdvancedTag         : "Napredno",\r
+DlgOpOther                     : "<Drugo>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Molimo unesite URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nije postavljeno>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Smjer jezika",\r
+DlgGenLangDirLtr       : "S lijeva na desno (LTR)",\r
+DlgGenLangDirRtl       : "S desna na lijevo (RTL)",\r
+DlgGenLangCode         : "Kôd jezika",\r
+DlgGenAccessKey                : "Pristupna tipka",\r
+DlgGenName                     : "Naziv",\r
+DlgGenTabIndex         : "Tab Indeks",\r
+DlgGenLongDescr                : "Dugački opis URL",\r
+DlgGenClass                    : "Stylesheet klase",\r
+DlgGenTitle                    : "Advisory naslov",\r
+DlgGenContType         : "Advisory vrsta sadržaja",\r
+DlgGenLinkCharset      : "Kodna stranica povezanih resursa",\r
+DlgGenStyle                    : "Stil",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Svojstva slika",\r
+DlgImgInfoTab          : "Info slike",\r
+DlgImgBtnUpload                : "Pošalji na server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Pošalji",\r
+DlgImgAlt                      : "Alternativni tekst",\r
+DlgImgWidth                    : "Širina",\r
+DlgImgHeight           : "Visina",\r
+DlgImgLockRatio                : "Zaključaj odnos",\r
+DlgBtnResetSize                : "Obriši veličinu",\r
+DlgImgBorder           : "Okvir",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Poravnaj",\r
+DlgImgAlignLeft                : "Lijevo",\r
+DlgImgAlignAbsBottom: "Abs dolje",\r
+DlgImgAlignAbsMiddle: "Abs sredina",\r
+DlgImgAlignBaseline    : "Bazno",\r
+DlgImgAlignBottom      : "Dolje",\r
+DlgImgAlignMiddle      : "Sredina",\r
+DlgImgAlignRight       : "Desno",\r
+DlgImgAlignTextTop     : "Vrh teksta",\r
+DlgImgAlignTop         : "Vrh",\r
+DlgImgPreview          : "Pregledaj",\r
+DlgImgAlertUrl         : "Unesite URL slike",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash svojstva",\r
+DlgFlashChkPlay                : "Auto Play",\r
+DlgFlashChkLoop                : "Ponavljaj",\r
+DlgFlashChkMenu                : "Omogući Flash izbornik",\r
+DlgFlashScale          : "Omjer",\r
+DlgFlashScaleAll       : "Prikaži sve",\r
+DlgFlashScaleNoBorder  : "Bez okvira",\r
+DlgFlashScaleFit       : "Točna veličina",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Link Info",\r
+DlgLnkTargetTab                : "Meta",\r
+\r
+DlgLnkType                     : "Link vrsta",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Sidro na ovoj stranici",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<drugo>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Odaberi sidro",\r
+DlgLnkAnchorByName     : "Po nazivu sidra",\r
+DlgLnkAnchorById       : "Po Id elementa",\r
+DlgLnkNoAnchors                : "<Nema dostupnih sidra>",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail adresa",\r
+DlgLnkEMailSubject     : "Naslov",\r
+DlgLnkEMailBody                : "Sadržaj poruke",\r
+DlgLnkUpload           : "Pošalji",\r
+DlgLnkBtnUpload                : "Pošalji na server",\r
+\r
+DlgLnkTarget           : "Meta",\r
+DlgLnkTargetFrame      : "<okvir>",\r
+DlgLnkTargetPopup      : "<popup prozor>",\r
+DlgLnkTargetBlank      : "Novi prozor (_blank)",\r
+DlgLnkTargetParent     : "Roditeljski prozor (_parent)",\r
+DlgLnkTargetSelf       : "Isti prozor (_self)",\r
+DlgLnkTargetTop                : "Vršni prozor (_top)",\r
+DlgLnkTargetFrameName  : "Ime ciljnog okvira",\r
+DlgLnkPopWinName       : "Naziv popup prozora",\r
+DlgLnkPopWinFeat       : "Mogućnosti popup prozora",\r
+DlgLnkPopResize                : "Promjenljive veličine",\r
+DlgLnkPopLocation      : "Traka za lokaciju",\r
+DlgLnkPopMenu          : "Izborna traka",\r
+DlgLnkPopScroll                : "Scroll traka",\r
+DlgLnkPopStatus                : "Statusna traka",\r
+DlgLnkPopToolbar       : "Traka s alatima",\r
+DlgLnkPopFullScrn      : "Cijeli ekran (IE)",\r
+DlgLnkPopDependent     : "Ovisno (Netscape)",\r
+DlgLnkPopWidth         : "Širina",\r
+DlgLnkPopHeight                : "Visina",\r
+DlgLnkPopLeft          : "Lijeva pozicija",\r
+DlgLnkPopTop           : "Gornja pozicija",\r
+\r
+DlnLnkMsgNoUrl         : "Molimo upišite URL link",\r
+DlnLnkMsgNoEMail       : "Molimo upišite e-mail adresu",\r
+DlnLnkMsgNoAnchor      : "Molimo odaberite sidro",\r
+DlnLnkMsgInvPopName    : "Ime popup prozora mora početi sa slovom i ne smije sadržavati razmake",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Odaberite boju",\r
+DlgColorBtnClear       : "Obriši",\r
+DlgColorHighlight      : "Osvijetli",\r
+DlgColorSelected       : "Odaberi",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Ubaci smješka",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Odaberite posebni karakter",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Svojstva tablice",\r
+DlgTableRows           : "Redova",\r
+DlgTableColumns                : "Kolona",\r
+DlgTableBorder         : "Veličina okvira",\r
+DlgTableAlign          : "Poravnanje",\r
+DlgTableAlignNotSet    : "<nije postavljeno>",\r
+DlgTableAlignLeft      : "Lijevo",\r
+DlgTableAlignCenter    : "Središnje",\r
+DlgTableAlignRight     : "Desno",\r
+DlgTableWidth          : "Širina",\r
+DlgTableWidthPx                : "piksela",\r
+DlgTableWidthPc                : "postotaka",\r
+DlgTableHeight         : "Visina",\r
+DlgTableCellSpace      : "Prostornost ćelija",\r
+DlgTableCellPad                : "Razmak ćelija",\r
+DlgTableCaption                : "Naslov",\r
+DlgTableSummary                : "Sažetak",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Svojstva ćelije",\r
+DlgCellWidth           : "Širina",\r
+DlgCellWidthPx         : "piksela",\r
+DlgCellWidthPc         : "postotaka",\r
+DlgCellHeight          : "Visina",\r
+DlgCellWordWrap                : "Word Wrap",\r
+DlgCellWordWrapNotSet  : "<nije postavljeno>",\r
+DlgCellWordWrapYes     : "Da",\r
+DlgCellWordWrapNo      : "Ne",\r
+DlgCellHorAlign                : "Vodoravno poravnanje",\r
+DlgCellHorAlignNotSet  : "<nije postavljeno>",\r
+DlgCellHorAlignLeft    : "Lijevo",\r
+DlgCellHorAlignCenter  : "Središnje",\r
+DlgCellHorAlignRight: "Desno",\r
+DlgCellVerAlign                : "Okomito poravnanje",\r
+DlgCellVerAlignNotSet  : "<nije postavljeno>",\r
+DlgCellVerAlignTop     : "Gornje",\r
+DlgCellVerAlignMiddle  : "Srednišnje",\r
+DlgCellVerAlignBottom  : "Donje",\r
+DlgCellVerAlignBaseline        : "Bazno",\r
+DlgCellRowSpan         : "Spajanje redova",\r
+DlgCellCollSpan                : "Spajanje kolona",\r
+DlgCellBackColor       : "Boja pozadine",\r
+DlgCellBorderColor     : "Boja okvira",\r
+DlgCellBtnSelect       : "Odaberi...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Pronađi",\r
+DlgFindFindBtn         : "Pronađi",\r
+DlgFindNotFoundMsg     : "Traženi tekst nije pronađen.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Zamijeni",\r
+DlgReplaceFindLbl              : "Pronađi:",\r
+DlgReplaceReplaceLbl   : "Zamijeni s:",\r
+DlgReplaceCaseChk              : "Usporedi mala/velika slova",\r
+DlgReplaceReplaceBtn   : "Zamijeni",\r
+DlgReplaceReplAllBtn   : "Zamijeni sve",\r
+DlgReplaceWordChk              : "Usporedi cijele riječi",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Sigurnosne postavke Vašeg pretraživača ne dozvoljavaju operacije automatskog izrezivanja. Molimo koristite kraticu na tipkovnici (Ctrl+X).",\r
+PasteErrorCopy : "Sigurnosne postavke Vašeg pretraživača ne dozvoljavaju operacije automatskog kopiranja. Molimo koristite kraticu na tipkovnici (Ctrl+C).",\r
+\r
+PasteAsText            : "Zalijepi kao čisti tekst",\r
+PasteFromWord  : "Zalijepi iz Worda",\r
+\r
+DlgPasteMsg2   : "Molimo zaljepite unutar doljnjeg okvira koristeći tipkovnicu (<STRONG>Ctrl+V</STRONG>) i kliknite <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Zanemari definiciju vrste fonta",\r
+DlgPasteRemoveStyles   : "Ukloni definicije stilova",\r
+DlgPasteCleanBox               : "Očisti okvir",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatski",\r
+ColorMoreColors        : "Više boja...",\r
+\r
+// Document Properties\r
+DocProps               : "Svojstva dokumenta",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Svojstva sidra",\r
+DlgAnchorName          : "Ime sidra",\r
+DlgAnchorErrorName     : "Molimo unesite ime sidra",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Nije u rječniku",\r
+DlgSpellChangeTo               : "Promijeni u",\r
+DlgSpellBtnIgnore              : "Zanemari",\r
+DlgSpellBtnIgnoreAll   : "Zanemari sve",\r
+DlgSpellBtnReplace             : "Zamijeni",\r
+DlgSpellBtnReplaceAll  : "Zamijeni sve",\r
+DlgSpellBtnUndo                        : "Vrati",\r
+DlgSpellNoSuggestions  : "-Nema preporuke-",\r
+DlgSpellProgress               : "Provjera u tijeku...",\r
+DlgSpellNoMispell              : "Provjera završena: Nema grešaka",\r
+DlgSpellNoChanges              : "Provjera završena: Nije napravljena promjena",\r
+DlgSpellOneChange              : "Provjera završena: Jedna riječ promjenjena",\r
+DlgSpellManyChanges            : "Provjera završena: Promijenjeno %1 riječi",\r
+\r
+IeSpellDownload                        : "Provjera pravopisa nije instalirana. Želite li skinuti provjeru pravopisa?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekst (vrijednost)",\r
+DlgButtonType          : "Vrsta",\r
+DlgButtonTypeBtn       : "Gumb",\r
+DlgButtonTypeSbm       : "Pošalji",\r
+DlgButtonTypeRst       : "Poništi",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Ime",\r
+DlgCheckboxValue       : "Vrijednost",\r
+DlgCheckboxSelected    : "Odabrano",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Ime",\r
+DlgFormAction  : "Akcija",\r
+DlgFormMethod  : "Metoda",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Ime",\r
+DlgSelectValue         : "Vrijednost",\r
+DlgSelectSize          : "Veličina",\r
+DlgSelectLines         : "linija",\r
+DlgSelectChkMulti      : "Dozvoli višestruki odabir",\r
+DlgSelectOpAvail       : "Dostupne opcije",\r
+DlgSelectOpText                : "Tekst",\r
+DlgSelectOpValue       : "Vrijednost",\r
+DlgSelectBtnAdd                : "Dodaj",\r
+DlgSelectBtnModify     : "Promijeni",\r
+DlgSelectBtnUp         : "Gore",\r
+DlgSelectBtnDown       : "Dolje",\r
+DlgSelectBtnSetValue : "Postavi kao odabranu vrijednost",\r
+DlgSelectBtnDelete     : "Obriši",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Ime",\r
+DlgTextareaCols        : "Kolona",\r
+DlgTextareaRows        : "Redova",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Ime",\r
+DlgTextValue           : "Vrijednost",\r
+DlgTextCharWidth       : "Širina",\r
+DlgTextMaxChars                : "Najviše karaktera",\r
+DlgTextType                    : "Vrsta",\r
+DlgTextTypeText                : "Tekst",\r
+DlgTextTypePass                : "Šifra",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Ime",\r
+DlgHiddenValue : "Vrijednost",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Svojstva liste",\r
+NumberedListProp       : "Svojstva brojčane liste",\r
+DlgLstStart                    : "Početak",\r
+DlgLstType                     : "Vrsta",\r
+DlgLstTypeCircle       : "Krug",\r
+DlgLstTypeDisc         : "Disk",\r
+DlgLstTypeSquare       : "Kvadrat",\r
+DlgLstTypeNumbers      : "Brojevi (1, 2, 3)",\r
+DlgLstTypeLCase                : "Mala slova (a, b, c)",\r
+DlgLstTypeUCase                : "Velika slova (A, B, C)",\r
+DlgLstTypeSRoman       : "Male rimske brojke (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Velike rimske brojke (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Općenito",\r
+DlgDocBackTab          : "Pozadina",\r
+DlgDocColorsTab                : "Boje i margine",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Naslov stranice",\r
+DlgDocLangDir          : "Smjer jezika",\r
+DlgDocLangDirLTR       : "S lijeva na desno",\r
+DlgDocLangDirRTL       : "S desna na lijevo",\r
+DlgDocLangCode         : "Kôd jezika",\r
+DlgDocCharSet          : "Enkodiranje znakova",\r
+DlgDocCharSetCE                : "Središnja Europa",\r
+DlgDocCharSetCT                : "Tradicionalna kineska (Big5)",\r
+DlgDocCharSetCR                : "Ćirilica",\r
+DlgDocCharSetGR                : "Grčka",\r
+DlgDocCharSetJP                : "Japanska",\r
+DlgDocCharSetKR                : "Koreanska",\r
+DlgDocCharSetTR                : "Turska",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Zapadna Europa",\r
+DlgDocCharSetOther     : "Ostalo enkodiranje znakova",\r
+\r
+DlgDocDocType          : "Zaglavlje vrste dokumenta",\r
+DlgDocDocTypeOther     : "Ostalo zaglavlje vrste dokumenta",\r
+DlgDocIncXHTML         : "Ubaci XHTML deklaracije",\r
+DlgDocBgColor          : "Boja pozadine",\r
+DlgDocBgImage          : "URL slike pozadine",\r
+DlgDocBgNoScroll       : "Pozadine se ne pomiče",\r
+DlgDocCText                    : "Tekst",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Posjećeni link",\r
+DlgDocCActive          : "Aktivni link",\r
+DlgDocMargins          : "Margine stranice",\r
+DlgDocMaTop                    : "Vrh",\r
+DlgDocMaLeft           : "Lijevo",\r
+DlgDocMaRight          : "Desno",\r
+DlgDocMaBottom         : "Dolje",\r
+DlgDocMeIndex          : "Ključne riječi dokumenta (odvojene zarezom)",\r
+DlgDocMeDescr          : "Opis dokumenta",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Autorska prava",\r
+DlgDocPreview          : "Pregledaj",\r
+\r
+// Templates Dialog\r
+Templates                      : "Predlošci",\r
+DlgTemplatesTitle      : "Predlošci sadržaja",\r
+DlgTemplatesSelMsg     : "Molimo odaberite predložak koji želite otvoriti<br>(stvarni sadržaj će biti izgubljen):",\r
+DlgTemplatesLoading    : "Učitavam listu predložaka. Molimo pričekajte...",\r
+DlgTemplatesNoTpl      : "(Nema definiranih predložaka)",\r
+DlgTemplatesReplace    : "Zamijeni trenutne sadržaje",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "O FCKEditoru",\r
+DlgAboutBrowserInfoTab : "Podaci o pretraživaču",\r
+DlgAboutLicenseTab     : "Licenca",\r
+DlgAboutVersion                : "inačica",\r
+DlgAboutInfo           : "Za više informacija posjetite"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/hu.js b/httemplate/elements/fckeditor/editor/lang/hu.js
new file mode 100644 (file)
index 0000000..73b912c
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Hungarian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Eszköztár elrejtése",\r
+ToolbarExpand          : "Eszköztár megjelenítése",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Mentés",\r
+NewPage                                : "Új oldal",\r
+Preview                                : "Előnézet",\r
+Cut                                    : "Kivágás",\r
+Copy                           : "Másolás",\r
+Paste                          : "Beillesztés",\r
+PasteText                      : "Beillesztés formázás nélkül",\r
+PasteWord                      : "Beillesztés Word-ből",\r
+Print                          : "Nyomtatás",\r
+SelectAll                      : "Mindent kijelöl",\r
+RemoveFormat           : "Formázás eltávolítása",\r
+InsertLinkLbl          : "Hivatkozás",\r
+InsertLink                     : "Hivatkozás beillesztése/módosítása",\r
+RemoveLink                     : "Hivatkozás törlése",\r
+Anchor                         : "Horgony beillesztése/szerkesztése",\r
+InsertImageLbl         : "Kép",\r
+InsertImage                    : "Kép beillesztése/módosítása",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Flash beillesztése, módosítása",\r
+InsertTableLbl         : "Táblázat",\r
+InsertTable                    : "Táblázat beillesztése/módosítása",\r
+InsertLineLbl          : "Vonal",\r
+InsertLine                     : "Elválasztóvonal beillesztése",\r
+InsertSpecialCharLbl: "Speciális karakter",\r
+InsertSpecialChar      : "Speciális karakter beillesztése",\r
+InsertSmileyLbl                : "Hangulatjelek",\r
+InsertSmiley           : "Hangulatjelek beillesztése",\r
+About                          : "FCKeditor névjegy",\r
+Bold                           : "Félkövér",\r
+Italic                         : "Dőlt",\r
+Underline                      : "Aláhúzott",\r
+StrikeThrough          : "Áthúzott",\r
+Subscript                      : "Alsó index",\r
+Superscript                    : "Felső index",\r
+LeftJustify                    : "Balra",\r
+CenterJustify          : "Középre",\r
+RightJustify           : "Jobbra",\r
+BlockJustify           : "Sorkizárt",\r
+DecreaseIndent         : "Behúzás csökkentése",\r
+IncreaseIndent         : "Behúzás növelése",\r
+Undo                           : "Visszavonás",\r
+Redo                           : "Ismétlés",\r
+NumberedListLbl                : "Számozás",\r
+NumberedList           : "Számozás beillesztése/törlése",\r
+BulletedListLbl                : "Felsorolás",\r
+BulletedList           : "Felsorolás beillesztése/törlése",\r
+ShowTableBorders       : "Táblázat szegély mutatása",\r
+ShowDetails                    : "Részletek mutatása",\r
+Style                          : "Stílus",\r
+FontFormat                     : "Formátum",\r
+Font                           : "Betűtípus",\r
+FontSize                       : "Méret",\r
+TextColor                      : "Betűszín",\r
+BGColor                                : "Háttérszín",\r
+Source                         : "Forráskód",\r
+Find                           : "Keresés",\r
+Replace                                : "Csere",\r
+SpellCheck                     : "Helyesírás-ellenőrzés",\r
+UniversalKeyboard      : "Univerzális billentyűzet",\r
+PageBreakLbl           : "Oldaltörés",\r
+PageBreak                      : "Oldaltörés beillesztése",\r
+\r
+Form                   : "Űrlap",\r
+Checkbox               : "Jelölőnégyzet",\r
+RadioButton            : "Választógomb",\r
+TextField              : "Szövegmező",\r
+Textarea               : "Szövegterület",\r
+HiddenField            : "Rejtettmező",\r
+Button                 : "Gomb",\r
+SelectionField : "Legördülő lista",\r
+ImageButton            : "Képgomb",\r
+\r
+FitWindow              : "Maximalizálás",\r
+\r
+// Context Menu\r
+EditLink                       : "Hivatkozás módosítása",\r
+CellCM                         : "Cella",\r
+RowCM                          : "Sor",\r
+ColumnCM                       : "Oszlop",\r
+InsertRow                      : "Sor beszúrása",\r
+DeleteRows                     : "Sorok törlése",\r
+InsertColumn           : "Oszlop beszúrása",\r
+DeleteColumns          : "Oszlopok törlése",\r
+InsertCell                     : "Cella beszúrása",\r
+DeleteCells                    : "Cellák törlése",\r
+MergeCells                     : "Cellák egyesítése",\r
+SplitCell                      : "Cella szétválasztása",\r
+TableDelete                    : "Táblázat törlése",\r
+CellProperties         : "Cella tulajdonságai",\r
+TableProperties                : "Táblázat tulajdonságai",\r
+ImageProperties                : "Kép tulajdonságai",\r
+FlashProperties                : "Flash tulajdonságai",\r
+\r
+AnchorProp                     : "Horgony tulajdonságai",\r
+ButtonProp                     : "Gomb tulajdonságai",\r
+CheckboxProp           : "Jelölőnégyzet tulajdonságai",\r
+HiddenFieldProp                : "Rejtett mező tulajdonságai",\r
+RadioButtonProp                : "Választógomb tulajdonságai",\r
+ImageButtonProp                : "Képgomb tulajdonságai",\r
+TextFieldProp          : "Szövegmező tulajdonságai",\r
+SelectionFieldProp     : "Legördülő lista tulajdonságai",\r
+TextareaProp           : "Szövegterület tulajdonságai",\r
+FormProp                       : "Űrlap tulajdonságai",\r
+\r
+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)",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML feldolgozása. Kérem várjon...",\r
+Done                           : "Kész",\r
+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?",\r
+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?",\r
+UnknownToolbarItem     : "Ismeretlen eszköztár elem \"%1\"",\r
+UnknownCommand         : "Ismeretlen parancs \"%1\"",\r
+NotImplemented         : "A parancs nem hajtható végre",\r
+UnknownToolbarSet      : "Az eszközkészlet \"%1\" nem létezik",\r
+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.",\r
+BrowseServerBlocked : "Nem lehet megnyitni a fájlböngészőt. Bizonyosodjon meg róla, hogy a felbukkanó ablakok engedélyezve vannak.",\r
+DialogBlocked          : "Nem lehet megnyitni a párbeszédablakot. Bizonyosodjon meg róla, hogy a felbukkanó ablakok engedélyezve vannak.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "Rendben",\r
+DlgBtnCancel           : "Mégsem",\r
+DlgBtnClose                    : "Bezárás",\r
+DlgBtnBrowseServer     : "Böngészés a szerveren",\r
+DlgAdvancedTag         : "További opciók",\r
+DlgOpOther                     : "Egyéb",\r
+DlgInfoTab                     : "Alaptulajdonságok",\r
+DlgAlertUrl                    : "Illessze be a webcímet",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nincs beállítva>",\r
+DlgGenId                       : "Azonosító",\r
+DlgGenLangDir          : "Írás iránya",\r
+DlgGenLangDirLtr       : "Balról jobbra",\r
+DlgGenLangDirRtl       : "Jobbról balra",\r
+DlgGenLangCode         : "Nyelv kódja",\r
+DlgGenAccessKey                : "Billentyűkombináció",\r
+DlgGenName                     : "Név",\r
+DlgGenTabIndex         : "Tabulátor index",\r
+DlgGenLongDescr                : "Részletes leírás webcíme",\r
+DlgGenClass                    : "Stíluskészlet",\r
+DlgGenTitle                    : "Súgócimke",\r
+DlgGenContType         : "Súgó tartalomtípusa",\r
+DlgGenLinkCharset      : "Hivatkozott tartalom kódlapja",\r
+DlgGenStyle                    : "Stílus",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Kép tulajdonságai",\r
+DlgImgInfoTab          : "Alaptulajdonságok",\r
+DlgImgBtnUpload                : "Küldés a szerverre",\r
+DlgImgURL                      : "Hivatkozás",\r
+DlgImgUpload           : "Feltöltés",\r
+DlgImgAlt                      : "Buborék szöveg",\r
+DlgImgWidth                    : "Szélesség",\r
+DlgImgHeight           : "Magasság",\r
+DlgImgLockRatio                : "Arány megtartása",\r
+DlgBtnResetSize                : "Eredeti méret",\r
+DlgImgBorder           : "Keret",\r
+DlgImgHSpace           : "Vízsz. táv",\r
+DlgImgVSpace           : "Függ. táv",\r
+DlgImgAlign                    : "Igazítás",\r
+DlgImgAlignLeft                : "Bal",\r
+DlgImgAlignAbsBottom: "Legaljára",\r
+DlgImgAlignAbsMiddle: "Közepére",\r
+DlgImgAlignBaseline    : "Alapvonalhoz",\r
+DlgImgAlignBottom      : "Aljára",\r
+DlgImgAlignMiddle      : "Középre",\r
+DlgImgAlignRight       : "Jobbra",\r
+DlgImgAlignTextTop     : "Szöveg tetejére",\r
+DlgImgAlignTop         : "Tetejére",\r
+DlgImgPreview          : "Előnézet",\r
+DlgImgAlertUrl         : "Töltse ki a kép webcímét",\r
+DlgImgLinkTab          : "Hivatkozás",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash tulajdonságai",\r
+DlgFlashChkPlay                : "Automata lejátszás",\r
+DlgFlashChkLoop                : "Folyamatosan",\r
+DlgFlashChkMenu                : "Flash menü engedélyezése",\r
+DlgFlashScale          : "Méretezés",\r
+DlgFlashScaleAll       : "Mindent mutat",\r
+DlgFlashScaleNoBorder  : "Keret nélkül",\r
+DlgFlashScaleFit       : "Teljes kitöltés",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Hivatkozás tulajdonságai",\r
+DlgLnkInfoTab          : "Alaptulajdonságok",\r
+DlgLnkTargetTab                : "Megjelenítés",\r
+\r
+DlgLnkType                     : "Hivatkozás típusa",\r
+DlgLnkTypeURL          : "Webcím",\r
+DlgLnkTypeAnchor       : "Horgony az oldalon",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokoll",\r
+DlgLnkProtoOther       : "<más>",\r
+DlgLnkURL                      : "Webcím",\r
+DlgLnkAnchorSel                : "Horgony választása",\r
+DlgLnkAnchorByName     : "Horgony név szerint",\r
+DlgLnkAnchorById       : "Azonosító szerint",\r
+DlgLnkNoAnchors                : "<Nincs horgony a dokumentumban>",            //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail cím",\r
+DlgLnkEMailSubject     : "Üzenet tárgya",\r
+DlgLnkEMailBody                : "Üzenet",\r
+DlgLnkUpload           : "Feltöltés",\r
+DlgLnkBtnUpload                : "Küldés a szerverre",\r
+\r
+DlgLnkTarget           : "Tartalom megjelenítése",\r
+DlgLnkTargetFrame      : "<keretben>",\r
+DlgLnkTargetPopup      : "<felugró ablakban>",\r
+DlgLnkTargetBlank      : "Új ablakban (_blank)",\r
+DlgLnkTargetParent     : "Szülő ablakban (_parent)",\r
+DlgLnkTargetSelf       : "Azonos ablakban (_self)",\r
+DlgLnkTargetTop                : "Legfelső ablakban (_top)",\r
+DlgLnkTargetFrameName  : "Keret neve",\r
+DlgLnkPopWinName       : "Felugró ablak neve",\r
+DlgLnkPopWinFeat       : "Felugró ablak jellemzői",\r
+DlgLnkPopResize                : "Méretezhető",\r
+DlgLnkPopLocation      : "Címsor",\r
+DlgLnkPopMenu          : "Menü sor",\r
+DlgLnkPopScroll                : "Gördítősáv",\r
+DlgLnkPopStatus                : "Állapotsor",\r
+DlgLnkPopToolbar       : "Eszköztár",\r
+DlgLnkPopFullScrn      : "Teljes képernyő (csak IE)",\r
+DlgLnkPopDependent     : "Szülőhöz kapcsolt (csak Netscape)",\r
+DlgLnkPopWidth         : "Szélesség",\r
+DlgLnkPopHeight                : "Magasság",\r
+DlgLnkPopLeft          : "Bal pozíció",\r
+DlgLnkPopTop           : "Felső pozíció",\r
+\r
+DlnLnkMsgNoUrl         : "Adja meg a hivatkozás webcímét",\r
+DlnLnkMsgNoEMail       : "Adja meg az E-Mail címet",\r
+DlnLnkMsgNoAnchor      : "Válasszon egy horgonyt",\r
+DlnLnkMsgInvPopName    : "A felbukkanó ablak neve alfanumerikus karakterrel kezdôdjön, valamint ne tartalmazzon szóközt",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Színválasztás",\r
+DlgColorBtnClear       : "Törlés",\r
+DlgColorHighlight      : "Előnézet",\r
+DlgColorSelected       : "Kiválasztott",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Hangulatjel beszúrása",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Speciális karakter választása",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Táblázat tulajdonságai",\r
+DlgTableRows           : "Sorok",\r
+DlgTableColumns                : "Oszlopok",\r
+DlgTableBorder         : "Szegélyméret",\r
+DlgTableAlign          : "Igazítás",\r
+DlgTableAlignNotSet    : "<Nincs beállítva>",\r
+DlgTableAlignLeft      : "Balra",\r
+DlgTableAlignCenter    : "Középre",\r
+DlgTableAlignRight     : "Jobbra",\r
+DlgTableWidth          : "Szélesség",\r
+DlgTableWidthPx                : "képpont",\r
+DlgTableWidthPc                : "százalék",\r
+DlgTableHeight         : "Magasság",\r
+DlgTableCellSpace      : "Cella térköz",\r
+DlgTableCellPad                : "Cella belső margó",\r
+DlgTableCaption                : "Felirat",\r
+DlgTableSummary                : "Leírás",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Cella tulajdonságai",\r
+DlgCellWidth           : "Szélesség",\r
+DlgCellWidthPx         : "képpont",\r
+DlgCellWidthPc         : "százalék",\r
+DlgCellHeight          : "Magasság",\r
+DlgCellWordWrap                : "Sortörés",\r
+DlgCellWordWrapNotSet  : "<Nincs beállítva>",\r
+DlgCellWordWrapYes     : "Igen",\r
+DlgCellWordWrapNo      : "Nem",\r
+DlgCellHorAlign                : "Vízsz. igazítás",\r
+DlgCellHorAlignNotSet  : "<Nincs beállítva>",\r
+DlgCellHorAlignLeft    : "Balra",\r
+DlgCellHorAlignCenter  : "Középre",\r
+DlgCellHorAlignRight: "Jobbra",\r
+DlgCellVerAlign                : "Függ. igazítás",\r
+DlgCellVerAlignNotSet  : "<Nincs beállítva>",\r
+DlgCellVerAlignTop     : "Tetejére",\r
+DlgCellVerAlignMiddle  : "Középre",\r
+DlgCellVerAlignBottom  : "Aljára",\r
+DlgCellVerAlignBaseline        : "Egyvonalba",\r
+DlgCellRowSpan         : "Sorok egyesítése",\r
+DlgCellCollSpan                : "Oszlopok egyesítése",\r
+DlgCellBackColor       : "Háttérszín",\r
+DlgCellBorderColor     : "Szegélyszín",\r
+DlgCellBtnSelect       : "Kiválasztás...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Keresés",\r
+DlgFindFindBtn         : "Keresés",\r
+DlgFindNotFoundMsg     : "A keresett szöveg nem található.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Csere",\r
+DlgReplaceFindLbl              : "Keresett szöveg:",\r
+DlgReplaceReplaceLbl   : "Csere erre:",\r
+DlgReplaceCaseChk              : "kis- és nagybetű megkülönböztetése",\r
+DlgReplaceReplaceBtn   : "Csere",\r
+DlgReplaceReplAllBtn   : "Az összes cseréje",\r
+DlgReplaceWordChk              : "csak ha ez a teljes szó",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Beillesztés formázatlan szövegként",\r
+PasteFromWord  : "Beillesztés Word-ből",\r
+\r
+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.",\r
+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\r
+DlgPasteIgnoreFont             : "Betű formázások megszüntetése",\r
+DlgPasteRemoveStyles   : "Stílusok eltávolítása",\r
+DlgPasteCleanBox               : "Törlés",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatikus",\r
+ColorMoreColors        : "További színek...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumentum tulajdonságai",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Horgony tulajdonságai",\r
+DlgAnchorName          : "Horgony neve",\r
+DlgAnchorErrorName     : "Kérem adja meg a horgony nevét",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Nincs a szótárban",\r
+DlgSpellChangeTo               : "Módosítás",\r
+DlgSpellBtnIgnore              : "Kihagyja",\r
+DlgSpellBtnIgnoreAll   : "Mindet kihagyja",\r
+DlgSpellBtnReplace             : "Csere",\r
+DlgSpellBtnReplaceAll  : "Összes cseréje",\r
+DlgSpellBtnUndo                        : "Visszavonás",\r
+DlgSpellNoSuggestions  : "Nincs javaslat",\r
+DlgSpellProgress               : "Helyesírás-ellenőrzés folyamatban...",\r
+DlgSpellNoMispell              : "Helyesírás-ellenőrzés kész: Nem találtam hibát",\r
+DlgSpellNoChanges              : "Helyesírás-ellenőrzés kész: Nincs változtatott szó",\r
+DlgSpellOneChange              : "Helyesírás-ellenőrzés kész: Egy szó cserélve",\r
+DlgSpellManyChanges            : "Helyesírás-ellenőrzés kész: %1 szó cserélve",\r
+\r
+IeSpellDownload                        : "A helyesírás-ellenőrző nincs telepítve. Szeretné letölteni most?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Szöveg (Érték)",\r
+DlgButtonType          : "Típus",\r
+DlgButtonTypeBtn       : "Gomb",\r
+DlgButtonTypeSbm       : "Küldés",\r
+DlgButtonTypeRst       : "Alaphelyzet",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Név",\r
+DlgCheckboxValue       : "Érték",\r
+DlgCheckboxSelected    : "Kiválasztott",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Név",\r
+DlgFormAction  : "Adatfeldolgozást végző hivatkozás",\r
+DlgFormMethod  : "Adatküldés módja",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Név",\r
+DlgSelectValue         : "Érték",\r
+DlgSelectSize          : "Méret",\r
+DlgSelectLines         : "sor",\r
+DlgSelectChkMulti      : "több sor is kiválasztható",\r
+DlgSelectOpAvail       : "Elérhető opciók",\r
+DlgSelectOpText                : "Szöveg",\r
+DlgSelectOpValue       : "Érték",\r
+DlgSelectBtnAdd                : "Hozzáad",\r
+DlgSelectBtnModify     : "Módosít",\r
+DlgSelectBtnUp         : "Fel",\r
+DlgSelectBtnDown       : "Le",\r
+DlgSelectBtnSetValue : "Legyen az alapértelmezett érték",\r
+DlgSelectBtnDelete     : "Töröl",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Név",\r
+DlgTextareaCols        : "Karakterek száma egy sorban",\r
+DlgTextareaRows        : "Sorok száma",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Név",\r
+DlgTextValue           : "Érték",\r
+DlgTextCharWidth       : "Megjelenített karakterek száma",\r
+DlgTextMaxChars                : "Maximális karakterszám",\r
+DlgTextType                    : "Típus",\r
+DlgTextTypeText                : "Szöveg",\r
+DlgTextTypePass                : "Jelszó",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Név",\r
+DlgHiddenValue : "Érték",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Felsorolás tulajdonságai",\r
+NumberedListProp       : "Számozás tulajdonságai",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Formátum",\r
+DlgLstTypeCircle       : "Kör",\r
+DlgLstTypeDisc         : "Lemez",\r
+DlgLstTypeSquare       : "Négyzet",\r
+DlgLstTypeNumbers      : "Számok (1, 2, 3)",\r
+DlgLstTypeLCase                : "Kisbetűk (a, b, c)",\r
+DlgLstTypeUCase                : "Nagybetűk (A, B, C)",\r
+DlgLstTypeSRoman       : "Kis római számok (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Nagy római számok (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Általános",\r
+DlgDocBackTab          : "Háttér",\r
+DlgDocColorsTab                : "Színek és margók",\r
+DlgDocMetaTab          : "Meta adatok",\r
+\r
+DlgDocPageTitle                : "Oldalcím",\r
+DlgDocLangDir          : "Írás iránya",\r
+DlgDocLangDirLTR       : "Balról jobbra",\r
+DlgDocLangDirRTL       : "Jobbról balra",\r
+DlgDocLangCode         : "Nyelv kód",\r
+DlgDocCharSet          : "Karakterkódolás",\r
+DlgDocCharSetCE                : "Közép-Európai",\r
+DlgDocCharSetCT                : "Kínai Tradicionális (Big5)",\r
+DlgDocCharSetCR                : "Cyrill",\r
+DlgDocCharSetGR                : "Görög",\r
+DlgDocCharSetJP                : "Japán",\r
+DlgDocCharSetKR                : "Koreai",\r
+DlgDocCharSetTR                : "Török",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Nyugat-Európai",\r
+DlgDocCharSetOther     : "Más karakterkódolás",\r
+\r
+DlgDocDocType          : "Dokumentum típus fejléc",\r
+DlgDocDocTypeOther     : "Más dokumentum típus fejléc",\r
+DlgDocIncXHTML         : "XHTML deklarációk beillesztése",\r
+DlgDocBgColor          : "Háttérszín",\r
+DlgDocBgImage          : "Háttérkép cím",\r
+DlgDocBgNoScroll       : "Nem gördíthető háttér",\r
+DlgDocCText                    : "Szöveg",\r
+DlgDocCLink                    : "Cím",\r
+DlgDocCVisited         : "Látogatott cím",\r
+DlgDocCActive          : "Aktív cím",\r
+DlgDocMargins          : "Oldal margók",\r
+DlgDocMaTop                    : "Felső",\r
+DlgDocMaLeft           : "Bal",\r
+DlgDocMaRight          : "Jobb",\r
+DlgDocMaBottom         : "Alsó",\r
+DlgDocMeIndex          : "Dokumentum keresőszavak (vesszővel elválasztva)",\r
+DlgDocMeDescr          : "Dokumentum leírás",\r
+DlgDocMeAuthor         : "Szerző",\r
+DlgDocMeCopy           : "Szerzői jog",\r
+DlgDocPreview          : "Előnézet",\r
+\r
+// Templates Dialog\r
+Templates                      : "Sablonok",\r
+DlgTemplatesTitle      : "Elérhető sablonok",\r
+DlgTemplatesSelMsg     : "Válassza ki melyik sablon nyíljon meg a szerkesztőben<br>(a jelenlegi tartalom elveszik):",\r
+DlgTemplatesLoading    : "Sablon lista betöltése. Kis türelmet...",\r
+DlgTemplatesNoTpl      : "(Nincs sablon megadva)",\r
+DlgTemplatesReplace    : "Kicseréli a jelenlegi tartalmat",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Névjegy",\r
+DlgAboutBrowserInfoTab : "Böngésző információ",\r
+DlgAboutLicenseTab     : "Licensz",\r
+DlgAboutVersion                : "verzió",\r
+DlgAboutInfo           : "További információkért látogasson el ide:"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/it.js b/httemplate/elements/fckeditor/editor/lang/it.js
new file mode 100644 (file)
index 0000000..a3dee1b
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Italian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Nascondi la barra degli strumenti",\r
+ToolbarExpand          : "Mostra la barra degli strumenti",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Salva",\r
+NewPage                                : "Nuova pagina vuota",\r
+Preview                                : "Anteprima",\r
+Cut                                    : "Taglia",\r
+Copy                           : "Copia",\r
+Paste                          : "Incolla",\r
+PasteText                      : "Incolla come testo semplice",\r
+PasteWord                      : "Incolla da Word",\r
+Print                          : "Stampa",\r
+SelectAll                      : "Seleziona tutto",\r
+RemoveFormat           : "Elimina formattazione",\r
+InsertLinkLbl          : "Collegamento",\r
+InsertLink                     : "Inserisci/Modifica collegamento",\r
+RemoveLink                     : "Elimina collegamento",\r
+Anchor                         : "Inserisci/Modifica Ancora",\r
+InsertImageLbl         : "Immagine",\r
+InsertImage                    : "Inserisci/Modifica immagine",\r
+InsertFlashLbl         : "Oggetto Flash",\r
+InsertFlash                    : "Inserisci/Modifica Oggetto Flash",\r
+InsertTableLbl         : "Tabella",\r
+InsertTable                    : "Inserisci/Modifica tabella",\r
+InsertLineLbl          : "Riga orizzontale",\r
+InsertLine                     : "Inserisci riga orizzontale",\r
+InsertSpecialCharLbl: "Caratteri speciali",\r
+InsertSpecialChar      : "Inserisci carattere speciale",\r
+InsertSmileyLbl                : "Emoticon",\r
+InsertSmiley           : "Inserisci emoticon",\r
+About                          : "Informazioni su FCKeditor",\r
+Bold                           : "Grassetto",\r
+Italic                         : "Corsivo",\r
+Underline                      : "Sottolineato",\r
+StrikeThrough          : "Barrato",\r
+Subscript                      : "Pedice",\r
+Superscript                    : "Apice",\r
+LeftJustify                    : "Allinea a sinistra",\r
+CenterJustify          : "Centra",\r
+RightJustify           : "Allinea a destra",\r
+BlockJustify           : "Giustifica",\r
+DecreaseIndent         : "Riduci rientro",\r
+IncreaseIndent         : "Aumenta rientro",\r
+Undo                           : "Annulla",\r
+Redo                           : "Ripristina",\r
+NumberedListLbl                : "Elenco numerato",\r
+NumberedList           : "Inserisci/Modifica elenco numerato",\r
+BulletedListLbl                : "Elenco puntato",\r
+BulletedList           : "Inserisci/Modifica elenco puntato",\r
+ShowTableBorders       : "Mostra bordi tabelle",\r
+ShowDetails                    : "Mostra dettagli",\r
+Style                          : "Stile",\r
+FontFormat                     : "Formato",\r
+Font                           : "Font",\r
+FontSize                       : "Dimensione",\r
+TextColor                      : "Colore testo",\r
+BGColor                                : "Colore sfondo",\r
+Source                         : "Codice Sorgente",\r
+Find                           : "Trova",\r
+Replace                                : "Sostituisci",\r
+SpellCheck                     : "Correttore ortografico",\r
+UniversalKeyboard      : "Tastiera universale",\r
+PageBreakLbl           : "Interruzione di pagina",\r
+PageBreak                      : "Inserisci interruzione di pagina",\r
+\r
+Form                   : "Modulo",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Radio Button",\r
+TextField              : "Campo di testo",\r
+Textarea               : "Area di testo",\r
+HiddenField            : "Campo nascosto",\r
+Button                 : "Bottone",\r
+SelectionField : "Menu di selezione",\r
+ImageButton            : "Bottone immagine",\r
+\r
+FitWindow              : "Massimizza l'area dell'editor",\r
+\r
+// Context Menu\r
+EditLink                       : "Modifica collegamento",\r
+CellCM                         : "Cella",\r
+RowCM                          : "Riga",\r
+ColumnCM                       : "Colonna",\r
+InsertRow                      : "Inserisci riga",\r
+DeleteRows                     : "Elimina righe",\r
+InsertColumn           : "Inserisci colonna",\r
+DeleteColumns          : "Elimina colonne",\r
+InsertCell                     : "Inserisci cella",\r
+DeleteCells                    : "Elimina celle",\r
+MergeCells                     : "Unisce celle",\r
+SplitCell                      : "Dividi celle",\r
+TableDelete                    : "Cancella Tabella",\r
+CellProperties         : "Proprietà cella",\r
+TableProperties                : "Proprietà tabella",\r
+ImageProperties                : "Proprietà immagine",\r
+FlashProperties                : "Proprietà Oggetto Flash",\r
+\r
+AnchorProp                     : "Proprietà ancora",\r
+ButtonProp                     : "Proprietà bottone",\r
+CheckboxProp           : "Proprietà checkbox",\r
+HiddenFieldProp                : "Proprietà campo nascosto",\r
+RadioButtonProp                : "Proprietà radio button",\r
+ImageButtonProp                : "Proprietà bottone immagine",\r
+TextFieldProp          : "Proprietà campo di testo",\r
+SelectionFieldProp     : "Proprietà menu di selezione",\r
+TextareaProp           : "Proprietà area di testo",\r
+FormProp                       : "Proprietà modulo",\r
+\r
+FontFormats                    : "Normale;Formattato;Indirizzo;Titolo 1;Titolo 2;Titolo 3;Titolo 4;Titolo 5;Titolo 6;Paragrafo (DIV)",         //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Elaborazione XHTML in corso. Attendere prego...",\r
+Done                           : "Completato",\r
+PasteWordConfirm       : "Il testo da incollare sembra provenire da Word. Desideri pulirlo prima di incollare?",\r
+NotCompatiblePaste     : "Questa funzione è disponibile solo per Internet Explorer 5.5 o superiore. Desideri incollare il testo senza pulirlo?",\r
+UnknownToolbarItem     : "Elemento della barra strumenti sconosciuto \"%1\"",\r
+UnknownCommand         : "Comando sconosciuto \"%1\"",\r
+NotImplemented         : "Comando non implementato",\r
+UnknownToolbarSet      : "La barra di strumenti \"%1\" non esiste",\r
+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.",\r
+BrowseServerBlocked : "Non è possibile aprire la finestra di espolorazione risorse. Verifica che tutti i blocca popup siano bloccati.",\r
+DialogBlocked          : "Non è possibile aprire la finestra di dialogo. Verifica che tutti i blocca popup siano bloccati.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Annulla",\r
+DlgBtnClose                    : "Chiudi",\r
+DlgBtnBrowseServer     : "Cerca sul server",\r
+DlgAdvancedTag         : "Avanzate",\r
+DlgOpOther                     : "<Altro>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Devi inserire l'URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<non impostato>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Direzione scrittura",\r
+DlgGenLangDirLtr       : "Da Sinistra a Destra (LTR)",\r
+DlgGenLangDirRtl       : "Da Destra a Sinistra (RTL)",\r
+DlgGenLangCode         : "Codice Lingua",\r
+DlgGenAccessKey                : "Scorciatoia<br />da tastiera",\r
+DlgGenName                     : "Nome",\r
+DlgGenTabIndex         : "Ordine di tabulazione",\r
+DlgGenLongDescr                : "URL descrizione estesa",\r
+DlgGenClass                    : "Nome classe CSS",\r
+DlgGenTitle                    : "Titolo",\r
+DlgGenContType         : "Tipo della risorsa collegata",\r
+DlgGenLinkCharset      : "Set di caretteri della risorsa collegata",\r
+DlgGenStyle                    : "Stile",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Proprietà immagine",\r
+DlgImgInfoTab          : "Informazioni immagine",\r
+DlgImgBtnUpload                : "Invia al server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Carica",\r
+DlgImgAlt                      : "Testo alternativo",\r
+DlgImgWidth                    : "Larghezza",\r
+DlgImgHeight           : "Altezza",\r
+DlgImgLockRatio                : "Blocca rapporto",\r
+DlgBtnResetSize                : "Reimposta dimensione",\r
+DlgImgBorder           : "Bordo",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Allineamento",\r
+DlgImgAlignLeft                : "Sinistra",\r
+DlgImgAlignAbsBottom: "In basso assoluto",\r
+DlgImgAlignAbsMiddle: "Centrato assoluto",\r
+DlgImgAlignBaseline    : "Linea base",\r
+DlgImgAlignBottom      : "In Basso",\r
+DlgImgAlignMiddle      : "Centrato",\r
+DlgImgAlignRight       : "Destra",\r
+DlgImgAlignTextTop     : "In alto al testo",\r
+DlgImgAlignTop         : "In Alto",\r
+DlgImgPreview          : "Anteprima",\r
+DlgImgAlertUrl         : "Devi inserire l'URL per l'immagine",\r
+DlgImgLinkTab          : "Collegamento",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Proprietà Oggetto Flash",\r
+DlgFlashChkPlay                : "Avvio Automatico",\r
+DlgFlashChkLoop                : "Cicla",\r
+DlgFlashChkMenu                : "Abilita Menu di Flash",\r
+DlgFlashScale          : "Ridimensiona",\r
+DlgFlashScaleAll       : "Mostra Tutto",\r
+DlgFlashScaleNoBorder  : "Senza Bordo",\r
+DlgFlashScaleFit       : "Dimensione Esatta",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Collegamento",\r
+DlgLnkInfoTab          : "Informazioni collegamento",\r
+DlgLnkTargetTab                : "Destinazione",\r
+\r
+DlgLnkType                     : "Tipo di Collegamento",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Ancora nella pagina",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocollo",\r
+DlgLnkProtoOther       : "<altro>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Scegli Ancora",\r
+DlgLnkAnchorByName     : "Per Nome",\r
+DlgLnkAnchorById       : "Per id elemento",\r
+DlgLnkNoAnchors                : "<Nessuna ancora disponibile nel documento>",         //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Indirizzo E-Mail",\r
+DlgLnkEMailSubject     : "Oggetto del messaggio",\r
+DlgLnkEMailBody                : "Corpo del messaggio",\r
+DlgLnkUpload           : "Carica",\r
+DlgLnkBtnUpload                : "Invia al Server",\r
+\r
+DlgLnkTarget           : "Destinazione",\r
+DlgLnkTargetFrame      : "<riquadro>",\r
+DlgLnkTargetPopup      : "<finestra popup>",\r
+DlgLnkTargetBlank      : "Nuova finestra (_blank)",\r
+DlgLnkTargetParent     : "Finestra padre (_parent)",\r
+DlgLnkTargetSelf       : "Stessa finestra (_self)",\r
+DlgLnkTargetTop                : "Finestra superiore (_top)",\r
+DlgLnkTargetFrameName  : "Nome del riquadro di destinazione",\r
+DlgLnkPopWinName       : "Nome finestra popup",\r
+DlgLnkPopWinFeat       : "Caratteristiche finestra popup",\r
+DlgLnkPopResize                : "Ridimensionabile",\r
+DlgLnkPopLocation      : "Barra degli indirizzi",\r
+DlgLnkPopMenu          : "Barra del menu",\r
+DlgLnkPopScroll                : "Barre di scorrimento",\r
+DlgLnkPopStatus                : "Barra di stato",\r
+DlgLnkPopToolbar       : "Barra degli strumenti",\r
+DlgLnkPopFullScrn      : "A tutto schermo (IE)",\r
+DlgLnkPopDependent     : "Dipendente (Netscape)",\r
+DlgLnkPopWidth         : "Larghezza",\r
+DlgLnkPopHeight                : "Altezza",\r
+DlgLnkPopLeft          : "Posizione da sinistra",\r
+DlgLnkPopTop           : "Posizione dall'alto",\r
+\r
+DlnLnkMsgNoUrl         : "Devi inserire l'URL del collegamento",\r
+DlnLnkMsgNoEMail       : "Devi inserire un'indirizzo e-mail",\r
+DlnLnkMsgNoAnchor      : "Devi selezionare un'ancora",\r
+DlnLnkMsgInvPopName    : "Il nome del popup deve iniziare con una lettera, e non può contenere spazi",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Seleziona colore",\r
+DlgColorBtnClear       : "Vuota",\r
+DlgColorHighlight      : "Evidenziato",\r
+DlgColorSelected       : "Selezionato",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Inserisci emoticon",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Seleziona carattere speciale",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Proprietà tabella",\r
+DlgTableRows           : "Righe",\r
+DlgTableColumns                : "Colonne",\r
+DlgTableBorder         : "Dimensione bordo",\r
+DlgTableAlign          : "Allineamento",\r
+DlgTableAlignNotSet    : "<non impostato>",\r
+DlgTableAlignLeft      : "Sinistra",\r
+DlgTableAlignCenter    : "Centrato",\r
+DlgTableAlignRight     : "Destra",\r
+DlgTableWidth          : "Larghezza",\r
+DlgTableWidthPx                : "pixel",\r
+DlgTableWidthPc                : "percento",\r
+DlgTableHeight         : "Altezza",\r
+DlgTableCellSpace      : "Spaziatura celle",\r
+DlgTableCellPad                : "Padding celle",\r
+DlgTableCaption                : "Intestazione",\r
+DlgTableSummary                : "Indice",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Proprietà cella",\r
+DlgCellWidth           : "Larghezza",\r
+DlgCellWidthPx         : "pixel",\r
+DlgCellWidthPc         : "percento",\r
+DlgCellHeight          : "Altezza",\r
+DlgCellWordWrap                : "A capo automatico",\r
+DlgCellWordWrapNotSet  : "<non impostato>",\r
+DlgCellWordWrapYes     : "Si",\r
+DlgCellWordWrapNo      : "No",\r
+DlgCellHorAlign                : "Allineamento orizzontale",\r
+DlgCellHorAlignNotSet  : "<non impostato>",\r
+DlgCellHorAlignLeft    : "Sinistra",\r
+DlgCellHorAlignCenter  : "Centrato",\r
+DlgCellHorAlignRight: "Destra",\r
+DlgCellVerAlign                : "Allineamento verticale",\r
+DlgCellVerAlignNotSet  : "<non impostato>",\r
+DlgCellVerAlignTop     : "In Alto",\r
+DlgCellVerAlignMiddle  : "Centrato",\r
+DlgCellVerAlignBottom  : "In Basso",\r
+DlgCellVerAlignBaseline        : "Linea base",\r
+DlgCellRowSpan         : "Righe occupate",\r
+DlgCellCollSpan                : "Colonne occupate",\r
+DlgCellBackColor       : "Colore sfondo",\r
+DlgCellBorderColor     : "Colore bordo",\r
+DlgCellBtnSelect       : "Scegli...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Trova",\r
+DlgFindFindBtn         : "Trova",\r
+DlgFindNotFoundMsg     : "L'elemento cercato non è stato trovato.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Sostituisci",\r
+DlgReplaceFindLbl              : "Trova:",\r
+DlgReplaceReplaceLbl   : "Sostituisci con:",\r
+DlgReplaceCaseChk              : "Maiuscole/minuscole",\r
+DlgReplaceReplaceBtn   : "Sostituisci",\r
+DlgReplaceReplAllBtn   : "Sostituisci tutto",\r
+DlgReplaceWordChk              : "Solo parole intere",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Le impostazioni di sicurezza del browser non permettono di tagliare automaticamente il testo. Usa la tastiera (Ctrl+X).",\r
+PasteErrorCopy : "Le impostazioni di sicurezza del browser non permettono di copiare automaticamente il testo. Usa la tastiera (Ctrl+C).",\r
+\r
+PasteAsText            : "Incolla come testo semplice",\r
+PasteFromWord  : "Incolla da Word",\r
+\r
+DlgPasteMsg2   : "Incolla il testo all'interno dell'area sottostante usando la scorciatoia di tastiere (<STRONG>Ctrl+V</STRONG>) e premi <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignora le definizioni di Font",\r
+DlgPasteRemoveStyles   : "Rimuovi le definizioni di Stile",\r
+DlgPasteCleanBox               : "Svuota area di testo",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatico",\r
+ColorMoreColors        : "Altri colori...",\r
+\r
+// Document Properties\r
+DocProps               : "Proprietà del Documento",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Proprietà ancora",\r
+DlgAnchorName          : "Nome ancora",\r
+DlgAnchorErrorName     : "Inserici il nome dell'ancora",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Non nel dizionario",\r
+DlgSpellChangeTo               : "Cambia in",\r
+DlgSpellBtnIgnore              : "Ignora",\r
+DlgSpellBtnIgnoreAll   : "Ignora tutto",\r
+DlgSpellBtnReplace             : "Cambia",\r
+DlgSpellBtnReplaceAll  : "Cambia tutto",\r
+DlgSpellBtnUndo                        : "Annulla",\r
+DlgSpellNoSuggestions  : "- Nessun suggerimento -",\r
+DlgSpellProgress               : "Controllo ortografico in corso",\r
+DlgSpellNoMispell              : "Controllo ortografico completato: nessun errore trovato",\r
+DlgSpellNoChanges              : "Controllo ortografico completato: nessuna parola cambiata",\r
+DlgSpellOneChange              : "Controllo ortografico completato: 1 parola cambiata",\r
+DlgSpellManyChanges            : "Controllo ortografico completato: %1 parole cambiate",\r
+\r
+IeSpellDownload                        : "Contollo ortografico non installato. Lo vuoi scaricare ora?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Testo (Value)",\r
+DlgButtonType          : "Tipo",\r
+DlgButtonTypeBtn       : "Bottone",\r
+DlgButtonTypeSbm       : "Invio",\r
+DlgButtonTypeRst       : "Annulla",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nome",\r
+DlgCheckboxValue       : "Valore",\r
+DlgCheckboxSelected    : "Selezionato",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nome",\r
+DlgFormAction  : "Azione",\r
+DlgFormMethod  : "Metodo",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nome",\r
+DlgSelectValue         : "Valore",\r
+DlgSelectSize          : "Dimensione",\r
+DlgSelectLines         : "righe",\r
+DlgSelectChkMulti      : "Permetti selezione multipla",\r
+DlgSelectOpAvail       : "Opzioni disponibili",\r
+DlgSelectOpText                : "Testo",\r
+DlgSelectOpValue       : "Valore",\r
+DlgSelectBtnAdd                : "Aggiungi",\r
+DlgSelectBtnModify     : "Modifica",\r
+DlgSelectBtnUp         : "Su",\r
+DlgSelectBtnDown       : "Gi",\r
+DlgSelectBtnSetValue : "Imposta come predefinito",\r
+DlgSelectBtnDelete     : "Rimuovi",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nome",\r
+DlgTextareaCols        : "Colonne",\r
+DlgTextareaRows        : "Righe",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nome",\r
+DlgTextValue           : "Valore",\r
+DlgTextCharWidth       : "Larghezza",\r
+DlgTextMaxChars                : "Numero massimo di caratteri",\r
+DlgTextType                    : "Tipo",\r
+DlgTextTypeText                : "Testo",\r
+DlgTextTypePass                : "Password",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nome",\r
+DlgHiddenValue : "Valore",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Proprietà lista puntata",\r
+NumberedListProp       : "Proprietà lista numerata",\r
+DlgLstStart                    : "Inizio",\r
+DlgLstType                     : "Tipo",\r
+DlgLstTypeCircle       : "Tondo",\r
+DlgLstTypeDisc         : "Disco",\r
+DlgLstTypeSquare       : "Quadrato",\r
+DlgLstTypeNumbers      : "Numeri (1, 2, 3)",\r
+DlgLstTypeLCase                : "Caratteri minuscoli (a, b, c)",\r
+DlgLstTypeUCase                : "Caratteri maiuscoli (A, B, C)",\r
+DlgLstTypeSRoman       : "Numeri Romani minuscoli (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Numeri Romani maiuscoli (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Genarale",\r
+DlgDocBackTab          : "Sfondo",\r
+DlgDocColorsTab                : "Colori e margini",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Titolo pagina",\r
+DlgDocLangDir          : "Direzione scrittura",\r
+DlgDocLangDirLTR       : "Da Sinistra a Destra (LTR)",\r
+DlgDocLangDirRTL       : "Da Destra a Sinistra (RTL)",\r
+DlgDocLangCode         : "Codice Lingua",\r
+DlgDocCharSet          : "Set di caretteri",\r
+DlgDocCharSetCE                : "Europa Centrale",\r
+DlgDocCharSetCT                : "Cinese Tradizionale (Big5)",\r
+DlgDocCharSetCR                : "Cirillico",\r
+DlgDocCharSetGR                : "Greco",\r
+DlgDocCharSetJP                : "Giapponese",\r
+DlgDocCharSetKR                : "Coreano",\r
+DlgDocCharSetTR                : "Turco",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Europa Occidentale",\r
+DlgDocCharSetOther     : "Altro set di caretteri",\r
+\r
+DlgDocDocType          : "Intestazione DocType",\r
+DlgDocDocTypeOther     : "Altra intestazione DocType",\r
+DlgDocIncXHTML         : "Includi dichiarazione XHTML",\r
+DlgDocBgColor          : "Colore di sfondo",\r
+DlgDocBgImage          : "Immagine di sfondo",\r
+DlgDocBgNoScroll       : "Sfondo fissato",\r
+DlgDocCText                    : "Testo",\r
+DlgDocCLink                    : "Collegamento",\r
+DlgDocCVisited         : "Collegamento visitato",\r
+DlgDocCActive          : "Collegamento attivo",\r
+DlgDocMargins          : "Margini",\r
+DlgDocMaTop                    : "In Alto",\r
+DlgDocMaLeft           : "A Sinistra",\r
+DlgDocMaRight          : "A Destra",\r
+DlgDocMaBottom         : "In Basso",\r
+DlgDocMeIndex          : "Chiavi di indicizzazione documento (separate da virgola)",\r
+DlgDocMeDescr          : "Descrizione documento",\r
+DlgDocMeAuthor         : "Autore",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Anteprima",\r
+\r
+// Templates Dialog\r
+Templates                      : "Modelli",\r
+DlgTemplatesTitle      : "Contenuto dei modelli",\r
+DlgTemplatesSelMsg     : "Seleziona il modello da aprire nell'editor<br />(il contenuto attuale verrà eliminato):",\r
+DlgTemplatesLoading    : "Caricamento modelli in corso. Attendere prego...",\r
+DlgTemplatesNoTpl      : "(Nessun modello definito)",\r
+DlgTemplatesReplace    : "Cancella il contenuto corrente",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Informazioni",\r
+DlgAboutBrowserInfoTab : "Informazioni Browser",\r
+DlgAboutLicenseTab     : "Licenza",\r
+DlgAboutVersion                : "versione",\r
+DlgAboutInfo           : "Per maggiori informazioni visitare"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/ja.js b/httemplate/elements/fckeditor/editor/lang/ja.js
new file mode 100644 (file)
index 0000000..c567d21
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Japanese language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "ツールバーを隠す",\r
+ToolbarExpand          : "ツールバーを表示",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "保存",\r
+NewPage                                : "新しいページ",\r
+Preview                                : "プレビュー",\r
+Cut                                    : "切り取り",\r
+Copy                           : "コピー",\r
+Paste                          : "貼り付け",\r
+PasteText                      : "プレーンテキスト貼り付け",\r
+PasteWord                      : "ワード文章から貼り付け",\r
+Print                          : "印刷",\r
+SelectAll                      : "すべて選択",\r
+RemoveFormat           : "フォーマット削除",\r
+InsertLinkLbl          : "リンク",\r
+InsertLink                     : "リンク挿入/編集",\r
+RemoveLink                     : "リンク削除",\r
+Anchor                         : "アンカー挿入/編集",\r
+InsertImageLbl         : "イメージ",\r
+InsertImage                    : "イメージ挿入/編集",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Flash挿入/編集",\r
+InsertTableLbl         : "テーブル",\r
+InsertTable                    : "テーブル挿入/編集",\r
+InsertLineLbl          : "ライン",\r
+InsertLine                     : "横罫線",\r
+InsertSpecialCharLbl: "特殊文字",\r
+InsertSpecialChar      : "特殊文字挿入",\r
+InsertSmileyLbl                : "絵文字",\r
+InsertSmiley           : "絵文字挿入",\r
+About                          : "FCKeditorヘルプ",\r
+Bold                           : "太字",\r
+Italic                         : "斜体",\r
+Underline                      : "下線",\r
+StrikeThrough          : "打ち消し線",\r
+Subscript                      : "添え字",\r
+Superscript                    : "上付き文字",\r
+LeftJustify                    : "左揃え",\r
+CenterJustify          : "中央揃え",\r
+RightJustify           : "右揃え",\r
+BlockJustify           : "両端揃え",\r
+DecreaseIndent         : "インデント解除",\r
+IncreaseIndent         : "インデント",\r
+Undo                           : "元に戻す",\r
+Redo                           : "やり直し",\r
+NumberedListLbl                : "段落番号",\r
+NumberedList           : "段落番号の追加/削除",\r
+BulletedListLbl                : "箇条書き",\r
+BulletedList           : "箇条書きの追加/削除",\r
+ShowTableBorders       : "テーブルボーダー表示",\r
+ShowDetails                    : "詳細表示",\r
+Style                          : "スタイル",\r
+FontFormat                     : "フォーマット",\r
+Font                           : "フォント",\r
+FontSize                       : "サイズ",\r
+TextColor                      : "テキスト色",\r
+BGColor                                : "背景色",\r
+Source                         : "ソース",\r
+Find                           : "検索",\r
+Replace                                : "置き換え",\r
+SpellCheck                     : "スペルチェック",\r
+UniversalKeyboard      : "ユニバーサル・キーボード",\r
+PageBreakLbl           : "改ページ",\r
+PageBreak                      : "改ページ挿入",\r
+\r
+Form                   : "フォーム",\r
+Checkbox               : "チェックボックス",\r
+RadioButton            : "ラジオボタン",\r
+TextField              : "1行テキスト",\r
+Textarea               : "テキストエリア",\r
+HiddenField            : "不可視フィールド",\r
+Button                 : "ボタン",\r
+SelectionField : "選択フィールド",\r
+ImageButton            : "画像ボタン",\r
+\r
+FitWindow              : "エディタサイズを最大にします",\r
+\r
+// Context Menu\r
+EditLink                       : "リンク編集",\r
+CellCM                         : "セル",\r
+RowCM                          : "行",\r
+ColumnCM                       : "カラム",\r
+InsertRow                      : "行挿入",\r
+DeleteRows                     : "行削除",\r
+InsertColumn           : "列挿入",\r
+DeleteColumns          : "列削除",\r
+InsertCell                     : "セル挿入",\r
+DeleteCells                    : "セル削除",\r
+MergeCells                     : "セル結合",\r
+SplitCell                      : "セル分割",\r
+TableDelete                    : "テーブル削除",\r
+CellProperties         : "セル プロパティ",\r
+TableProperties                : "テーブル プロパティ",\r
+ImageProperties                : "イメージ プロパティ",\r
+FlashProperties                : "Flash プロパティ",\r
+\r
+AnchorProp                     : "アンカー プロパティ",\r
+ButtonProp                     : "ボタン プロパティ",\r
+CheckboxProp           : "チェックボックス プロパティ",\r
+HiddenFieldProp                : "不可視フィールド プロパティ",\r
+RadioButtonProp                : "ラジオボタン プロパティ",\r
+ImageButtonProp                : "画像ボタン プロパティ",\r
+TextFieldProp          : "1行テキスト プロパティ",\r
+SelectionFieldProp     : "選択フィールド プロパティ",\r
+TextareaProp           : "テキストエリア プロパティ",\r
+FormProp                       : "フォーム プロパティ",\r
+\r
+FontFormats                    : "標準;書式付き;アドレス;見出し 1;見出し 2;見出し 3;見出し 4;見出し 5;見出し 6;標準 (DIV)",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML処理中. しばらくお待ちください...",\r
+Done                           : "完了",\r
+PasteWordConfirm       : "貼り付けを行うテキストは、ワード文章からコピーされようとしています。貼り付ける前にクリーニングを行いますか?",\r
+NotCompatiblePaste     : "このコマンドはインターネット・エクスプローラーバージョン5.5以上で利用可能です。クリーニングしないで貼り付けを行いますか?",\r
+UnknownToolbarItem     : "未知のツールバー項目 \"%1\"",\r
+UnknownCommand         : "未知のコマンド名 \"%1\"",\r
+NotImplemented         : "コマンドはインプリメントされませんでした。",\r
+UnknownToolbarSet      : "ツールバー設定 \"%1\" 存在しません。",\r
+NoActiveX                      : "エラー、警告メッセージなどが発生した場合、ブラウザーのセキュリティ設定によりエディタのいくつかの機能が制限されている可能性があります。セキュリティ設定のオプションで\"ActiveXコントロールとプラグインの実行\"を有効にするにしてください。",\r
+BrowseServerBlocked : "サーバーブラウザーを開くことができませんでした。ポップアップ・ブロック機能が無効になっているか確認してください。",\r
+DialogBlocked          : "ダイアログウィンドウを開くことができませんでした。ポップアップ・ブロック機能が無効になっているか確認してください。",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "キャンセル",\r
+DlgBtnClose                    : "閉じる",\r
+DlgBtnBrowseServer     : "サーバーブラウザー",\r
+DlgAdvancedTag         : "高度な設定",\r
+DlgOpOther                     : "<その他>",\r
+DlgInfoTab                     : "情報",\r
+DlgAlertUrl                    : "URLを挿入してください",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<なし>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "文字表記の方向",\r
+DlgGenLangDirLtr       : "左から右 (LTR)",\r
+DlgGenLangDirRtl       : "右から左 (RTL)",\r
+DlgGenLangCode         : "言語コード",\r
+DlgGenAccessKey                : "アクセスキー",\r
+DlgGenName                     : "Name属性",\r
+DlgGenTabIndex         : "タブインデックス",\r
+DlgGenLongDescr                : "longdesc属性(長文説明)",\r
+DlgGenClass                    : "スタイルシートクラス",\r
+DlgGenTitle                    : "Title属性",\r
+DlgGenContType         : "Content Type属性",\r
+DlgGenLinkCharset      : "リンクcharset属性",\r
+DlgGenStyle                    : "スタイルシート",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "イメージ プロパティ",\r
+DlgImgInfoTab          : "イメージ 情報",\r
+DlgImgBtnUpload                : "サーバーに送信",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "アップロード",\r
+DlgImgAlt                      : "代替テキスト",\r
+DlgImgWidth                    : "幅",\r
+DlgImgHeight           : "高さ",\r
+DlgImgLockRatio                : "ロック比率",\r
+DlgBtnResetSize                : "サイズリセット",\r
+DlgImgBorder           : "ボーダー",\r
+DlgImgHSpace           : "横間隔",\r
+DlgImgVSpace           : "縦間隔",\r
+DlgImgAlign                    : "行揃え",\r
+DlgImgAlignLeft                : "左",\r
+DlgImgAlignAbsBottom: "下部(絶対的)",\r
+DlgImgAlignAbsMiddle: "中央(絶対的)",\r
+DlgImgAlignBaseline    : "ベースライン",\r
+DlgImgAlignBottom      : "下",\r
+DlgImgAlignMiddle      : "中央",\r
+DlgImgAlignRight       : "右",\r
+DlgImgAlignTextTop     : "テキスト上部",\r
+DlgImgAlignTop         : "上",\r
+DlgImgPreview          : "プレビュー",\r
+DlgImgAlertUrl         : "イメージのURLを入力してください。",\r
+DlgImgLinkTab          : "リンク",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash プロパティ",\r
+DlgFlashChkPlay                : "再生",\r
+DlgFlashChkLoop                : "ループ再生",\r
+DlgFlashChkMenu                : "Flashメニュー可能",\r
+DlgFlashScale          : "拡大縮小設定",\r
+DlgFlashScaleAll       : "すべて表示",\r
+DlgFlashScaleNoBorder  : "外が見えない様に拡大",\r
+DlgFlashScaleFit       : "上下左右にフィット",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "ハイパーリンク",\r
+DlgLnkInfoTab          : "ハイパーリンク 情報",\r
+DlgLnkTargetTab                : "ターゲット",\r
+\r
+DlgLnkType                     : "リンクタイプ",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "このページのアンカー",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "プロトコル",\r
+DlgLnkProtoOther       : "<その他>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "アンカーを選択",\r
+DlgLnkAnchorByName     : "アンカー名",\r
+DlgLnkAnchorById       : "エレメントID",\r
+DlgLnkNoAnchors                : "<ドキュメントにおいて利用可能なアンカーはありません。>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail アドレス",\r
+DlgLnkEMailSubject     : "件名",\r
+DlgLnkEMailBody                : "本文",\r
+DlgLnkUpload           : "アップロード",\r
+DlgLnkBtnUpload                : "サーバーに送信",\r
+\r
+DlgLnkTarget           : "ターゲット",\r
+DlgLnkTargetFrame      : "<フレーム>",\r
+DlgLnkTargetPopup      : "<ポップアップウィンドウ>",\r
+DlgLnkTargetBlank      : "新しいウィンドウ (_blank)",\r
+DlgLnkTargetParent     : "親ウィンドウ (_parent)",\r
+DlgLnkTargetSelf       : "同じウィンドウ (_self)",\r
+DlgLnkTargetTop                : "最上位ウィンドウ (_top)",\r
+DlgLnkTargetFrameName  : "目的のフレーム名",\r
+DlgLnkPopWinName       : "ポップアップウィンドウ名",\r
+DlgLnkPopWinFeat       : "ポップアップウィンドウ特徴",\r
+DlgLnkPopResize                : "リサイズ可能",\r
+DlgLnkPopLocation      : "ロケーションバー",\r
+DlgLnkPopMenu          : "メニューバー",\r
+DlgLnkPopScroll                : "スクロールバー",\r
+DlgLnkPopStatus                : "ステータスバー",\r
+DlgLnkPopToolbar       : "ツールバー",\r
+DlgLnkPopFullScrn      : "全画面モード(IE)",\r
+DlgLnkPopDependent     : "開いたウィンドウに連動して閉じる (Netscape)",\r
+DlgLnkPopWidth         : "幅",\r
+DlgLnkPopHeight                : "高さ",\r
+DlgLnkPopLeft          : "左端からの座標で指定",\r
+DlgLnkPopTop           : "上端からの座標で指定",\r
+\r
+DlnLnkMsgNoUrl         : "リンクURLを入力してください。",\r
+DlnLnkMsgNoEMail       : "メールアドレスを入力してください。",\r
+DlnLnkMsgNoAnchor      : "アンカーを選択してください。",\r
+DlnLnkMsgInvPopName    : "ポップ・アップ名は英字で始まる文字で指定してくだい。ポップ・アップ名にスペースは含めません",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "色選択",\r
+DlgColorBtnClear       : "クリア",\r
+DlgColorHighlight      : "ハイライト",\r
+DlgColorSelected       : "選択色",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "顔文字挿入",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "特殊文字選択",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "テーブル プロパティ",\r
+DlgTableRows           : "行",\r
+DlgTableColumns                : "列",\r
+DlgTableBorder         : "ボーダーサイズ",\r
+DlgTableAlign          : "キャプションの整列",\r
+DlgTableAlignNotSet    : "<なし>",\r
+DlgTableAlignLeft      : "左",\r
+DlgTableAlignCenter    : "中央",\r
+DlgTableAlignRight     : "右",\r
+DlgTableWidth          : "テーブル幅",\r
+DlgTableWidthPx                : "ピクセル",\r
+DlgTableWidthPc                : "パーセント",\r
+DlgTableHeight         : "テーブル高さ",\r
+DlgTableCellSpace      : "セル内余白",\r
+DlgTableCellPad                : "セル内間隔",\r
+DlgTableCaption                : "キャプション",\r
+DlgTableSummary                : "テーブル目的/構造",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "セル プロパティ",\r
+DlgCellWidth           : "幅",\r
+DlgCellWidthPx         : "ピクセル",\r
+DlgCellWidthPc         : "パーセント",\r
+DlgCellHeight          : "高さ",\r
+DlgCellWordWrap                : "折り返し",\r
+DlgCellWordWrapNotSet  : "<なし>",\r
+DlgCellWordWrapYes     : "Yes",\r
+DlgCellWordWrapNo      : "No",\r
+DlgCellHorAlign                : "セル横の整列",\r
+DlgCellHorAlignNotSet  : "<なし>",\r
+DlgCellHorAlignLeft    : "左",\r
+DlgCellHorAlignCenter  : "中央",\r
+DlgCellHorAlignRight: "右",\r
+DlgCellVerAlign                : "セル縦の整列",\r
+DlgCellVerAlignNotSet  : "<なし>",\r
+DlgCellVerAlignTop     : "上",\r
+DlgCellVerAlignMiddle  : "中央",\r
+DlgCellVerAlignBottom  : "下",\r
+DlgCellVerAlignBaseline        : "ベースライン",\r
+DlgCellRowSpan         : "縦幅(行数)",\r
+DlgCellCollSpan                : "横幅(列数)",\r
+DlgCellBackColor       : "背景色",\r
+DlgCellBorderColor     : "ボーダーカラー",\r
+DlgCellBtnSelect       : "選択...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "検索",\r
+DlgFindFindBtn         : "検索",\r
+DlgFindNotFoundMsg     : "指定された文字列は見つかりませんでした。",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "置き換え",\r
+DlgReplaceFindLbl              : "検索する文字列:",\r
+DlgReplaceReplaceLbl   : "置換えする文字列:",\r
+DlgReplaceCaseChk              : "部分一致",\r
+DlgReplaceReplaceBtn   : "置換え",\r
+DlgReplaceReplAllBtn   : "すべて置換え",\r
+DlgReplaceWordChk              : "単語単位で一致",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "ブラウザーのセキュリティ設定によりエディタの切り取り操作が自動で実行することができません。実行するには手動でキーボードの(Ctrl+X)を使用してください。",\r
+PasteErrorCopy : "ブラウザーのセキュリティ設定によりエディタのコピー操作が自動で実行することができません。実行するには手動でキーボードの(Ctrl+C)を使用してください。",\r
+\r
+PasteAsText            : "プレーンテキスト貼り付け",\r
+PasteFromWord  : "ワード文章から貼り付け",\r
+\r
+DlgPasteMsg2   : "キーボード(<STRONG>Ctrl+V</STRONG>)を使用して、次の入力エリア内で貼って、<STRONG>OK</STRONG>を押してください。",\r
+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\r
+DlgPasteIgnoreFont             : "FontタグのFace属性を無視します。",\r
+DlgPasteRemoveStyles   : "スタイル定義を削除します。",\r
+DlgPasteCleanBox               : "入力エリアクリア",\r
+\r
+// Color Picker\r
+ColorAutomatic : "自動",\r
+ColorMoreColors        : "その他の色...",\r
+\r
+// Document Properties\r
+DocProps               : "文書 プロパティ",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "アンカー プロパティ",\r
+DlgAnchorName          : "アンカー名",\r
+DlgAnchorErrorName     : "アンカー名を必ず入力してください。",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "辞書にありません",\r
+DlgSpellChangeTo               : "変更",\r
+DlgSpellBtnIgnore              : "無視",\r
+DlgSpellBtnIgnoreAll   : "すべて無視",\r
+DlgSpellBtnReplace             : "置換",\r
+DlgSpellBtnReplaceAll  : "すべて置換",\r
+DlgSpellBtnUndo                        : "やり直し",\r
+DlgSpellNoSuggestions  : "- 該当なし -",\r
+DlgSpellProgress               : "スペルチェック処理中...",\r
+DlgSpellNoMispell              : "スペルチェック完了: スペルの誤りはありませんでした",\r
+DlgSpellNoChanges              : "スペルチェック完了: 語句は変更されませんでした",\r
+DlgSpellOneChange              : "スペルチェック完了: 1語句変更されました",\r
+DlgSpellManyChanges            : "スペルチェック完了: %1 語句変更されました",\r
+\r
+IeSpellDownload                        : "スペルチェッカーがインストールされていません。今すぐダウンロードしますか?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "テキスト (値)",\r
+DlgButtonType          : "タイプ",\r
+DlgButtonTypeBtn       : "ボタン",\r
+DlgButtonTypeSbm       : "送信",\r
+DlgButtonTypeRst       : "リセット",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "名前",\r
+DlgCheckboxValue       : "値",\r
+DlgCheckboxSelected    : "選択済み",\r
+\r
+// Form Dialog\r
+DlgFormName            : "フォーム名",\r
+DlgFormAction  : "アクション",\r
+DlgFormMethod  : "メソッド",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "名前",\r
+DlgSelectValue         : "値",\r
+DlgSelectSize          : "サイズ",\r
+DlgSelectLines         : "行",\r
+DlgSelectChkMulti      : "複数項目選択を許可",\r
+DlgSelectOpAvail       : "利用可能なオプション",\r
+DlgSelectOpText                : "選択項目名",\r
+DlgSelectOpValue       : "選択項目値",\r
+DlgSelectBtnAdd                : "追加",\r
+DlgSelectBtnModify     : "編集",\r
+DlgSelectBtnUp         : "上へ",\r
+DlgSelectBtnDown       : "下へ",\r
+DlgSelectBtnSetValue : "選択した値を設定",\r
+DlgSelectBtnDelete     : "削除",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "名前",\r
+DlgTextareaCols        : "列",\r
+DlgTextareaRows        : "行",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "名前",\r
+DlgTextValue           : "値",\r
+DlgTextCharWidth       : "サイズ",\r
+DlgTextMaxChars                : "最大長",\r
+DlgTextType                    : "タイプ",\r
+DlgTextTypeText                : "テキスト",\r
+DlgTextTypePass                : "パスワード入力",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "名前",\r
+DlgHiddenValue : "値",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "箇条書き プロパティ",\r
+NumberedListProp       : "段落番号 プロパティ",\r
+DlgLstStart                    : "開始文字",\r
+DlgLstType                     : "タイプ",\r
+DlgLstTypeCircle       : "白丸",\r
+DlgLstTypeDisc         : "黒丸",\r
+DlgLstTypeSquare       : "四角",\r
+DlgLstTypeNumbers      : "アラビア数字 (1, 2, 3)",\r
+DlgLstTypeLCase                : "英字小文字 (a, b, c)",\r
+DlgLstTypeUCase                : "英字大文字 (A, B, C)",\r
+DlgLstTypeSRoman       : "ローマ数字小文字 (i, ii, iii)",\r
+DlgLstTypeLRoman       : "ローマ数字大文字 (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "全般",\r
+DlgDocBackTab          : "背景",\r
+DlgDocColorsTab                : "色とマージン",\r
+DlgDocMetaTab          : "メタデータ",\r
+\r
+DlgDocPageTitle                : "ページタイトル",\r
+DlgDocLangDir          : "言語文字表記の方向",\r
+DlgDocLangDirLTR       : "左から右に表記(LTR)",\r
+DlgDocLangDirRTL       : "右から左に表記(RTL)",\r
+DlgDocLangCode         : "言語コード",\r
+DlgDocCharSet          : "文字セット符号化",\r
+DlgDocCharSetCE                : "Central European",\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Greek",\r
+DlgDocCharSetJP                : "Japanese",\r
+DlgDocCharSetKR                : "Korean",\r
+DlgDocCharSetTR                : "Turkish",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Western European",\r
+DlgDocCharSetOther     : "他の文字セット符号化",\r
+\r
+DlgDocDocType          : "文書タイプヘッダー",\r
+DlgDocDocTypeOther     : "その他文書タイプヘッダー",\r
+DlgDocIncXHTML         : "XHTML宣言をインクルード",\r
+DlgDocBgColor          : "背景色",\r
+DlgDocBgImage          : "背景画像 URL",\r
+DlgDocBgNoScroll       : "スクロールしない背景",\r
+DlgDocCText                    : "テキスト",\r
+DlgDocCLink                    : "リンク",\r
+DlgDocCVisited         : "アクセス済みリンク",\r
+DlgDocCActive          : "アクセス中リンク",\r
+DlgDocMargins          : "ページ・マージン",\r
+DlgDocMaTop                    : "上部",\r
+DlgDocMaLeft           : "左",\r
+DlgDocMaRight          : "右",\r
+DlgDocMaBottom         : "下部",\r
+DlgDocMeIndex          : "文書のキーワード(カンマ区切り)",\r
+DlgDocMeDescr          : "文書の概要",\r
+DlgDocMeAuthor         : "文書の作者",\r
+DlgDocMeCopy           : "文書の著作権",\r
+DlgDocPreview          : "プレビュー",\r
+\r
+// Templates Dialog\r
+Templates                      : "テンプレート(雛形)",\r
+DlgTemplatesTitle      : "テンプレート内容",\r
+DlgTemplatesSelMsg     : "エディターで使用するテンプレートを選択してください。<br>(現在のエディタの内容は失われます):",\r
+DlgTemplatesLoading    : "テンプレート一覧読み込み中. しばらくお待ちください...",\r
+DlgTemplatesNoTpl      : "(テンプレートが定義されていません)",\r
+DlgTemplatesReplace    : "現在のエディタの内容と置換えをします",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "バージョン情報",\r
+DlgAboutBrowserInfoTab : "ブラウザ情報",\r
+DlgAboutLicenseTab     : "ライセンス",\r
+DlgAboutVersion                : "バージョン",\r
+DlgAboutInfo           : "より詳しい情報はこちらで"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/km.js b/httemplate/elements/fckeditor/editor/lang/km.js
new file mode 100644 (file)
index 0000000..e90291f
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Khmer language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "បង្រួមរបាឧបរកណ៍",\r
+ToolbarExpand          : "ពង្រីករបាឧបរណ៍",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "រក្សាទុក",\r
+NewPage                                : "ទំព័រថ្មី",\r
+Preview                                : "មើលសាកល្បង",\r
+Cut                                    : "កាត់យក",\r
+Copy                           : "ចំលងយក",\r
+Paste                          : "ចំលងដាក់",\r
+PasteText                      : "ចំលងដាក់ជាអត្ថបទធម្មតា",\r
+PasteWord                      : "ចំលងដាក់ពី Word",\r
+Print                          : "បោះពុម្ភ",\r
+SelectAll                      : "ជ្រើសរើសទាំងអស់",\r
+RemoveFormat           : "លប់ចោល ការរចនា",\r
+InsertLinkLbl          : "ឈ្នាប់",\r
+InsertLink                     : "បន្ថែម/កែប្រែ ឈ្នាប់",\r
+RemoveLink                     : "លប់ឈ្នាប់",\r
+Anchor                         : "បន្ថែម/កែប្រែ យុថ្កា",\r
+InsertImageLbl         : "រូបភាព",\r
+InsertImage                    : "បន្ថែម/កែប្រែ រូបភាព",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "បន្ថែម/កែប្រែ Flash",\r
+InsertTableLbl         : "តារាង",\r
+InsertTable                    : "បន្ថែម/កែប្រែ តារាង",\r
+InsertLineLbl          : "បន្ទាត់",\r
+InsertLine                     : "បន្ថែមបន្ទាត់ផ្តេក",\r
+InsertSpecialCharLbl: "អក្សរពិសេស",\r
+InsertSpecialChar      : "បន្ថែមអក្សរពិសេស",\r
+InsertSmileyLbl                : "រូបភាព",\r
+InsertSmiley           : "បន្ថែម រូបភាព",\r
+About                          : "អំពី FCKeditor",\r
+Bold                           : "អក្សរដិតធំ",\r
+Italic                         : "អក្សរផ្តេក",\r
+Underline                      : "ដិតបន្ទាត់ពីក្រោមអក្សរ",\r
+StrikeThrough          : "ដិតបន្ទាត់ពាក់កណ្តាលអក្សរ",\r
+Subscript                      : "អក្សរតូចក្រោម",\r
+Superscript                    : "អក្សរតូចលើ",\r
+LeftJustify                    : "តំរឹមឆ្វេង",\r
+CenterJustify          : "តំរឹមកណ្តាល",\r
+RightJustify           : "តំរឹមស្តាំ",\r
+BlockJustify           : "តំរឹមសងខាង",\r
+DecreaseIndent         : "បន្ថយការចូលបន្ទាត់",\r
+IncreaseIndent         : "បន្ថែមការចូលបន្ទាត់",\r
+Undo                           : "សារឡើងវិញ",\r
+Redo                           : "ធ្វើឡើងវិញ",\r
+NumberedListLbl                : "បញ្ជីជាអក្សរ",\r
+NumberedList           : "បន្ថែម/លប់ បញ្ជីជាអក្សរ",\r
+BulletedListLbl                : "បញ្ជីជារង្វង់មូល",\r
+BulletedList           : "បន្ថែម/លប់ បញ្ជីជារង្វង់មូល",\r
+ShowTableBorders       : "បង្ហាញស៊ុមតារាង",\r
+ShowDetails                    : "បង្ហាញពិស្តារ",\r
+Style                          : "ម៉ូត",\r
+FontFormat                     : "រចនា",\r
+Font                           : "ហ្វុង",\r
+FontSize                       : "ទំហំ",\r
+TextColor                      : "ពណ៌អក្សរ",\r
+BGColor                                : "ពណ៌ផ្ទៃខាងក្រោយ",\r
+Source                         : "កូត",\r
+Find                           : "ស្វែងរក",\r
+Replace                                : "ជំនួស",\r
+SpellCheck                     : "ពិនិត្យអក្ខរាវិរុទ្ធ",\r
+UniversalKeyboard      : "ក្តារពុម្ភអក្សរសកល",\r
+PageBreakLbl           : "ការផ្តាច់ទំព័រ",\r
+PageBreak                      : "បន្ថែម ការផ្តាច់ទំព័រ",\r
+\r
+Form                   : "បែបបទ",\r
+Checkbox               : "ប្រអប់ជ្រើសរើស",\r
+RadioButton            : "ប៉ូតុនរង្វង់មូល",\r
+TextField              : "ជួរសរសេរអត្ថបទ",\r
+Textarea               : "តំបន់សរសេរអត្ថបទ",\r
+HiddenField            : "ជួរលាក់",\r
+Button                 : "ប៉ូតុន",\r
+SelectionField : "ជួរជ្រើសរើស",\r
+ImageButton            : "ប៉ូតុនរូបភាព",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "កែប្រែឈ្នាប់",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "បន្ថែមជួរផ្តេក",\r
+DeleteRows                     : "លប់ជួរផ្តេក",\r
+InsertColumn           : "បន្ថែមជួរឈរ",\r
+DeleteColumns          : "លប់ជួរឈរ",\r
+InsertCell                     : "បន្ថែម សែល",\r
+DeleteCells                    : "លប់សែល",\r
+MergeCells                     : "បញ្ជូលសែល",\r
+SplitCell                      : "ផ្តាច់សែល",\r
+TableDelete                    : "លប់តារាង",\r
+CellProperties         : "ការកំណត់សែល",\r
+TableProperties                : "ការកំណត់តារាង",\r
+ImageProperties                : "ការកំណត់រូបភាព",\r
+FlashProperties                : "ការកំណត់ Flash",\r
+\r
+AnchorProp                     : "ការកំណត់យុថ្កា",\r
+ButtonProp                     : "ការកំណត់ ប៉ូតុន",\r
+CheckboxProp           : "ការកំណត់ប្រអប់ជ្រើសរើស",\r
+HiddenFieldProp                : "ការកំណត់ជួរលាក់",\r
+RadioButtonProp                : "ការកំណត់ប៉ូតុនរង្វង់",\r
+ImageButtonProp                : "ការកំណត់ប៉ូតុនរូបភាព",\r
+TextFieldProp          : "ការកំណត់ជួរអត្ថបទ",\r
+SelectionFieldProp     : "ការកំណត់ជួរជ្រើសរើស",\r
+TextareaProp           : "ការកំណត់កន្លែងសរសេរអត្ថបទ",\r
+FormProp                       : "ការកំណត់បែបបទ",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "កំពុងដំណើរការ XHTML ។ សូមរងចាំ...",\r
+Done                           : "ចប់រួចរាល់",\r
+PasteWordConfirm       : "អត្ថបទដែលលោកអ្នកបំរុងចំលងដាក់ ហាក់បីដូចជាត្រូវចំលងមកពីកម្មវិធី​Word​។ តើលោកអ្នកចង់សំអាតមុនចំលងអត្ថបទដាក់ទេ?",\r
+NotCompatiblePaste     : "ពាក្យបញ្ជានេះប្រើបានតែជាមួយ Internet Explorer កំរិត 5.5 រឺ លើសនេះ ។ តើលោកអ្នកចង់ចំលងដាក់ដោយមិនចាំបាច់សំអាតទេ?",\r
+UnknownToolbarItem     : "វត្ថុលើរបាឧបរកណ៍ មិនស្គាល់ \"%1\"",\r
+UnknownCommand         : "ឈ្មោះពាក្យបញ្ជា មិនស្គាល់ \"%1\"",\r
+NotImplemented         : "ពាក្យបញ្ជា មិនបានអនុវត្ត",\r
+UnknownToolbarSet      : "របាឧបរកណ៍ \"%1\" ពុំមាន ។",\r
+NoActiveX                      : "ការកំណត់សុវត្ថភាពរបស់កម្មវិធីរុករករបស់លោកអ្នក នេះ​អាចធ្វើអោយលោកអ្នកមិនអាចប្រើមុខងារខ្លះរបស់កម្មវិធីតាក់តែងអត្ថបទនេះ ។ លោកអ្នកត្រូវកំណត់អោយ \"ActiveX និង​កម្មវិធីជំនួយក្នុង (plug-ins)\" អោយដំណើរការ ។ លោកអ្នកអាចជួបប្រទះនឹង បញ្ហា ព្រមជាមួយនឹងការបាត់បង់មុខងារណាមួយរបស់កម្មវិធីតាក់តែងអត្ថបទនេះ ។",\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "វីនដូវមិនអាចបើកបានទេ ។ សូមពិនិត្យចំពោះកម្មវិធីបិទ វីនដូវលោត (popup) ថាតើវាដំណើរការរឺទេ ។",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "យល់ព្រម",\r
+DlgBtnCancel           : "មិនយល់ព្រម",\r
+DlgBtnClose                    : "បិទ",\r
+DlgBtnBrowseServer     : "មើល",\r
+DlgAdvancedTag         : "កំរិតខ្ពស់",\r
+DlgOpOther                     : "<ផ្សេងទៅត>",\r
+DlgInfoTab                     : "ពត៌មាន",\r
+DlgAlertUrl                    : "សូមសរសេរ URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<មិនមែន>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "ទិសដៅភាសា",\r
+DlgGenLangDirLtr       : "ពីឆ្វេងទៅស្តាំ(LTR)",\r
+DlgGenLangDirRtl       : "ពីស្តាំទៅឆ្វេង(RTL)",\r
+DlgGenLangCode         : "លេខកូតភាសា",\r
+DlgGenAccessKey                : "ឃី សំរាប់ចូល",\r
+DlgGenName                     : "ឈ្មោះ",\r
+DlgGenTabIndex         : "លេខ Tab",\r
+DlgGenLongDescr                : "អធិប្បាយ URL វែង",\r
+DlgGenClass                    : "Stylesheet Classes",\r
+DlgGenTitle                    : "ចំណងជើង ប្រឹក្សា",\r
+DlgGenContType         : "ប្រភេទអត្ថបទ ប្រឹក្សា",\r
+DlgGenLinkCharset      : "លេខកូតអក្សររបស់ឈ្នាប់",\r
+DlgGenStyle                    : "ម៉ូត",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "ការកំណត់រូបភាព",\r
+DlgImgInfoTab          : "ពត៌មានអំពីរូបភាព",\r
+DlgImgBtnUpload                : "បញ្ជូនទៅកាន់ម៉ាស៊ីនផ្តល់សេវា",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "ទាញយក",\r
+DlgImgAlt                      : "អត្ថបទជំនួស",\r
+DlgImgWidth                    : "ទទឹង",\r
+DlgImgHeight           : "កំពស់",\r
+DlgImgLockRatio                : "អត្រាឡុក",\r
+DlgBtnResetSize                : "កំណត់ទំហំឡើងវិញ",\r
+DlgImgBorder           : "ស៊ុម",\r
+DlgImgHSpace           : "គំលាតទទឹង",\r
+DlgImgVSpace           : "គំលាតបណ្តោយ",\r
+DlgImgAlign                    : "កំណត់ទីតាំង",\r
+DlgImgAlignLeft                : "ខាងឆ្វង",\r
+DlgImgAlignAbsBottom: "Abs Bottom",    //MISSING\r
+DlgImgAlignAbsMiddle: "Abs Middle",    //MISSING\r
+DlgImgAlignBaseline    : "បន្ទាត់ជាមូលដ្ឋាន",\r
+DlgImgAlignBottom      : "ខាងក្រោម",\r
+DlgImgAlignMiddle      : "កណ្តាល",\r
+DlgImgAlignRight       : "ខាងស្តាំ",\r
+DlgImgAlignTextTop     : "លើអត្ថបទ",\r
+DlgImgAlignTop         : "ខាងលើ",\r
+DlgImgPreview          : "មើលសាកល្បង",\r
+DlgImgAlertUrl         : "សូមសរសេរងាស័យដ្ឋានរបស់រូបភាព",\r
+DlgImgLinkTab          : "ឈ្នាប់",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "ការកំណត់ Flash",\r
+DlgFlashChkPlay                : "លេងដោយស្វ័យប្រវត្ត",\r
+DlgFlashChkLoop                : "ចំនួនដង",\r
+DlgFlashChkMenu                : "បង្ហាញ មឺនុយរបស់ Flash",\r
+DlgFlashScale          : "ទំហំ",\r
+DlgFlashScaleAll       : "បង្ហាញទាំងអស់",\r
+DlgFlashScaleNoBorder  : "មិនបង្ហាញស៊ុម",\r
+DlgFlashScaleFit       : "ត្រូវល្មម",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "ឈ្នាប់",\r
+DlgLnkInfoTab          : "ពត៌មានអំពីឈ្នាប់",\r
+DlgLnkTargetTab                : "គោលដៅ",\r
+\r
+DlgLnkType                     : "ប្រភេទឈ្នាប់",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "យុថ្កានៅក្នុងទំព័រនេះ",\r
+DlgLnkTypeEMail                : "អ៊ីមែល",\r
+DlgLnkProto                    : "ប្រូតូកូល",\r
+DlgLnkProtoOther       : "<ផ្សេងទៀត>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "ជ្រើសរើសយុថ្កា",\r
+DlgLnkAnchorByName     : "តាមឈ្មោះរបស់យុថ្កា",\r
+DlgLnkAnchorById       : "តាម Id",\r
+DlgLnkNoAnchors                : "<ពុំមានយុថ្កានៅក្នុងឯកសារនេះទេ>",          //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "អ៊ីមែល",\r
+DlgLnkEMailSubject     : "ចំណងជើងអត្ថបទ",\r
+DlgLnkEMailBody                : "អត្ថបទ",\r
+DlgLnkUpload           : "ទាញយក",\r
+DlgLnkBtnUpload                : "ទាញយក",\r
+\r
+DlgLnkTarget           : "គោលដៅ",\r
+DlgLnkTargetFrame      : "<ហ្វ្រេម>",\r
+DlgLnkTargetPopup      : "<វីនដូវ លោត>",\r
+DlgLnkTargetBlank      : "វីនដូវថ្មី (_blank)",\r
+DlgLnkTargetParent     : "វីនដូវមេ (_parent)",\r
+DlgLnkTargetSelf       : "វីនដូវដដែល (_self)",\r
+DlgLnkTargetTop                : "វីនដូវនៅលើគេ(_top)",\r
+DlgLnkTargetFrameName  : "ឈ្មោះហ្រ្វេមដែលជាគោលដៅ",\r
+DlgLnkPopWinName       : "ឈ្មោះវីនដូវលោត",\r
+DlgLnkPopWinFeat       : "លក្ខណះរបស់វីនដូលលោត",\r
+DlgLnkPopResize                : "ទំហំអាចផ្លាស់ប្តូរ",\r
+DlgLnkPopLocation      : "របា ទីតាំង",\r
+DlgLnkPopMenu          : "របា មឺនុយ",\r
+DlgLnkPopScroll                : "របា ទាញ",\r
+DlgLnkPopStatus                : "របា ពត៌មាន",\r
+DlgLnkPopToolbar       : "របា ឩបករណ៍",\r
+DlgLnkPopFullScrn      : "អេក្រុងពេញ(IE)",\r
+DlgLnkPopDependent     : "អាស្រ័យលើ (Netscape)",\r
+DlgLnkPopWidth         : "ទទឹង",\r
+DlgLnkPopHeight                : "កំពស់",\r
+DlgLnkPopLeft          : "ទីតាំងខាងឆ្វេង",\r
+DlgLnkPopTop           : "ទីតាំងខាងលើ",\r
+\r
+DlnLnkMsgNoUrl         : "សូមសរសេរ អាស័យដ្ឋាន URL",\r
+DlnLnkMsgNoEMail       : "សូមសរសេរ អាស័យដ្ឋាន អ៊ីមែល",\r
+DlnLnkMsgNoAnchor      : "សូមជ្រើសរើស យុថ្កា",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "ជ្រើសរើស ពណ៌",\r
+DlgColorBtnClear       : "លប់",\r
+DlgColorHighlight      : "ផាត់ពណ៌",\r
+DlgColorSelected       : "បានជ្រើសរើស",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "បញ្ជូលរូបភាព",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "តូអក្សរពិសេស",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "ការកំណត់ តារាង",\r
+DlgTableRows           : "ជួរផ្តេក",\r
+DlgTableColumns                : "ជួរឈរ",\r
+DlgTableBorder         : "ទំហំស៊ុម",\r
+DlgTableAlign          : "ការកំណត់ទីតាំង",\r
+DlgTableAlignNotSet    : "<មិនកំណត់>",\r
+DlgTableAlignLeft      : "ខាងឆ្វេង",\r
+DlgTableAlignCenter    : "កណ្តាល",\r
+DlgTableAlignRight     : "ខាងស្តាំ",\r
+DlgTableWidth          : "ទទឹង",\r
+DlgTableWidthPx                : "ភីកសែល",\r
+DlgTableWidthPc                : "ភាគរយ",\r
+DlgTableHeight         : "កំពស់",\r
+DlgTableCellSpace      : "គំលាតសែល",\r
+DlgTableCellPad                : "គែមសែល",\r
+DlgTableCaption                : "ចំណងជើង",\r
+DlgTableSummary                : "សេចក្តីសង្ខេប",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "ការកំណត់ សែល",\r
+DlgCellWidth           : "ទទឹង",\r
+DlgCellWidthPx         : "ភីកសែល",\r
+DlgCellWidthPc         : "ភាគរយ",\r
+DlgCellHeight          : "កំពស់",\r
+DlgCellWordWrap                : "បង្ហាញអត្ថបទទាំងអស់",\r
+DlgCellWordWrapNotSet  : "<មិនកំណត់>",\r
+DlgCellWordWrapYes     : "បាទ(ចា)",\r
+DlgCellWordWrapNo      : "ទេ",\r
+DlgCellHorAlign                : "តំរឹមផ្តេក",\r
+DlgCellHorAlignNotSet  : "<មិនកំណត់>",\r
+DlgCellHorAlignLeft    : "ខាងឆ្វេង",\r
+DlgCellHorAlignCenter  : "កណ្តាល",\r
+DlgCellHorAlignRight: "Right", //MISSING\r
+DlgCellVerAlign                : "តំរឹមឈរ",\r
+DlgCellVerAlignNotSet  : "<មិនកណត់>",\r
+DlgCellVerAlignTop     : "ខាងលើ",\r
+DlgCellVerAlignMiddle  : "កណ្តាល",\r
+DlgCellVerAlignBottom  : "ខាងក្រោម",\r
+DlgCellVerAlignBaseline        : "បន្ទាត់ជាមូលដ្ឋាន",\r
+DlgCellRowSpan         : "បញ្ជូលជួរផ្តេក",\r
+DlgCellCollSpan                : "បញ្ជូលជួរឈរ",\r
+DlgCellBackColor       : "ពណ៌ផ្នែកខាងក្រោម",\r
+DlgCellBorderColor     : "ពណ៌ស៊ុម",\r
+DlgCellBtnSelect       : "ជ្រើសរើស...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "ស្វែងរក",\r
+DlgFindFindBtn         : "ស្វែងរក",\r
+DlgFindNotFoundMsg     : "ពាក្យនេះ រកមិនឃើញទេ ។",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "ជំនួស",\r
+DlgReplaceFindLbl              : "ស្វែងរកអ្វី:",\r
+DlgReplaceReplaceLbl   : "ជំនួសជាមួយ:",\r
+DlgReplaceCaseChk              : "ករណ៉ត្រូវរក",\r
+DlgReplaceReplaceBtn   : "ជំនួស",\r
+DlgReplaceReplAllBtn   : "ជំនួសទាំងអស់",\r
+DlgReplaceWordChk              : "ត្រូវពាក្យទាំងអស់",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "ការកំណត់សុវត្ថភាពរបស់កម្មវិធីរុករករបស់លោកអ្នក នេះ​មិនអាចធ្វើកម្មវិធីតាក់តែងអត្ថបទ កាត់អត្ថបទយកដោយស្វ័យប្រវត្តបានឡើយ ។ សូមប្រើប្រាស់បន្សំ ឃីដូចនេះ  (Ctrl+X) ។",\r
+PasteErrorCopy : "ការកំណត់សុវត្ថភាពរបស់កម្មវិធីរុករករបស់លោកអ្នក នេះ​មិនអាចធ្វើកម្មវិធីតាក់តែងអត្ថបទ ចំលងអត្ថបទយកដោយស្វ័យប្រវត្តបានឡើយ ។ សូមប្រើប្រាស់បន្សំ ឃីដូចនេះ (Ctrl+C)។",\r
+\r
+PasteAsText            : "ចំលងដាក់អត្ថបទធម្មតា",\r
+PasteFromWord  : "ចំលងពាក្យពីកម្មវិធី Word",\r
+\r
+DlgPasteMsg2   : "សូមចំលងអត្ថបទទៅដាក់ក្នុងប្រអប់ដូចខាងក្រោមដោយប្រើប្រាស់ ឃី ​(<STRONG>Ctrl+V</STRONG>) ហើយចុច <STRONG>OK</STRONG> ។",\r
+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\r
+DlgPasteIgnoreFont             : "មិនគិតអំពីប្រភេទពុម្ភអក្សរ",\r
+DlgPasteRemoveStyles   : "លប់ម៉ូត",\r
+DlgPasteCleanBox               : "លប់អត្ថបទចេញពីប្រអប់",\r
+\r
+// Color Picker\r
+ColorAutomatic : "ស្វ័យប្រវត្ត",\r
+ColorMoreColors        : "ពណ៌ផ្សេងទៀត..",\r
+\r
+// Document Properties\r
+DocProps               : "ការកំណត់ ឯកសារ",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "ការកំណត់ចំណងជើងយុទ្ធថ្កា",\r
+DlgAnchorName          : "ឈ្មោះយុទ្ធថ្កា",\r
+DlgAnchorErrorName     : "សូមសរសេរ ឈ្មោះយុទ្ធថ្កា",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "គ្មានក្នុងវចនានុក្រម",\r
+DlgSpellChangeTo               : "ផ្លាស់ប្តូរទៅ",\r
+DlgSpellBtnIgnore              : "មិនផ្លាស់ប្តូរ",\r
+DlgSpellBtnIgnoreAll   : "មិនផ្លាស់ប្តូរ ទាំងអស់",\r
+DlgSpellBtnReplace             : "ជំនួស",\r
+DlgSpellBtnReplaceAll  : "ជំនួសទាំងអស់",\r
+DlgSpellBtnUndo                        : "សារឡើងវិញ",\r
+DlgSpellNoSuggestions  : "- គ្មានសំណើរ -",\r
+DlgSpellProgress               : "កំពុងពិនិត្យអក្ខរាវិរុទ្ធ...",\r
+DlgSpellNoMispell              : "ការពិនិត្យអក្ខរាវិរុទ្ធបានចប់: គ្មានកំហុស",\r
+DlgSpellNoChanges              : "ការពិនិត្យអក្ខរាវិរុទ្ធបានចប់: ពុំមានផ្លាស់ប្តូរ",\r
+DlgSpellOneChange              : "ការពិនិត្យអក្ខរាវិរុទ្ធបានចប់: ពាក្យមួយត្រូចបានផ្លាស់ប្តូរ",\r
+DlgSpellManyChanges            : "ការពិនិត្យអក្ខរាវិរុទ្ធបានចប់: %1 ពាក្យបានផ្លាស់ប្តូរ",\r
+\r
+IeSpellDownload                        : "ពុំមានកម្មវិធីពិនិត្យអក្ខរាវិរុទ្ធ ។ តើចង់ទាញយកពីណា?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "អត្ថបទ(តំលៃ)",\r
+DlgButtonType          : "ប្រភេទ",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "ឈ្មោះ",\r
+DlgCheckboxValue       : "តំលៃ",\r
+DlgCheckboxSelected    : "បានជ្រើសរើស",\r
+\r
+// Form Dialog\r
+DlgFormName            : "ឈ្មោះ",\r
+DlgFormAction  : "សកម្មភាព",\r
+DlgFormMethod  : "វិធី",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "ឈ្មោះ",\r
+DlgSelectValue         : "តំលៃ",\r
+DlgSelectSize          : "ទំហំ",\r
+DlgSelectLines         : "បន្ទាត់",\r
+DlgSelectChkMulti      : "អនុញ្ញាតអោយជ្រើសរើសច្រើន",\r
+DlgSelectOpAvail       : "ការកំណត់ជ្រើសរើស ដែលអាចកំណត់បាន",\r
+DlgSelectOpText                : "ពាក្យ",\r
+DlgSelectOpValue       : "តំលៃ",\r
+DlgSelectBtnAdd                : "បន្ថែម",\r
+DlgSelectBtnModify     : "ផ្លាស់ប្តូរ",\r
+DlgSelectBtnUp         : "លើ",\r
+DlgSelectBtnDown       : "ក្រោម",\r
+DlgSelectBtnSetValue : "Set as selected value",        //MISSING\r
+DlgSelectBtnDelete     : "លប់",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "ឈ្មោះ",\r
+DlgTextareaCols        : "ជូរឈរ",\r
+DlgTextareaRows        : "ជូរផ្តេក",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "ឈ្មោះ",\r
+DlgTextValue           : "តំលៃ",\r
+DlgTextCharWidth       : "ទទឹង អក្សរ",\r
+DlgTextMaxChars                : "អក្សរអតិបរិមា",\r
+DlgTextType                    : "ប្រភេទ",\r
+DlgTextTypeText                : "ពាក្យ",\r
+DlgTextTypePass                : "ពាក្យសំងាត់",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "ឈ្មោះ",\r
+DlgHiddenValue : "តំលៃ",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "កំណត់បញ្ជីរង្វង់",\r
+NumberedListProp       : "កំណត់បញ្េជីលេខ",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "ប្រភេទ",\r
+DlgLstTypeCircle       : "រង្វង់",\r
+DlgLstTypeDisc         : "Disc",\r
+DlgLstTypeSquare       : "ការេ",\r
+DlgLstTypeNumbers      : "លេខ(1, 2, 3)",\r
+DlgLstTypeLCase                : "អក្សរតូច(a, b, c)",\r
+DlgLstTypeUCase                : "អក្សរធំ(A, B, C)",\r
+DlgLstTypeSRoman       : "អក្សរឡាតាំងតូច(i, ii, iii)",\r
+DlgLstTypeLRoman       : "អក្សរឡាតាំងធំ(I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "ទូទៅ",\r
+DlgDocBackTab          : "ផ្នែកខាងក្រោយ",\r
+DlgDocColorsTab                : "ទំព័រ​និង ស៊ុម",\r
+DlgDocMetaTab          : "ទិន្នន័យមេ",\r
+\r
+DlgDocPageTitle                : "ចំណងជើងទំព័រ",\r
+DlgDocLangDir          : "ទិសដៅសរសេរភាសា",\r
+DlgDocLangDirLTR       : "ពីឆ្វេងទៅស្ដាំ(LTR)",\r
+DlgDocLangDirRTL       : "ពីស្ដាំទៅឆ្វេង(RTL)",\r
+DlgDocLangCode         : "លេខកូតភាសា",\r
+DlgDocCharSet          : "កំណត់លេខកូតភាសា",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "កំណត់លេខកូតភាសាផ្សេងទៀត",\r
+\r
+DlgDocDocType          : "ប្រភេទក្បាលទំព័រ",\r
+DlgDocDocTypeOther     : "ប្រភេទក្បាលទំព័រផ្សេងទៀត",\r
+DlgDocIncXHTML         : "បញ្ជូល XHTML",\r
+DlgDocBgColor          : "ពណ៌ខាងក្រោម",\r
+DlgDocBgImage          : "URL របស់រូបភាពខាងក្រោម",\r
+DlgDocBgNoScroll       : "ទំព័រក្រោមមិនប្តូរ",\r
+DlgDocCText                    : "អត្តបទ",\r
+DlgDocCLink                    : "ឈ្នាប់",\r
+DlgDocCVisited         : "ឈ្នាប់មើលហើយ",\r
+DlgDocCActive          : "ឈ្នាប់កំពុងមើល",\r
+DlgDocMargins          : "ស៊ុមទំព័រ",\r
+DlgDocMaTop                    : "លើ",\r
+DlgDocMaLeft           : "ឆ្វេង",\r
+DlgDocMaRight          : "ស្ដាំ",\r
+DlgDocMaBottom         : "ក្រោម",\r
+DlgDocMeIndex          : "ពាក្យនៅក្នុងឯកសារ (ផ្តាច់ពីគ្នាដោយក្បៀស)",\r
+DlgDocMeDescr          : "សេចក្តីអត្ថាធិប្បាយអំពីឯកសារ",\r
+DlgDocMeAuthor         : "អ្នកនិពន្ធ",\r
+DlgDocMeCopy           : "រក្សាសិទ្ធិ៏",\r
+DlgDocPreview          : "មើលសាកល្បង",\r
+\r
+// Templates Dialog\r
+Templates                      : "ឯកសារគំរូ",\r
+DlgTemplatesTitle      : "ឯកសារគំរូ របស់អត្ថន័យ",\r
+DlgTemplatesSelMsg     : "សូមជ្រើសរើសឯកសារគំរូ ដើម្បីបើកនៅក្នុងកម្មវិធីតាក់តែងអត្ថបទ<br>(អត្ថបទនឹងបាត់បង់):",\r
+DlgTemplatesLoading    : "កំពុងអានបញ្ជីឯកសារគំរូ ។ សូមរងចាំ...",\r
+DlgTemplatesNoTpl      : "(ពុំមានឯកសារគំរូត្រូវបានកំណត់)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "អំពី",\r
+DlgAboutBrowserInfoTab : "ព៌តមានកម្មវិធីរុករក",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "ជំនាន់",\r
+DlgAboutInfo           : "សំរាប់ព៌តមានផ្សេងទៀត សូមទាក់ទង"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/ko.js b/httemplate/elements/fckeditor/editor/lang/ko.js
new file mode 100644 (file)
index 0000000..0a2efa6
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Korean language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "툴바 감추기",\r
+ToolbarExpand          : "툴바 보이기",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "저장하기",\r
+NewPage                                : "새 문서",\r
+Preview                                : "미리보기",\r
+Cut                                    : "잘라내기",\r
+Copy                           : "복사하기",\r
+Paste                          : "붙여넣기",\r
+PasteText                      : "텍스트로 붙여넣기",\r
+PasteWord                      : "MS Word 형식에서 붙여넣기",\r
+Print                          : "인쇄하기",\r
+SelectAll                      : "전체선택",\r
+RemoveFormat           : "포맷 지우기",\r
+InsertLinkLbl          : "링크",\r
+InsertLink                     : "링크 삽입/변경",\r
+RemoveLink                     : "링크 삭제",\r
+Anchor                         : "책갈피 삽입/변경",\r
+InsertImageLbl         : "이미지",\r
+InsertImage                    : "이미지 삽입/변경",\r
+InsertFlashLbl         : "플래쉬",\r
+InsertFlash                    : "플래쉬 삽입/변경",\r
+InsertTableLbl         : "표",\r
+InsertTable                    : "표 삽입/변경",\r
+InsertLineLbl          : "수평선",\r
+InsertLine                     : "수평선 삽입",\r
+InsertSpecialCharLbl: "특수문자 삽입",\r
+InsertSpecialChar      : "특수문자 삽입",\r
+InsertSmileyLbl                : "아이콘",\r
+InsertSmiley           : "아이콘 삽입",\r
+About                          : "FCKeditor에 대하여",\r
+Bold                           : "진하게",\r
+Italic                         : "이텔릭",\r
+Underline                      : "밑줄",\r
+StrikeThrough          : "취소선",\r
+Subscript                      : "아래 첨자",\r
+Superscript                    : "위 첨자",\r
+LeftJustify                    : "왼쪽 정렬",\r
+CenterJustify          : "가운데 정렬",\r
+RightJustify           : "오른쪽 정렬",\r
+BlockJustify           : "양쪽 맞춤",\r
+DecreaseIndent         : "내어쓰기",\r
+IncreaseIndent         : "들여쓰기",\r
+Undo                           : "취소",\r
+Redo                           : "재실행",\r
+NumberedListLbl                : "순서있는 목록",\r
+NumberedList           : "순서있는 목록",\r
+BulletedListLbl                : "순서없는 목록",\r
+BulletedList           : "순서없는 목록",\r
+ShowTableBorders       : "표 테두리 보기",\r
+ShowDetails                    : "문서기호 보기",\r
+Style                          : "스타일",\r
+FontFormat                     : "포맷",\r
+Font                           : "폰트",\r
+FontSize                       : "글자 크기",\r
+TextColor                      : "글자 색상",\r
+BGColor                                : "배경 색상",\r
+Source                         : "소스",\r
+Find                           : "찾기",\r
+Replace                                : "바꾸기",\r
+SpellCheck                     : "철자검사",\r
+UniversalKeyboard      : "다국어 입력기",\r
+PageBreakLbl           : "Page Break", //MISSING\r
+PageBreak                      : "Insert Page Break",  //MISSING\r
+\r
+Form                   : "폼",\r
+Checkbox               : "체크박스",\r
+RadioButton            : "라디오버튼",\r
+TextField              : "입력필드",\r
+Textarea               : "입력영역",\r
+HiddenField            : "숨김필드",\r
+Button                 : "버튼",\r
+SelectionField : "펼침목록",\r
+ImageButton            : "이미지버튼",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "링크 수정",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "가로줄 삽입",\r
+DeleteRows                     : "가로줄 삭제",\r
+InsertColumn           : "세로줄 삽입",\r
+DeleteColumns          : "세로줄 삭제",\r
+InsertCell                     : "셀 삽입",\r
+DeleteCells                    : "셀 삭제",\r
+MergeCells                     : "셀 합치기",\r
+SplitCell                      : "셀 나누기",\r
+TableDelete                    : "Delete Table",       //MISSING\r
+CellProperties         : "셀 속성",\r
+TableProperties                : "표 속성",\r
+ImageProperties                : "이미지 속성",\r
+FlashProperties                : "플래쉬 속성",\r
+\r
+AnchorProp                     : "책갈피 속성",\r
+ButtonProp                     : "버튼 속성",\r
+CheckboxProp           : "체크박스 속성",\r
+HiddenFieldProp                : "숨김필드 속성",\r
+RadioButtonProp                : "라디오버튼 속성",\r
+ImageButtonProp                : "이미지버튼 속성",\r
+TextFieldProp          : "입력필드 속성",\r
+SelectionFieldProp     : "펼침목록 속성",\r
+TextareaProp           : "입력영역 속성",\r
+FormProp                       : "폼 속성",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6",               //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML 처리중. 잠시만 기다려주십시요.",\r
+Done                           : "완료",\r
+PasteWordConfirm       : "붙여넣기 할 텍스트는 MS Word에서 복사한 것입니다. 붙여넣기 전에 MS Word 포멧을 삭제하시겠습니까?",\r
+NotCompatiblePaste     : "이 명령은 인터넷익스플로러 5.5 버전 이상에서만 작동합니다. 포멧을 삭제하지 않고 붙여넣기 하시겠습니까?",\r
+UnknownToolbarItem     : "알수없는 툴바입니다. : \"%1\"",\r
+UnknownCommand         : "알수없는 기능입니다. : \"%1\"",\r
+NotImplemented         : "기능이 실행되지 않았습니다.",\r
+UnknownToolbarSet      : "툴바 설정이 없습니다. : \"%1\"",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "예",\r
+DlgBtnCancel           : "아니오",\r
+DlgBtnClose                    : "닫기",\r
+DlgBtnBrowseServer     : "서버 보기",\r
+DlgAdvancedTag         : "자세히",\r
+DlgOpOther                     : "<기타>",\r
+DlgInfoTab                     : "정보",\r
+DlgAlertUrl                    : "URL을 입력하십시요",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<설정되지 않음>",\r
+DlgGenId                       : "ID",\r
+DlgGenLangDir          : "쓰기 방향",\r
+DlgGenLangDirLtr       : "왼쪽에서 오른쪽 (LTR)",\r
+DlgGenLangDirRtl       : "오른쪽에서 왼쪽 (RTL)",\r
+DlgGenLangCode         : "언어 코드",\r
+DlgGenAccessKey                : "엑세스 키",\r
+DlgGenName                     : "Name",\r
+DlgGenTabIndex         : "탭 순서",\r
+DlgGenLongDescr                : "URL 설명",\r
+DlgGenClass                    : "Stylesheet Classes",\r
+DlgGenTitle                    : "Advisory Title",\r
+DlgGenContType         : "Advisory Content Type",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Style",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "이미지 설정",\r
+DlgImgInfoTab          : "이미지 정보",\r
+DlgImgBtnUpload                : "서버로 전송",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "업로드",\r
+DlgImgAlt                      : "이미지 설명",\r
+DlgImgWidth                    : "너비",\r
+DlgImgHeight           : "높이",\r
+DlgImgLockRatio                : "비율 유지",\r
+DlgBtnResetSize                : "원래 크기로",\r
+DlgImgBorder           : "테두리",\r
+DlgImgHSpace           : "수평여백",\r
+DlgImgVSpace           : "수직여백",\r
+DlgImgAlign                    : "정렬",\r
+DlgImgAlignLeft                : "왼쪽",\r
+DlgImgAlignAbsBottom: "줄아래(Abs Bottom)",\r
+DlgImgAlignAbsMiddle: "줄중간(Abs Middle)",\r
+DlgImgAlignBaseline    : "기준선",\r
+DlgImgAlignBottom      : "아래",\r
+DlgImgAlignMiddle      : "중간",\r
+DlgImgAlignRight       : "오른쪽",\r
+DlgImgAlignTextTop     : "글자위(Text Top)",\r
+DlgImgAlignTop         : "위",\r
+DlgImgPreview          : "미리보기",\r
+DlgImgAlertUrl         : "이미지 URL을 입력하십시요",\r
+DlgImgLinkTab          : "링크",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "플래쉬 등록정보",\r
+DlgFlashChkPlay                : "자동재생",\r
+DlgFlashChkLoop                : "반복",\r
+DlgFlashChkMenu                : "플래쉬메뉴 가능",\r
+DlgFlashScale          : "영역",\r
+DlgFlashScaleAll       : "모두보기",\r
+DlgFlashScaleNoBorder  : "경계선없음",\r
+DlgFlashScaleFit       : "영역자동조절",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "링크",\r
+DlgLnkInfoTab          : "링크 정보",\r
+DlgLnkTargetTab                : "타겟",\r
+\r
+DlgLnkType                     : "링크 종류",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "책갈피",\r
+DlgLnkTypeEMail                : "이메일",\r
+DlgLnkProto                    : "프로토콜",\r
+DlgLnkProtoOther       : "<기타>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "책갈피 선택",\r
+DlgLnkAnchorByName     : "책갈피 이름",\r
+DlgLnkAnchorById       : "책갈피 ID",\r
+DlgLnkNoAnchors                : "<문서에 책갈피가 없습니다.>",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "이메일 주소",\r
+DlgLnkEMailSubject     : "제목",\r
+DlgLnkEMailBody                : "내용",\r
+DlgLnkUpload           : "업로드",\r
+DlgLnkBtnUpload                : "서버로 전송",\r
+\r
+DlgLnkTarget           : "타겟",\r
+DlgLnkTargetFrame      : "<프레임>",\r
+DlgLnkTargetPopup      : "<팝업창>",\r
+DlgLnkTargetBlank      : "새 창 (_blank)",\r
+DlgLnkTargetParent     : "부모 창 (_parent)",\r
+DlgLnkTargetSelf       : "현재 창 (_self)",\r
+DlgLnkTargetTop                : "최 상위 창 (_top)",\r
+DlgLnkTargetFrameName  : "타겟 프레임 이름",\r
+DlgLnkPopWinName       : "팝업창 이름",\r
+DlgLnkPopWinFeat       : "팝업창 설정",\r
+DlgLnkPopResize                : "크기조정",\r
+DlgLnkPopLocation      : "주소표시줄",\r
+DlgLnkPopMenu          : "메뉴바",\r
+DlgLnkPopScroll                : "스크롤바",\r
+DlgLnkPopStatus                : "상태바",\r
+DlgLnkPopToolbar       : "툴바",\r
+DlgLnkPopFullScrn      : "전체화면 (IE)",\r
+DlgLnkPopDependent     : "Dependent (Netscape)",\r
+DlgLnkPopWidth         : "너비",\r
+DlgLnkPopHeight                : "높이",\r
+DlgLnkPopLeft          : "왼쪽 위치",\r
+DlgLnkPopTop           : "윗쪽 위치",\r
+\r
+DlnLnkMsgNoUrl         : "링크 URL을 입력하십시요.",\r
+DlnLnkMsgNoEMail       : "이메일주소를 입력하십시요.",\r
+DlnLnkMsgNoAnchor      : "책갈피명을 입력하십시요.",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "색상 선택",\r
+DlgColorBtnClear       : "지우기",\r
+DlgColorHighlight      : "현재",\r
+DlgColorSelected       : "선택됨",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "아이콘 삽입",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "특수문자 선택",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "표 설정",\r
+DlgTableRows           : "가로줄",\r
+DlgTableColumns                : "세로줄",\r
+DlgTableBorder         : "테두리 크기",\r
+DlgTableAlign          : "정렬",\r
+DlgTableAlignNotSet    : "<설정되지 않음>",\r
+DlgTableAlignLeft      : "왼쪽",\r
+DlgTableAlignCenter    : "가운데",\r
+DlgTableAlignRight     : "오른쪽",\r
+DlgTableWidth          : "너비",\r
+DlgTableWidthPx                : "픽셀",\r
+DlgTableWidthPc                : "퍼센트",\r
+DlgTableHeight         : "높이",\r
+DlgTableCellSpace      : "셀 간격",\r
+DlgTableCellPad                : "셀 여백",\r
+DlgTableCaption                : "캡션",\r
+DlgTableSummary                : "Summary",    //MISSING\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "셀 설정",\r
+DlgCellWidth           : "너비",\r
+DlgCellWidthPx         : "픽셀",\r
+DlgCellWidthPc         : "퍼센트",\r
+DlgCellHeight          : "높이",\r
+DlgCellWordWrap                : "워드랩",\r
+DlgCellWordWrapNotSet  : "<설정되지 않음>",\r
+DlgCellWordWrapYes     : "예",\r
+DlgCellWordWrapNo      : "아니오",\r
+DlgCellHorAlign                : "수평 정렬",\r
+DlgCellHorAlignNotSet  : "<설정되지 않음>",\r
+DlgCellHorAlignLeft    : "왼쪽",\r
+DlgCellHorAlignCenter  : "가운데",\r
+DlgCellHorAlignRight: "오른쪽",\r
+DlgCellVerAlign                : "수직 정렬",\r
+DlgCellVerAlignNotSet  : "<설정되지 않음>",\r
+DlgCellVerAlignTop     : "위",\r
+DlgCellVerAlignMiddle  : "중간",\r
+DlgCellVerAlignBottom  : "아래",\r
+DlgCellVerAlignBaseline        : "기준선",\r
+DlgCellRowSpan         : "세로 합치기",\r
+DlgCellCollSpan                : "가로 합치기",\r
+DlgCellBackColor       : "배경 색상",\r
+DlgCellBorderColor     : "테두리 색상",\r
+DlgCellBtnSelect       : "선택",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "찾기",\r
+DlgFindFindBtn         : "찾기",\r
+DlgFindNotFoundMsg     : "문자열을 찾을 수 없습니다.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "바꾸기",\r
+DlgReplaceFindLbl              : "찾을 문자열:",\r
+DlgReplaceReplaceLbl   : "바꿀 문자열:",\r
+DlgReplaceCaseChk              : "대소문자 구분",\r
+DlgReplaceReplaceBtn   : "바꾸기",\r
+DlgReplaceReplAllBtn   : "모두 바꾸기",\r
+DlgReplaceWordChk              : "온전한 단어",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "브라우저의 보안설정때문에 잘라내기 기능을 실행할 수 없습니다. 키보드 명령을 사용하십시요. (Ctrl+X).",\r
+PasteErrorCopy : "브라우저의 보안설정때문에 복사하기 기능을 실행할 수 없습니다. 키보드 명령을 사용하십시요.  (Ctrl+C).",\r
+\r
+PasteAsText            : "텍스트로 붙여넣기",\r
+PasteFromWord  : "MS Word 형식에서 붙여넣기",\r
+\r
+DlgPasteMsg2   : "키보드의 (<STRONG>Ctrl+V</STRONG>) 를 이용해서 상자안에 붙여넣고 <STRONG>OK</STRONG> 를 누르세요.",\r
+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\r
+DlgPasteIgnoreFont             : "폰트 설정 무시",\r
+DlgPasteRemoveStyles   : "스타일 정의 제거",\r
+DlgPasteCleanBox               : "글상자 제거",\r
+\r
+// Color Picker\r
+ColorAutomatic : "기본색상",\r
+ColorMoreColors        : "색상선택...",\r
+\r
+// Document Properties\r
+DocProps               : "문서 속성",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "책갈피 속성",\r
+DlgAnchorName          : "책갈피 이름",\r
+DlgAnchorErrorName     : "책갈피 이름을 입력하십시요.",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "사전에 없는 단어",\r
+DlgSpellChangeTo               : "변경할 단어",\r
+DlgSpellBtnIgnore              : "건너뜀",\r
+DlgSpellBtnIgnoreAll   : "모두 건너뜀",\r
+DlgSpellBtnReplace             : "변경",\r
+DlgSpellBtnReplaceAll  : "모두 변경",\r
+DlgSpellBtnUndo                        : "취소",\r
+DlgSpellNoSuggestions  : "- 추천단어 없음 -",\r
+DlgSpellProgress               : "철자검사를 진행중입니다...",\r
+DlgSpellNoMispell              : "철자검사 완료: 잘못된 철자가 없습니다.",\r
+DlgSpellNoChanges              : "철자검사 완료: 변경된 단어가 없습니다.",\r
+DlgSpellOneChange              : "철자검사 완료: 단어가 변경되었습니다.",\r
+DlgSpellManyChanges            : "철자검사 완료: %1 단어가 변경되었습니다.",\r
+\r
+IeSpellDownload                        : "철자 검사기가 철치되지 않았습니다. 지금 다운로드하시겠습니까?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "버튼글자(값)",\r
+DlgButtonType          : "버튼종류",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "이름",\r
+DlgCheckboxValue       : "값",\r
+DlgCheckboxSelected    : "선택됨",\r
+\r
+// Form Dialog\r
+DlgFormName            : "폼이름",\r
+DlgFormAction  : "실행경로(Action)",\r
+DlgFormMethod  : "방법(Method)",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "이름",\r
+DlgSelectValue         : "값",\r
+DlgSelectSize          : "세로크기",\r
+DlgSelectLines         : "줄",\r
+DlgSelectChkMulti      : "여러항목 선택 허용",\r
+DlgSelectOpAvail       : "선택옵션",\r
+DlgSelectOpText                : "이름",\r
+DlgSelectOpValue       : "값",\r
+DlgSelectBtnAdd                : "추가",\r
+DlgSelectBtnModify     : "변경",\r
+DlgSelectBtnUp         : "위로",\r
+DlgSelectBtnDown       : "아래로",\r
+DlgSelectBtnSetValue : "선택된것으로 설정",\r
+DlgSelectBtnDelete     : "삭제",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "이름",\r
+DlgTextareaCols        : "칸수",\r
+DlgTextareaRows        : "줄수",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "이름",\r
+DlgTextValue           : "값",\r
+DlgTextCharWidth       : "글자 너비",\r
+DlgTextMaxChars                : "최대 글자수",\r
+DlgTextType                    : "종류",\r
+DlgTextTypeText                : "문자열",\r
+DlgTextTypePass                : "비밀번호",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "이름",\r
+DlgHiddenValue : "값",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "순서없는 목록 속성",\r
+NumberedListProp       : "순서있는 목록 속성",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "종류",\r
+DlgLstTypeCircle       : "원(Circle)",\r
+DlgLstTypeDisc         : "Disc",       //MISSING\r
+DlgLstTypeSquare       : "네모점(Square)",\r
+DlgLstTypeNumbers      : "번호 (1, 2, 3)",\r
+DlgLstTypeLCase                : "소문자 (a, b, c)",\r
+DlgLstTypeUCase                : "대문자 (A, B, C)",\r
+DlgLstTypeSRoman       : "로마자 수문자 (i, ii, iii)",\r
+DlgLstTypeLRoman       : "로마자 대문자 (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "일반",\r
+DlgDocBackTab          : "배경",\r
+DlgDocColorsTab                : "색상 및 여백",\r
+DlgDocMetaTab          : "메타데이터",\r
+\r
+DlgDocPageTitle                : "페이지명",\r
+DlgDocLangDir          : "문자 쓰기방향",\r
+DlgDocLangDirLTR       : "왼쪽에서 오른쪽 (LTR)",\r
+DlgDocLangDirRTL       : "오른쪽에서 왼쪽 (RTL)",\r
+DlgDocLangCode         : "언어코드",\r
+DlgDocCharSet          : "캐릭터셋 인코딩",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "다른 캐릭터셋 인코딩",\r
+\r
+DlgDocDocType          : "문서 헤드",\r
+DlgDocDocTypeOther     : "다른 문서헤드",\r
+DlgDocIncXHTML         : "XHTML 문서정의 포함",\r
+DlgDocBgColor          : "배경색상",\r
+DlgDocBgImage          : "배경이미지 URL",\r
+DlgDocBgNoScroll       : "스크롤되지않는 배경",\r
+DlgDocCText                    : "텍스트",\r
+DlgDocCLink                    : "링크",\r
+DlgDocCVisited         : "방문한 링크(Visited)",\r
+DlgDocCActive          : "활성화된 링크(Active)",\r
+DlgDocMargins          : "페이지 여백",\r
+DlgDocMaTop                    : "위",\r
+DlgDocMaLeft           : "왼쪽",\r
+DlgDocMaRight          : "오른쪽",\r
+DlgDocMaBottom         : "아래",\r
+DlgDocMeIndex          : "문서 키워드 (콤마로 구분)",\r
+DlgDocMeDescr          : "문서 설명",\r
+DlgDocMeAuthor         : "작성자",\r
+DlgDocMeCopy           : "저작권",\r
+DlgDocPreview          : "미리보기",\r
+\r
+// Templates Dialog\r
+Templates                      : "템플릿",\r
+DlgTemplatesTitle      : "내용 템플릿",\r
+DlgTemplatesSelMsg     : "에디터에서 사용할 템플릿을 선택하십시요.<br>(지금까지 작성된 내용은 사라집니다.):",\r
+DlgTemplatesLoading    : "템플릿 목록을 불러오는중입니다. 잠시만 기다려주십시요.",\r
+DlgTemplatesNoTpl      : "(템플릿이 없습니다.)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "About",\r
+DlgAboutBrowserInfoTab : "브라우저 정보",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "버전",\r
+DlgAboutInfo           : "For further information go to"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/lt.js b/httemplate/elements/fckeditor/editor/lang/lt.js
new file mode 100644 (file)
index 0000000..db994d0
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Lithuanian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Sutraukti mygtukų juostą",\r
+ToolbarExpand          : "Išplėsti mygtukų juostą",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Išsaugoti",\r
+NewPage                                : "Naujas puslapis",\r
+Preview                                : "Peržiūra",\r
+Cut                                    : "Iškirpti",\r
+Copy                           : "Kopijuoti",\r
+Paste                          : "Įdėti",\r
+PasteText                      : "Įdėti kaip gryną tekstą",\r
+PasteWord                      : "Įdėti iš Word",\r
+Print                          : "Spausdinti",\r
+SelectAll                      : "Pažymėti viską",\r
+RemoveFormat           : "Panaikinti formatą",\r
+InsertLinkLbl          : "Nuoroda",\r
+InsertLink                     : "Įterpti/taisyti nuorodą",\r
+RemoveLink                     : "Panaikinti nuorodą",\r
+Anchor                         : "Įterpti/modifikuoti žymę",\r
+InsertImageLbl         : "Vaizdas",\r
+InsertImage                    : "Įterpti/taisyti vaizdą",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Įterpti/taisyti Flash",\r
+InsertTableLbl         : "Lentelė",\r
+InsertTable                    : "Įterpti/taisyti lentelę",\r
+InsertLineLbl          : "Linija",\r
+InsertLine                     : "Įterpti horizontalią liniją",\r
+InsertSpecialCharLbl: "Spec. simbolis",\r
+InsertSpecialChar      : "Įterpti specialų simbolį",\r
+InsertSmileyLbl                : "Veideliai",\r
+InsertSmiley           : "Įterpti veidelį",\r
+About                          : "Apie FCKeditor",\r
+Bold                           : "Pusjuodis",\r
+Italic                         : "Kursyvas",\r
+Underline                      : "Pabrauktas",\r
+StrikeThrough          : "Perbrauktas",\r
+Subscript                      : "Apatinis indeksas",\r
+Superscript                    : "Viršutinis indeksas",\r
+LeftJustify                    : "Lygiuoti kairę",\r
+CenterJustify          : "Centruoti",\r
+RightJustify           : "Lygiuoti dešinę",\r
+BlockJustify           : "Lygiuoti abi puses",\r
+DecreaseIndent         : "Sumažinti įtrauką",\r
+IncreaseIndent         : "Padidinti įtrauką",\r
+Undo                           : "Atšaukti",\r
+Redo                           : "Atstatyti",\r
+NumberedListLbl                : "Numeruotas sąrašas",\r
+NumberedList           : "Įterpti/Panaikinti numeruotą sąrašą",\r
+BulletedListLbl                : "Suženklintas sąrašas",\r
+BulletedList           : "Įterpti/Panaikinti suženklintą sąrašą",\r
+ShowTableBorders       : "Rodyti lentelės rėmus",\r
+ShowDetails                    : "Rodyti detales",\r
+Style                          : "Stilius",\r
+FontFormat                     : "Šrifto formatas",\r
+Font                           : "Šriftas",\r
+FontSize                       : "Šrifto dydis",\r
+TextColor                      : "Teksto spalva",\r
+BGColor                                : "Fono spalva",\r
+Source                         : "Šaltinis",\r
+Find                           : "Rasti",\r
+Replace                                : "Pakeisti",\r
+SpellCheck                     : "Rašybos tikrinimas",\r
+UniversalKeyboard      : "Universali klaviatūra",\r
+PageBreakLbl           : "Puslapių skirtukas",\r
+PageBreak                      : "Įterpti puslapių skirtuką",\r
+\r
+Form                   : "Forma",\r
+Checkbox               : "Žymimasis langelis",\r
+RadioButton            : "Žymimoji akutė",\r
+TextField              : "Teksto laukas",\r
+Textarea               : "Teksto sritis",\r
+HiddenField            : "Nerodomas laukas",\r
+Button                 : "Mygtukas",\r
+SelectionField : "Atrankos laukas",\r
+ImageButton            : "Vaizdinis mygtukas",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Taisyti nuorodą",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Įterpti eilutę",\r
+DeleteRows                     : "Šalinti eilutes",\r
+InsertColumn           : "Įterpti stulpelį",\r
+DeleteColumns          : "Šalinti stulpelius",\r
+InsertCell                     : "Įterpti langelį",\r
+DeleteCells                    : "Šalinti langelius",\r
+MergeCells                     : "Sujungti langelius",\r
+SplitCell                      : "Skaidyti langelius",\r
+TableDelete                    : "Šalinti lentelę",\r
+CellProperties         : "Langelio savybės",\r
+TableProperties                : "Lentelės savybės",\r
+ImageProperties                : "Vaizdo savybės",\r
+FlashProperties                : "Flash savybės",\r
+\r
+AnchorProp                     : "Žymės savybės",\r
+ButtonProp                     : "Mygtuko savybės",\r
+CheckboxProp           : "Žymimojo langelio savybės",\r
+HiddenFieldProp                : "Nerodomo lauko savybės",\r
+RadioButtonProp                : "Žymimosios akutės savybės",\r
+ImageButtonProp                : "Vaizdinio mygtuko savybės",\r
+TextFieldProp          : "Teksto lauko savybės",\r
+SelectionFieldProp     : "Atrankos lauko savybės",\r
+TextareaProp           : "Teksto srities savybės",\r
+FormProp                       : "Formos savybės",\r
+\r
+FontFormats                    : "Normalus;Formuotas;Kreipinio;Antraštinis 1;Antraštinis 2;Antraštinis 3;Antraštinis 4;Antraštinis 5;Antraštinis 6",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Apdorojamas XHTML. Prašome palaukti...",\r
+Done                           : "Baigta",\r
+PasteWordConfirm       : "Įdedamas tekstas yra panašus į kopiją iš Word. Ar Jūs norite prieš įdėjimą išvalyti jį?",\r
+NotCompatiblePaste     : "Ši komanda yra prieinama tik per Internet Explorer 5.5 ar aukštesnę versiją. Ar Jūs norite įterpti be valymo?",\r
+UnknownToolbarItem     : "Nežinomas mygtukų juosta elementas \"%1\"",\r
+UnknownCommand         : "Nežinomas komandos vardas \"%1\"",\r
+NotImplemented         : "Komanda nėra įgyvendinta",\r
+UnknownToolbarSet      : "Mygtukų juostos rinkinys \"%1\" neegzistuoja",\r
+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.",\r
+BrowseServerBlocked : "Neįmanoma atidaryti naujo naršyklės lango. Įsitikinkite, kad iškylančių langų blokavimo programos neveiksnios.",\r
+DialogBlocked          : "Neįmanoma atidaryti dialogo lango. Įsitikinkite, kad iškylančių langų blokavimo programos neveiksnios.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Nutraukti",\r
+DlgBtnClose                    : "Uždaryti",\r
+DlgBtnBrowseServer     : "Naršyti po serverį",\r
+DlgAdvancedTag         : "Papildomas",\r
+DlgOpOther                     : "<Kita>",\r
+DlgInfoTab                     : "Informacija",\r
+DlgAlertUrl                    : "Prašome įrašyti URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nėra nustatyta>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Teksto kryptis",\r
+DlgGenLangDirLtr       : "Iš kairės į dešinę (LTR)",\r
+DlgGenLangDirRtl       : "Iš dešinės į kairę (RTL)",\r
+DlgGenLangCode         : "Kalbos kodas",\r
+DlgGenAccessKey                : "Prieigos raktas",\r
+DlgGenName                     : "Vardas",\r
+DlgGenTabIndex         : "Tabuliavimo indeksas",\r
+DlgGenLongDescr                : "Ilgas aprašymas URL",\r
+DlgGenClass                    : "Stilių lentelės klasės",\r
+DlgGenTitle                    : "Konsultacinė antraštė",\r
+DlgGenContType         : "Konsultacinio turinio tipas",\r
+DlgGenLinkCharset      : "Susietų išteklių simbolių lentelė",\r
+DlgGenStyle                    : "Stilius",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Vaizdo savybės",\r
+DlgImgInfoTab          : "Vaizdo informacija",\r
+DlgImgBtnUpload                : "Siųsti į serverį",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Nusiųsti",\r
+DlgImgAlt                      : "Alternatyvus Tekstas",\r
+DlgImgWidth                    : "Plotis",\r
+DlgImgHeight           : "Aukštis",\r
+DlgImgLockRatio                : "Išlaikyti proporciją",\r
+DlgBtnResetSize                : "Atstatyti dydį",\r
+DlgImgBorder           : "Rėmelis",\r
+DlgImgHSpace           : "Hor.Erdvė",\r
+DlgImgVSpace           : "Vert.Erdvė",\r
+DlgImgAlign                    : "Lygiuoti",\r
+DlgImgAlignLeft                : "Kairę",\r
+DlgImgAlignAbsBottom: "Absoliučią apačią",\r
+DlgImgAlignAbsMiddle: "Absoliutų vidurį",\r
+DlgImgAlignBaseline    : "Apatinę liniją",\r
+DlgImgAlignBottom      : "Apačią",\r
+DlgImgAlignMiddle      : "Vidurį",\r
+DlgImgAlignRight       : "Dešinę",\r
+DlgImgAlignTextTop     : "Teksto viršūnę",\r
+DlgImgAlignTop         : "Viršūnę",\r
+DlgImgPreview          : "Peržiūra",\r
+DlgImgAlertUrl         : "Prašome įvesti vaizdo URL",\r
+DlgImgLinkTab          : "Nuoroda",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash savybės",\r
+DlgFlashChkPlay                : "Automatinis paleidimas",\r
+DlgFlashChkLoop                : "Ciklas",\r
+DlgFlashChkMenu                : "Leisti Flash meniu",\r
+DlgFlashScale          : "Mastelis",\r
+DlgFlashScaleAll       : "Rodyti visą",\r
+DlgFlashScaleNoBorder  : "Be rėmelio",\r
+DlgFlashScaleFit       : "Tikslus atitikimas",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Nuoroda",\r
+DlgLnkInfoTab          : "Nuorodos informacija",\r
+DlgLnkTargetTab                : "Paskirtis",\r
+\r
+DlgLnkType                     : "Nuorodos tipas",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Žymė šiame puslapyje",\r
+DlgLnkTypeEMail                : "El.paštas",\r
+DlgLnkProto                    : "Protokolas",\r
+DlgLnkProtoOther       : "<kitas>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Pasirinkite žymę",\r
+DlgLnkAnchorByName     : "Pagal žymės vardą",\r
+DlgLnkAnchorById       : "Pagal žymės Id",\r
+DlgLnkNoAnchors                : "<Šiame dokumente žymių nėra>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "El.pašto adresas",\r
+DlgLnkEMailSubject     : "Žinutės tema",\r
+DlgLnkEMailBody                : "Žinutės turinys",\r
+DlgLnkUpload           : "Siųsti",\r
+DlgLnkBtnUpload                : "Siųsti į serverį",\r
+\r
+DlgLnkTarget           : "Paskirties vieta",\r
+DlgLnkTargetFrame      : "<kadras>",\r
+DlgLnkTargetPopup      : "<išskleidžiamas langas>",\r
+DlgLnkTargetBlank      : "Naujas langas (_blank)",\r
+DlgLnkTargetParent     : "Pirminis langas (_parent)",\r
+DlgLnkTargetSelf       : "Tas pats langas (_self)",\r
+DlgLnkTargetTop                : "Svarbiausias langas (_top)",\r
+DlgLnkTargetFrameName  : "Paskirties kadro vardas",\r
+DlgLnkPopWinName       : "Paskirties lango vardas",\r
+DlgLnkPopWinFeat       : "Išskleidžiamo lango savybės",\r
+DlgLnkPopResize                : "Keičiamas dydis",\r
+DlgLnkPopLocation      : "Adreso juosta",\r
+DlgLnkPopMenu          : "Meniu juosta",\r
+DlgLnkPopScroll                : "Slinkties juostos",\r
+DlgLnkPopStatus                : "Būsenos juosta",\r
+DlgLnkPopToolbar       : "Mygtukų juosta",\r
+DlgLnkPopFullScrn      : "Visas ekranas (IE)",\r
+DlgLnkPopDependent     : "Priklausomas (Netscape)",\r
+DlgLnkPopWidth         : "Plotis",\r
+DlgLnkPopHeight                : "Aukštis",\r
+DlgLnkPopLeft          : "Kairė pozicija",\r
+DlgLnkPopTop           : "Viršutinė pozicija",\r
+\r
+DlnLnkMsgNoUrl         : "Prašome įvesti nuorodos URL",\r
+DlnLnkMsgNoEMail       : "Prašome įvesti el.pašto adresą",\r
+DlnLnkMsgNoAnchor      : "Prašome pasirinkti žymę",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Pasirinkite spalvą",\r
+DlgColorBtnClear       : "Trinti",\r
+DlgColorHighlight      : "Paryškinta",\r
+DlgColorSelected       : "Pažymėta",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Įterpti veidelį",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Pasirinkite specialų simbolį",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Lentelės savybės",\r
+DlgTableRows           : "Eilutės",\r
+DlgTableColumns                : "Stulpeliai",\r
+DlgTableBorder         : "Rėmelio dydis",\r
+DlgTableAlign          : "Lygiuoti",\r
+DlgTableAlignNotSet    : "<Nenustatyta>",\r
+DlgTableAlignLeft      : "Kairę",\r
+DlgTableAlignCenter    : "Centrą",\r
+DlgTableAlignRight     : "Dešinę",\r
+DlgTableWidth          : "Plotis",\r
+DlgTableWidthPx                : "taškais",\r
+DlgTableWidthPc                : "procentais",\r
+DlgTableHeight         : "Aukštis",\r
+DlgTableCellSpace      : "Tarpas tarp langelių",\r
+DlgTableCellPad                : "Trapas nuo langelio rėmo iki teksto",\r
+DlgTableCaption                : "Antraštė",\r
+DlgTableSummary                : "Santrauka",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Langelio savybės",\r
+DlgCellWidth           : "Plotis",\r
+DlgCellWidthPx         : "taškais",\r
+DlgCellWidthPc         : "procentais",\r
+DlgCellHeight          : "Aukštis",\r
+DlgCellWordWrap                : "Teksto laužymas",\r
+DlgCellWordWrapNotSet  : "<Nenustatyta>",\r
+DlgCellWordWrapYes     : "Taip",\r
+DlgCellWordWrapNo      : "Ne",\r
+DlgCellHorAlign                : "Horizontaliai lygiuoti",\r
+DlgCellHorAlignNotSet  : "<Nenustatyta>",\r
+DlgCellHorAlignLeft    : "Kairę",\r
+DlgCellHorAlignCenter  : "Centrą",\r
+DlgCellHorAlignRight: "Dešinę",\r
+DlgCellVerAlign                : "Vertikaliai lygiuoti",\r
+DlgCellVerAlignNotSet  : "<Nenustatyta>",\r
+DlgCellVerAlignTop     : "Viršų",\r
+DlgCellVerAlignMiddle  : "Vidurį",\r
+DlgCellVerAlignBottom  : "Apačią",\r
+DlgCellVerAlignBaseline        : "Apatinę liniją",\r
+DlgCellRowSpan         : "Eilučių apjungimas",\r
+DlgCellCollSpan                : "Stulpelių apjungimas",\r
+DlgCellBackColor       : "Fono spalva",\r
+DlgCellBorderColor     : "Rėmelio spalva",\r
+DlgCellBtnSelect       : "Pažymėti...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Paieška",\r
+DlgFindFindBtn         : "Surasti",\r
+DlgFindNotFoundMsg     : "Nurodytas tekstas nerastas.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Pakeisti",\r
+DlgReplaceFindLbl              : "Surasti tekstą:",\r
+DlgReplaceReplaceLbl   : "Pakeisti tekstu:",\r
+DlgReplaceCaseChk              : "Skirti didžiąsias ir mažąsias raides",\r
+DlgReplaceReplaceBtn   : "Pakeisti",\r
+DlgReplaceReplAllBtn   : "Pakeisti viską",\r
+DlgReplaceWordChk              : "Atitikti pilną žodį",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+PasteErrorCopy : "Jūsų naršyklės saugumo nustatymai neleidžia redaktoriui automatiškai įvykdyti kopijavimo operacijų. Tam prašome naudoti klaviatūrą (Ctrl+C).",\r
+\r
+PasteAsText            : "Įdėti kaip gryną tekstą",\r
+PasteFromWord  : "Įdėti iš Word",\r
+\r
+DlgPasteMsg2   : "Žemiau esančiame įvedimo lauke įdėkite tekstą, naudodami klaviatūrą (<STRONG>Ctrl+V</STRONG>) ir spūstelkite mygtuką <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignoruoti šriftų nustatymus",\r
+DlgPasteRemoveStyles   : "Pašalinti stilių nustatymus",\r
+DlgPasteCleanBox               : "Trinti įvedimo lauką",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatinis",\r
+ColorMoreColors        : "Daugiau spalvų...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumento savybės",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Žymės savybės",\r
+DlgAnchorName          : "Žymės vardas",\r
+DlgAnchorErrorName     : "Prašome įvesti žymės vardą",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Žodyne nerastas",\r
+DlgSpellChangeTo               : "Pakeisti į",\r
+DlgSpellBtnIgnore              : "Ignoruoti",\r
+DlgSpellBtnIgnoreAll   : "Ignoruoti visus",\r
+DlgSpellBtnReplace             : "Pakeisti",\r
+DlgSpellBtnReplaceAll  : "Pakeisti visus",\r
+DlgSpellBtnUndo                        : "Atšaukti",\r
+DlgSpellNoSuggestions  : "- Nėra pasiūlymų -",\r
+DlgSpellProgress               : "Vyksta rašybos tikrinimas...",\r
+DlgSpellNoMispell              : "Rašybos tikrinimas baigtas: Nerasta rašybos klaidų",\r
+DlgSpellNoChanges              : "Rašybos tikrinimas baigtas: Nėra pakeistų žodžių",\r
+DlgSpellOneChange              : "Rašybos tikrinimas baigtas: Vienas žodis pakeistas",\r
+DlgSpellManyChanges            : "Rašybos tikrinimas baigtas: Pakeista %1 žodžių",\r
+\r
+IeSpellDownload                        : "Rašybos tikrinimas neinstaliuotas. Ar Jūs norite jį dabar atsisiųsti?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekstas (Reikšmė)",\r
+DlgButtonType          : "Tipas",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Vardas",\r
+DlgCheckboxValue       : "Reikšmė",\r
+DlgCheckboxSelected    : "Pažymėtas",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Vardas",\r
+DlgFormAction  : "Veiksmas",\r
+DlgFormMethod  : "Metodas",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Vardas",\r
+DlgSelectValue         : "Reikšmė",\r
+DlgSelectSize          : "Dydis",\r
+DlgSelectLines         : "eilučių",\r
+DlgSelectChkMulti      : "Leisti daugeriopą atranką",\r
+DlgSelectOpAvail       : "Galimos parinktys",\r
+DlgSelectOpText                : "Tekstas",\r
+DlgSelectOpValue       : "Reikšmė",\r
+DlgSelectBtnAdd                : "Įtraukti",\r
+DlgSelectBtnModify     : "Modifikuoti",\r
+DlgSelectBtnUp         : "Aukštyn",\r
+DlgSelectBtnDown       : "Žemyn",\r
+DlgSelectBtnSetValue : "Laikyti pažymėta reikšme",\r
+DlgSelectBtnDelete     : "Trinti",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Vardas",\r
+DlgTextareaCols        : "Ilgis",\r
+DlgTextareaRows        : "Plotis",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Vardas",\r
+DlgTextValue           : "Reikšmė",\r
+DlgTextCharWidth       : "Ilgis simboliais",\r
+DlgTextMaxChars                : "Maksimalus simbolių skaičius",\r
+DlgTextType                    : "Tipas",\r
+DlgTextTypeText                : "Tekstas",\r
+DlgTextTypePass                : "Slaptažodis",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Vardas",\r
+DlgHiddenValue : "Reikšmė",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Suženklinto sąrašo savybės",\r
+NumberedListProp       : "Numeruoto sąrašo savybės",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Tipas",\r
+DlgLstTypeCircle       : "Apskritimas",\r
+DlgLstTypeDisc         : "Diskas",\r
+DlgLstTypeSquare       : "Kvadratas",\r
+DlgLstTypeNumbers      : "Skaičiai (1, 2, 3)",\r
+DlgLstTypeLCase                : "Mažosios raidės (a, b, c)",\r
+DlgLstTypeUCase                : "Didžiosios raidės (A, B, C)",\r
+DlgLstTypeSRoman       : "Romėnų mažieji skaičiai (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Romėnų didieji skaičiai (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Bendros savybės",\r
+DlgDocBackTab          : "Fonas",\r
+DlgDocColorsTab                : "Spalvos ir kraštinės",\r
+DlgDocMetaTab          : "Meta duomenys",\r
+\r
+DlgDocPageTitle                : "Puslapio antraštė",\r
+DlgDocLangDir          : "Kalbos kryptis",\r
+DlgDocLangDirLTR       : "Iš kairės į dešinę (LTR)",\r
+DlgDocLangDirRTL       : "Iš dešinės į kairę (RTL)",\r
+DlgDocLangCode         : "Kalbos kodas",\r
+DlgDocCharSet          : "Simbolių kodavimo lentelė",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Kita simbolių kodavimo lentelė",\r
+\r
+DlgDocDocType          : "Dokumento tipo antraštė",\r
+DlgDocDocTypeOther     : "Kita dokumento tipo antraštė",\r
+DlgDocIncXHTML         : "Įtraukti XHTML deklaracijas",\r
+DlgDocBgColor          : "Fono spalva",\r
+DlgDocBgImage          : "Fono paveikslėlio nuoroda (URL)",\r
+DlgDocBgNoScroll       : "Neslenkantis fonas",\r
+DlgDocCText                    : "Tekstas",\r
+DlgDocCLink                    : "Nuoroda",\r
+DlgDocCVisited         : "Aplankyta nuoroda",\r
+DlgDocCActive          : "Aktyvi nuoroda",\r
+DlgDocMargins          : "Puslapio kraštinės",\r
+DlgDocMaTop                    : "Viršuje",\r
+DlgDocMaLeft           : "Kairėje",\r
+DlgDocMaRight          : "Dešinėje",\r
+DlgDocMaBottom         : "Apačioje",\r
+DlgDocMeIndex          : "Dokumento indeksavimo raktiniai žodžiai (atskirti kableliais)",\r
+DlgDocMeDescr          : "Dokumento apibūdinimas",\r
+DlgDocMeAuthor         : "Autorius",\r
+DlgDocMeCopy           : "Autorinės teisės",\r
+DlgDocPreview          : "Peržiūra",\r
+\r
+// Templates Dialog\r
+Templates                      : "Šablonai",\r
+DlgTemplatesTitle      : "Turinio šablonai",\r
+DlgTemplatesSelMsg     : "Pasirinkite norimą šabloną<br>(<b>Dėmesio!</b> esamas turinys bus prarastas):",\r
+DlgTemplatesLoading    : "Įkeliamas šablonų sąrašas. Prašome palaukti...",\r
+DlgTemplatesNoTpl      : "(Šablonų sąrašas tuščias)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Apie",\r
+DlgAboutBrowserInfoTab : "Naršyklės informacija",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "versija",\r
+DlgAboutInfo           : "Papildomą informaciją galima gauti"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/lv.js b/httemplate/elements/fckeditor/editor/lang/lv.js
new file mode 100644 (file)
index 0000000..6809426
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Latvian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Samazināt rīku joslu",\r
+ToolbarExpand          : "Paplašināt rīku joslu",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Saglabāt",\r
+NewPage                                : "Jauna lapa",\r
+Preview                                : "Pārskatīt",\r
+Cut                                    : "Izgriezt",\r
+Copy                           : "Kopēt",\r
+Paste                          : "Ievietot",\r
+PasteText                      : "Ievietot kā vienkāršu tekstu",\r
+PasteWord                      : "Ievietot no Worda",\r
+Print                          : "Drukāt",\r
+SelectAll                      : "Iezīmēt visu",\r
+RemoveFormat           : "Noņemt stilus",\r
+InsertLinkLbl          : "Hipersaite",\r
+InsertLink                     : "Ievietot/Labot hipersaiti",\r
+RemoveLink                     : "Noņemt hipersaiti",\r
+Anchor                         : "Ievietot/Labot iezīmi",\r
+InsertImageLbl         : "Attēls",\r
+InsertImage                    : "Ievietot/Labot Attēlu",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Ievietot/Labot Flash",\r
+InsertTableLbl         : "Tabula",\r
+InsertTable                    : "Ievietot/Labot Tabulu",\r
+InsertLineLbl          : "Atdalītājsvītra",\r
+InsertLine                     : "Ievietot horizontālu Atdalītājsvītru",\r
+InsertSpecialCharLbl: "Īpašs simbols",\r
+InsertSpecialChar      : "Ievietot speciālo simbolu",\r
+InsertSmileyLbl                : "Smaidiņi",\r
+InsertSmiley           : "Ievietot smaidiņu",\r
+About                          : "Īsumā par FCKeditor",\r
+Bold                           : "Treknu šriftu",\r
+Italic                         : "Slīprakstā",\r
+Underline                      : "Apakšsvītra",\r
+StrikeThrough          : "Pārsvītrots",\r
+Subscript                      : "Zemrakstā",\r
+Superscript                    : "Augšrakstā",\r
+LeftJustify                    : "Izlīdzināt pa kreisi",\r
+CenterJustify          : "Izlīdzināt pret centru",\r
+RightJustify           : "Izlīdzināt pa labi",\r
+BlockJustify           : "Izlīdzināt malas",\r
+DecreaseIndent         : "Samazināt atkāpi",\r
+IncreaseIndent         : "Palielināt atkāpi",\r
+Undo                           : "Atcelt",\r
+Redo                           : "Atkārtot",\r
+NumberedListLbl                : "Numurēts saraksts",\r
+NumberedList           : "Ievietot/Noņemt numerēto sarakstu",\r
+BulletedListLbl                : "Izcelts saraksts",\r
+BulletedList           : "Ievietot/Noņemt izceltu sarakstu",\r
+ShowTableBorders       : "Parādīt tabulas robežas",\r
+ShowDetails                    : "Parādīt sīkāku informāciju",\r
+Style                          : "Stils",\r
+FontFormat                     : "Formāts",\r
+Font                           : "Šrifts",\r
+FontSize                       : "Izmērs",\r
+TextColor                      : "Teksta krāsa",\r
+BGColor                                : "Fona krāsa",\r
+Source                         : "HTML kods",\r
+Find                           : "Meklēt",\r
+Replace                                : "Nomainīt",\r
+SpellCheck                     : "Pareizrakstības pārbaude",\r
+UniversalKeyboard      : "Universāla klaviatūra",\r
+PageBreakLbl           : "Lapas pārtraukums",\r
+PageBreak                      : "Ievietot lapas pārtraukumu",\r
+\r
+Form                   : "Forma",\r
+Checkbox               : "Atzīmēšanas kastīte",\r
+RadioButton            : "Izvēles poga",\r
+TextField              : "Teksta rinda",\r
+Textarea               : "Teksta laukums",\r
+HiddenField            : "Paslēpta teksta rinda",\r
+Button                 : "Poga",\r
+SelectionField : "Iezīmēšanas lauks",\r
+ImageButton            : "Attēlpoga",\r
+\r
+FitWindow              : "Maksimizēt redaktora izmēru",\r
+\r
+// Context Menu\r
+EditLink                       : "Labot hipersaiti",\r
+CellCM                         : "Šūna",\r
+RowCM                          : "Rinda",\r
+ColumnCM                       : "Kolonna",\r
+InsertRow                      : "Ievietot rindu",\r
+DeleteRows                     : "Dzēst rindas",\r
+InsertColumn           : "Ievietot kolonnu",\r
+DeleteColumns          : "Dzēst kolonnas",\r
+InsertCell                     : "Ievietot rūtiņu",\r
+DeleteCells                    : "Dzēst rūtiņas",\r
+MergeCells                     : "Apvienot rūtiņas",\r
+SplitCell                      : "Sadalīt rūtiņu",\r
+TableDelete                    : "Dzēst tabulu",\r
+CellProperties         : "Rūtiņas īpašības",\r
+TableProperties                : "Tabulas īpašības",\r
+ImageProperties                : "Attēla īpašības",\r
+FlashProperties                : "Flash īpašības",\r
+\r
+AnchorProp                     : "Iezīmes īpašības",\r
+ButtonProp                     : "Pogas īpašības",\r
+CheckboxProp           : "Atzīmēšanas kastītes īpašības",\r
+HiddenFieldProp                : "Paslēptās teksta rindas īpašības",\r
+RadioButtonProp                : "Izvēles poga īpašības",\r
+ImageButtonProp                : "Attēlpogas īpašības",\r
+TextFieldProp          : "Teksta rindas  īpašības",\r
+SelectionFieldProp     : "Iezīmēšanas lauka īpašības",\r
+TextareaProp           : "Teksta laukuma īpašības",\r
+FormProp                       : "Formas īpašības",\r
+\r
+FontFormats                    : "Normāls teksts;Formatēts teksts;Adrese;Virsraksts 1;Virsraksts 2;Virsraksts 3;Virsraksts 4;Virsraksts 5;Virsraksts 6;Rindkopa (DIV)",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Tiek apstrādāts XHTML. Lūdzu uzgaidiet...",\r
+Done                           : "Darīts",\r
+PasteWordConfirm       : "Teksta fragments, kas tiek ievietots, izskatās, ka būtu sagatavots Word'ā. Vai vēlaties to apstrādāt pirms ievietošanas?",\r
+NotCompatiblePaste     : "Šī darbība ir pieejama Internet Explorer'ī, kas jaunāks par 5.5 versiju. Vai vēlaties ievietot bez apstrādes?",\r
+UnknownToolbarItem     : "Nezināms rīku joslas objekts \"%1\"",\r
+UnknownCommand         : "Nezināmas darbības nosaukums \"%1\"",\r
+NotImplemented         : "Darbība netika paveikta",\r
+UnknownToolbarSet      : "Rīku joslas komplekts \"%1\" neeksistē",\r
+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.",\r
+BrowseServerBlocked : "Resursu pārlūks nevar tikt atvērts. Pārliecinieties, ka uznirstošo logu bloķētāji ir atslēgti.",\r
+DialogBlocked          : "Nav iespējams atvērt dialoglogu. Pārliecinieties, ka uznirstošo logu bloķētāji ir atslēgti.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "Darīts!",\r
+DlgBtnCancel           : "Atcelt",\r
+DlgBtnClose                    : "Aizvērt",\r
+DlgBtnBrowseServer     : "Skatīt servera saturu",\r
+DlgAdvancedTag         : "Izvērstais",\r
+DlgOpOther                     : "<Cits>",\r
+DlgInfoTab                     : "Informācija",\r
+DlgAlertUrl                    : "Lūdzu, ievietojiet hipersaiti",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nav iestatīts>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Valodas lasīšanas virziens",\r
+DlgGenLangDirLtr       : "No kreisās uz labo (LTR)",\r
+DlgGenLangDirRtl       : "No labās uz kreiso (RTL)",\r
+DlgGenLangCode         : "Valodas kods",\r
+DlgGenAccessKey                : "Pieejas kods",\r
+DlgGenName                     : "Nosaukums",\r
+DlgGenTabIndex         : "Ciļņu indekss",\r
+DlgGenLongDescr                : "Gara apraksta Hipersaite",\r
+DlgGenClass                    : "Stilu saraksta klases",\r
+DlgGenTitle                    : "Konsultatīvs virsraksts",\r
+DlgGenContType         : "Konsultatīvs satura tips",\r
+DlgGenLinkCharset      : "Pievienotā resursa kodu tabula",\r
+DlgGenStyle                    : "Stils",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Attēla īpašības",\r
+DlgImgInfoTab          : "Informācija par attēlu",\r
+DlgImgBtnUpload                : "Nosūtīt serverim",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Augšupielādēt",\r
+DlgImgAlt                      : "Alternatīvais teksts",\r
+DlgImgWidth                    : "Platums",\r
+DlgImgHeight           : "Augstums",\r
+DlgImgLockRatio                : "Nemainīga Augstuma/Platuma attiecība",\r
+DlgBtnResetSize                : "Atjaunot sākotnējo izmēru",\r
+DlgImgBorder           : "Rāmis",\r
+DlgImgHSpace           : "Horizontālā telpa",\r
+DlgImgVSpace           : "Vertikālā telpa",\r
+DlgImgAlign                    : "Nolīdzināt",\r
+DlgImgAlignLeft                : "Pa kreisi",\r
+DlgImgAlignAbsBottom: "Absolūti apakšā",\r
+DlgImgAlignAbsMiddle: "Absolūti vertikāli centrēts",\r
+DlgImgAlignBaseline    : "Pamatrindā",\r
+DlgImgAlignBottom      : "Apakšā",\r
+DlgImgAlignMiddle      : "Vertikāli centrēts",\r
+DlgImgAlignRight       : "Pa labi",\r
+DlgImgAlignTextTop     : "Teksta augšā",\r
+DlgImgAlignTop         : "Augšā",\r
+DlgImgPreview          : "Pārskats",\r
+DlgImgAlertUrl         : "Lūdzu norādīt attēla hipersaiti",\r
+DlgImgLinkTab          : "Hipersaite",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash īpašības",\r
+DlgFlashChkPlay                : "Automātiska atskaņošana",\r
+DlgFlashChkLoop                : "Nepārtraukti",\r
+DlgFlashChkMenu                : "Atļaut Flash izvēlni",\r
+DlgFlashScale          : "Mainīt izmēru",\r
+DlgFlashScaleAll       : "Rādīt visu",\r
+DlgFlashScaleNoBorder  : "Bez rāmja",\r
+DlgFlashScaleFit       : "Precīzs izmērs",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Hipersaite",\r
+DlgLnkInfoTab          : "Hipersaites informācija",\r
+DlgLnkTargetTab                : "Mērķis",\r
+\r
+DlgLnkType                     : "Hipersaites tips",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Iezīme šajā lapā",\r
+DlgLnkTypeEMail                : "E-pasts",\r
+DlgLnkProto                    : "Protokols",\r
+DlgLnkProtoOther       : "<cits>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Izvēlēties iezīmi",\r
+DlgLnkAnchorByName     : "Pēc iezīmes nosaukuma",\r
+DlgLnkAnchorById       : "Pēc elementa ID",\r
+DlgLnkNoAnchors                : "<Šajā dokumentā nav iezīmju>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-pasta adrese",\r
+DlgLnkEMailSubject     : "Ziņas tēma",\r
+DlgLnkEMailBody                : "Ziņas saturs",\r
+DlgLnkUpload           : "Augšupielādēt",\r
+DlgLnkBtnUpload                : "Nosūtīt serverim",\r
+\r
+DlgLnkTarget           : "Mērķis",\r
+DlgLnkTargetFrame      : "<ietvars>",\r
+DlgLnkTargetPopup      : "<uznirstošā logā>",\r
+DlgLnkTargetBlank      : "Jaunā logā (_blank)",\r
+DlgLnkTargetParent     : "Esošajā logā (_parent)",\r
+DlgLnkTargetSelf       : "Tajā pašā logā (_self)",\r
+DlgLnkTargetTop                : "Visredzamākajā logā (_top)",\r
+DlgLnkTargetFrameName  : "Mērķa ietvara nosaukums",\r
+DlgLnkPopWinName       : "Uznirstošā loga nosaukums",\r
+DlgLnkPopWinFeat       : "Uznirstošā loga nosaukums īpašības",\r
+DlgLnkPopResize                : "Ar maināmu izmēru",\r
+DlgLnkPopLocation      : "Atrašanās vietas josla",\r
+DlgLnkPopMenu          : "Izvēlnes josla",\r
+DlgLnkPopScroll                : "Ritjoslas",\r
+DlgLnkPopStatus                : "Statusa josla",\r
+DlgLnkPopToolbar       : "Rīku josla",\r
+DlgLnkPopFullScrn      : "Pilnā ekrānā (IE)",\r
+DlgLnkPopDependent     : "Atkarīgs (Netscape)",\r
+DlgLnkPopWidth         : "Platums",\r
+DlgLnkPopHeight                : "Augstums",\r
+DlgLnkPopLeft          : "Kreisā koordināte",\r
+DlgLnkPopTop           : "Augšējā koordināte",\r
+\r
+DlnLnkMsgNoUrl         : "Lūdzu norādi hipersaiti",\r
+DlnLnkMsgNoEMail       : "Lūdzu norādi e-pasta adresi",\r
+DlnLnkMsgNoAnchor      : "Lūdzu norādi iezīmi",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Izvēlies krāsu",\r
+DlgColorBtnClear       : "Dzēst",\r
+DlgColorHighlight      : "Izcelt",\r
+DlgColorSelected       : "Iezīmētais",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Ievietot smaidiņu",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Ievietot īpašu simbolu",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Tabulas īpašības",\r
+DlgTableRows           : "Rindas",\r
+DlgTableColumns                : "Kolonnas",\r
+DlgTableBorder         : "Rāmja izmērs",\r
+DlgTableAlign          : "Novietojums",\r
+DlgTableAlignNotSet    : "<nav norādīts>",\r
+DlgTableAlignLeft      : "Pa kreisi",\r
+DlgTableAlignCenter    : "Centrēti",\r
+DlgTableAlignRight     : "Pa labi",\r
+DlgTableWidth          : "Platums",\r
+DlgTableWidthPx                : "pikseļos",\r
+DlgTableWidthPc                : "procentuāli",\r
+DlgTableHeight         : "Augstums",\r
+DlgTableCellSpace      : "Rūtiņu atstatums",\r
+DlgTableCellPad                : "Rūtiņu nobīde",\r
+DlgTableCaption                : "Leģenda",\r
+DlgTableSummary                : "Anotācija",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Rūtiņas īpašības",\r
+DlgCellWidth           : "Platums",\r
+DlgCellWidthPx         : "pikseļi",\r
+DlgCellWidthPc         : "procentos",\r
+DlgCellHeight          : "Augstums",\r
+DlgCellWordWrap                : "Teksta pārnese",\r
+DlgCellWordWrapNotSet  : "<nav norādīta>",\r
+DlgCellWordWrapYes     : "Jā",\r
+DlgCellWordWrapNo      : "Nē",\r
+DlgCellHorAlign                : "Horizontāla novietojums",\r
+DlgCellHorAlignNotSet  : "<Nav norādīts>",\r
+DlgCellHorAlignLeft    : "Pa kreisi",\r
+DlgCellHorAlignCenter  : "Centrēti",\r
+DlgCellHorAlignRight: "Pa labi",\r
+DlgCellVerAlign                : "Vertikālais novietojums",\r
+DlgCellVerAlignNotSet  : "<nav norādīts>",\r
+DlgCellVerAlignTop     : "Augša",\r
+DlgCellVerAlignMiddle  : "Vidus",\r
+DlgCellVerAlignBottom  : "Apakša",\r
+DlgCellVerAlignBaseline        : "Pamatrindā",\r
+DlgCellRowSpan         : "Rindu pārnese",\r
+DlgCellCollSpan                : "Kolonnu pārnese",\r
+DlgCellBackColor       : "Fona krāsa",\r
+DlgCellBorderColor     : "Rāmja krāsa",\r
+DlgCellBtnSelect       : "Iezīmē...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Meklētājs",\r
+DlgFindFindBtn         : "Meklēt",\r
+DlgFindNotFoundMsg     : "Norādītā frāze netika atrasta.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Aizvietošana",\r
+DlgReplaceFindLbl              : "Meklēt:",\r
+DlgReplaceReplaceLbl   : "Nomainīt uz:",\r
+DlgReplaceCaseChk              : "Reģistrjūtīgs",\r
+DlgReplaceReplaceBtn   : "Aizvietot",\r
+DlgReplaceReplAllBtn   : "Aizvietot visu",\r
+DlgReplaceWordChk              : "Jāsakrīt pilnībā",\r
+\r
+// Paste Operations / Dialog\r
+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.",\r
+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.",\r
+\r
+PasteAsText            : "Ievietot kā vienkāršu tekstu",\r
+PasteFromWord  : "Ievietot no Worda",\r
+\r
+DlgPasteMsg2   : "Lūdzu, ievietojiet tekstu šajā laukumā, izmantojot klaviatūru (<STRONG>Ctrl+V</STRONG>) un apstipriniet ar <STRONG>Darīts!</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorēt iepriekš norādītos fontus",\r
+DlgPasteRemoveStyles   : "Noņemt norādītos stilus",\r
+DlgPasteCleanBox               : "Apstrādāt laukuma saturu",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automātiska",\r
+ColorMoreColors        : "Plašāka palete...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumenta īpašības",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Iezīmes īpašības",\r
+DlgAnchorName          : "Iezīmes nosaukums",\r
+DlgAnchorErrorName     : "Lūdzu norādiet iezīmes nosaukumu",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Netika atrasts vārdnīcā",\r
+DlgSpellChangeTo               : "Nomainīt uz",\r
+DlgSpellBtnIgnore              : "Ignorēt",\r
+DlgSpellBtnIgnoreAll   : "Ignorēt visu",\r
+DlgSpellBtnReplace             : "Aizvietot",\r
+DlgSpellBtnReplaceAll  : "Aizvietot visu",\r
+DlgSpellBtnUndo                        : "Atcelt",\r
+DlgSpellNoSuggestions  : "- Nav ieteikumu -",\r
+DlgSpellProgress               : "Notiek pareizrakstības pārbaude...",\r
+DlgSpellNoMispell              : "Pareizrakstības pārbaude pabeigta: kļūdas netika atrastas",\r
+DlgSpellNoChanges              : "Pareizrakstības pārbaude pabeigta: nekas netika labots",\r
+DlgSpellOneChange              : "Pareizrakstības pārbaude pabeigta: 1 vārds izmainīts",\r
+DlgSpellManyChanges            : "Pareizrakstības pārbaude pabeigta: %1 vārdi tika mainīti",\r
+\r
+IeSpellDownload                        : "Pareizrakstības pārbaudītājs nav pievienots. Vai vēlaties to lejupielādēt tagad?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Teksts (vērtība)",\r
+DlgButtonType          : "Tips",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nosaukums",\r
+DlgCheckboxValue       : "Vērtība",\r
+DlgCheckboxSelected    : "Iezīmēts",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nosaukums",\r
+DlgFormAction  : "Darbība",\r
+DlgFormMethod  : "Metode",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nosaukums",\r
+DlgSelectValue         : "Vērtība",\r
+DlgSelectSize          : "Izmērs",\r
+DlgSelectLines         : "rindas",\r
+DlgSelectChkMulti      : "Atļaut vairākus iezīmējumus",\r
+DlgSelectOpAvail       : "Pieejamās iespējas",\r
+DlgSelectOpText                : "Teksts",\r
+DlgSelectOpValue       : "Vērtība",\r
+DlgSelectBtnAdd                : "Pievienot",\r
+DlgSelectBtnModify     : "Veikt izmaiņas",\r
+DlgSelectBtnUp         : "Augšup",\r
+DlgSelectBtnDown       : "Lejup",\r
+DlgSelectBtnSetValue : "Noteikt kā iezīmēto vērtību",\r
+DlgSelectBtnDelete     : "Dzēst",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nosaukums",\r
+DlgTextareaCols        : "Kolonnas",\r
+DlgTextareaRows        : "Rindas",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nosaukums",\r
+DlgTextValue           : "Vērtība",\r
+DlgTextCharWidth       : "Simbolu platums",\r
+DlgTextMaxChars                : "Simbolu maksimālais daudzums",\r
+DlgTextType                    : "Tips",\r
+DlgTextTypeText                : "Teksts",\r
+DlgTextTypePass                : "Parole",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nosaukums",\r
+DlgHiddenValue : "Vērtība",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Aizzīmju saraksta īpašības",\r
+NumberedListProp       : "Numerētā saraksta īpašības",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Tips",\r
+DlgLstTypeCircle       : "Aplis",\r
+DlgLstTypeDisc         : "Disks",\r
+DlgLstTypeSquare       : "Kvadrāts",\r
+DlgLstTypeNumbers      : "Skaitļi (1, 2, 3)",\r
+DlgLstTypeLCase                : "Maziem burtiem (a, b, c)",\r
+DlgLstTypeUCase                : "Lieliem burtiem (A, B, C)",\r
+DlgLstTypeSRoman       : "Maziem romiešu cipariem (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Lieliem romiešu cipariem (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Vispārīga informācija",\r
+DlgDocBackTab          : "Fons",\r
+DlgDocColorsTab                : "Krāsas un robežu nobīdes",\r
+DlgDocMetaTab          : "META dati",\r
+\r
+DlgDocPageTitle                : "Dokumenta virsraksts <Title>",\r
+DlgDocLangDir          : "Valodas lasīšanas virziens",\r
+DlgDocLangDirLTR       : "No kreisās uz labo (LTR)",\r
+DlgDocLangDirRTL       : "No labās uz kreiso (RTL)",\r
+DlgDocLangCode         : "Valodas kods",\r
+DlgDocCharSet          : "Simbolu kodējums",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Cits simbolu kodējums",\r
+\r
+DlgDocDocType          : "Dokumenta tips",\r
+DlgDocDocTypeOther     : "Cits dokumenta tips",\r
+DlgDocIncXHTML         : "Ietvert XHTML deklarācijas",\r
+DlgDocBgColor          : "Fona krāsa",\r
+DlgDocBgImage          : "Fona attēla hipersaite",\r
+DlgDocBgNoScroll       : "Fona attēls ir fiksēts",\r
+DlgDocCText                    : "Teksts",\r
+DlgDocCLink                    : "Hipersaite",\r
+DlgDocCVisited         : "Apmeklēta hipersaite",\r
+DlgDocCActive          : "Aktīva hipersaite",\r
+DlgDocMargins          : "Lapas robežas",\r
+DlgDocMaTop                    : "Augšā",\r
+DlgDocMaLeft           : "Pa kreisi",\r
+DlgDocMaRight          : "Pa labi",\r
+DlgDocMaBottom         : "Apakšā",\r
+DlgDocMeIndex          : "Dokumentu aprakstoši atslēgvārdi (atdalīti ar komatu)",\r
+DlgDocMeDescr          : "Dokumenta apraksts",\r
+DlgDocMeAuthor         : "Autors",\r
+DlgDocMeCopy           : "Autortiesības",\r
+DlgDocPreview          : "Priekšskats",\r
+\r
+// Templates Dialog\r
+Templates                      : "Sagataves",\r
+DlgTemplatesTitle      : "Satura sagataves",\r
+DlgTemplatesSelMsg     : "Lūdzu, norādiet sagatavi, ko atvērt editorā<br>(patreizējie dati tiks zaudēti):",\r
+DlgTemplatesLoading    : "Notiek sagatavju saraksta ielāde. Lūdzu, uzgaidiet...",\r
+DlgTemplatesNoTpl      : "(Nav norādītas sagataves)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Par",\r
+DlgAboutBrowserInfoTab : "Informācija par pārlūkprogrammu",\r
+DlgAboutLicenseTab     : "Licence",\r
+DlgAboutVersion                : "versija",\r
+DlgAboutInfo           : "Papildus informācija ir pieejama"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/mn.js b/httemplate/elements/fckeditor/editor/lang/mn.js
new file mode 100644 (file)
index 0000000..ba8f798
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Mongolian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Багажны хэсэг эвдэх",\r
+ToolbarExpand          : "Багажны хэсэг өргөтгөх",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Хадгалах",\r
+NewPage                                : "Шинэ хуудас",\r
+Preview                                : "Уридчлан харах",\r
+Cut                                    : "Хайчлах",\r
+Copy                           : "Хуулах",\r
+Paste                          : "Буулгах",\r
+PasteText                      : "plain text-ээс буулгах",\r
+PasteWord                      : "Word-оос буулгах",\r
+Print                          : "Хэвлэх",\r
+SelectAll                      : "Бүгдийг нь сонгох",\r
+RemoveFormat           : "Формат авч хаях",\r
+InsertLinkLbl          : "Линк",\r
+InsertLink                     : "Линк Оруулах/Засварлах",\r
+RemoveLink                     : "Линк авч хаях",\r
+Anchor                         : "Insert/Edit Anchor", //MISSING\r
+InsertImageLbl         : "Зураг",\r
+InsertImage                    : "Зураг Оруулах/Засварлах",\r
+InsertFlashLbl         : "Flash",      //MISSING\r
+InsertFlash                    : "Insert/Edit Flash",  //MISSING\r
+InsertTableLbl         : "Хүснэгт",\r
+InsertTable                    : "Хүснэгт Оруулах/Засварлах",\r
+InsertLineLbl          : "Зураас",\r
+InsertLine                     : "Хөндлөн зураас оруулах",\r
+InsertSpecialCharLbl: "Онцгой тэмдэгт",\r
+InsertSpecialChar      : "Онцгой тэмдэгт оруулах",\r
+InsertSmileyLbl                : "Тодорхойлолт",\r
+InsertSmiley           : "Тодорхойлолт оруулах",\r
+About                          : "FCKeditor-н тухай",\r
+Bold                           : "Тод бүдүүн",\r
+Italic                         : "Налуу",\r
+Underline                      : "Доогуур нь зураастай болгох",\r
+StrikeThrough          : "Дундуур нь зураастай болгох",\r
+Subscript                      : "Суурь болгох",\r
+Superscript                    : "Зэрэг болгох",\r
+LeftJustify                    : "Зүүн талд байрлуулах",\r
+CenterJustify          : "Төвд байрлуулах",\r
+RightJustify           : "Баруун талд байрлуулах",\r
+BlockJustify           : "Блок хэлбэрээр байрлуулах",\r
+DecreaseIndent         : "Догол мөр нэмэх",\r
+IncreaseIndent         : "Догол мөр хасах",\r
+Undo                           : "Хүчингүй болгох",\r
+Redo                           : "Өмнөх үйлдлээ сэргээх",\r
+NumberedListLbl                : "Дугаарлагдсан жагсаалт",\r
+NumberedList           : "Дугаарлагдсан жагсаалт Оруулах/Авах",\r
+BulletedListLbl                : "Цэгтэй жагсаалт",\r
+BulletedList           : "Цэгтэй жагсаалт Оруулах/Авах",\r
+ShowTableBorders       : "Хүснэгтийн хүрээг үзүүлэх",\r
+ShowDetails                    : "Деталчлан үзүүлэх",\r
+Style                          : "Загвар",\r
+FontFormat                     : "Формат",\r
+Font                           : "Фонт",\r
+FontSize                       : "Хэмжээ",\r
+TextColor                      : "Фонтны өнгө",\r
+BGColor                                : "Фонны өнгө",\r
+Source                         : "Код",\r
+Find                           : "Хайх",\r
+Replace                                : "Солих",\r
+SpellCheck                     : "Check Spelling",     //MISSING\r
+UniversalKeyboard      : "Universal Keyboard", //MISSING\r
+PageBreakLbl           : "Page Break", //MISSING\r
+PageBreak                      : "Insert Page Break",  //MISSING\r
+\r
+Form                   : "Form",       //MISSING\r
+Checkbox               : "Checkbox",   //MISSING\r
+RadioButton            : "Radio Button",       //MISSING\r
+TextField              : "Text Field", //MISSING\r
+Textarea               : "Textarea",   //MISSING\r
+HiddenField            : "Hidden Field",       //MISSING\r
+Button                 : "Button",     //MISSING\r
+SelectionField : "Selection Field",    //MISSING\r
+ImageButton            : "Image Button",       //MISSING\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Холбоос засварлах",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Мөр оруулах",\r
+DeleteRows                     : "Мөр устгах",\r
+InsertColumn           : "Багана оруулах",\r
+DeleteColumns          : "Багана устгах",\r
+InsertCell                     : "Нүх оруулах",\r
+DeleteCells                    : "Нүх устгах",\r
+MergeCells                     : "Нүх нэгтэх",\r
+SplitCell                      : "Нүх тусгайрлах",\r
+TableDelete                    : "Delete Table",       //MISSING\r
+CellProperties         : "Хоосон зайн шинж чанар",\r
+TableProperties                : "Хүснэгт",\r
+ImageProperties                : "Зураг",\r
+FlashProperties                : "Flash Properties",   //MISSING\r
+\r
+AnchorProp                     : "Anchor Properties",  //MISSING\r
+ButtonProp                     : "Button Properties",  //MISSING\r
+CheckboxProp           : "Checkbox Properties",        //MISSING\r
+HiddenFieldProp                : "Hidden Field Properties",    //MISSING\r
+RadioButtonProp                : "Radio Button Properties",    //MISSING\r
+ImageButtonProp                : "Image Button Properties",    //MISSING\r
+TextFieldProp          : "Text Field Properties",      //MISSING\r
+SelectionFieldProp     : "Selection Field Properties", //MISSING\r
+TextareaProp           : "Textarea Properties",        //MISSING\r
+FormProp                       : "Form Properties",    //MISSING\r
+\r
+FontFormats                    : "Хэвийн;Formatted;Хаяг;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Paragraph (DIV)",                //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML үйл явц явагдаж байна. Хүлээнэ үү...",\r
+Done                           : "Хийх",\r
+PasteWordConfirm       : "Word-оос хуулсан текстээ санаж байгааг нь буулгахыг та хүсч байна уу. Та текст-ээ буулгахын өмнө цэвэрлэх үү?",\r
+NotCompatiblePaste     : "Энэ комманд Internet Explorer-ын 5.5 буюу түүнээс дээш хувилбарт идвэхшинэ. Та цэвэрлэхгүйгээр буулгахыг хүсч байна?",\r
+UnknownToolbarItem     : "Багажны хэсгийн \"%1\" item мэдэгдэхгүй байна",\r
+UnknownCommand         : "\"%1\" комманд нэр мэдагдэхгүй байна",\r
+NotImplemented         : "Зөвшөөрөгдөхгүй комманд",\r
+UnknownToolbarSet      : "Багажны хэсэгт \"%1\" оноох, үүсээгүй байна",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Болих",\r
+DlgBtnClose                    : "Хаах",\r
+DlgBtnBrowseServer     : "Browse Server",      //MISSING\r
+DlgAdvancedTag         : "Нэмэлт",\r
+DlgOpOther                     : "<Other>",    //MISSING\r
+DlgInfoTab                     : "Info",       //MISSING\r
+DlgAlertUrl                    : "Please insert the URL",      //MISSING\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<Оноохгүй>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Хэлний чиглэл",\r
+DlgGenLangDirLtr       : "Зүүнээс баруун (LTR)",\r
+DlgGenLangDirRtl       : "Баруунаас зүүн (RTL)",\r
+DlgGenLangCode         : "Хэлний код",\r
+DlgGenAccessKey                : "Холбох түлхүүр",\r
+DlgGenName                     : "Нэр",\r
+DlgGenTabIndex         : "Tab индекс",\r
+DlgGenLongDescr                : "URL-ын тайлбар",\r
+DlgGenClass                    : "Stylesheet классууд",\r
+DlgGenTitle                    : "Зөвлөлдөх гарчиг",\r
+DlgGenContType         : "Зөвлөлдөх төрлийн агуулга",\r
+DlgGenLinkCharset      : "Тэмдэгт оноох нөөцөд холбогдсон",\r
+DlgGenStyle                    : "Загвар",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Зураг",\r
+DlgImgInfoTab          : "Зурагны мэдээлэл",\r
+DlgImgBtnUpload                : "Үүнийг сервэррүү илгээ",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Хуулах",\r
+DlgImgAlt                      : "Тайлбар текст",\r
+DlgImgWidth                    : "Өргөн",\r
+DlgImgHeight           : "Өндөр",\r
+DlgImgLockRatio                : "Lock Ratio",\r
+DlgBtnResetSize                : "хэмжээ дахин оноох",\r
+DlgImgBorder           : "Хүрээ",\r
+DlgImgHSpace           : "Хөндлөн зай",\r
+DlgImgVSpace           : "Босоо зай",\r
+DlgImgAlign                    : "Эгнээ",\r
+DlgImgAlignLeft                : "Зүүн",\r
+DlgImgAlignAbsBottom: "Abs доод талд",\r
+DlgImgAlignAbsMiddle: "Abs Дунд талд",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Доод талд",\r
+DlgImgAlignMiddle      : "Дунд талд",\r
+DlgImgAlignRight       : "Баруун",\r
+DlgImgAlignTextTop     : "Текст дээр",\r
+DlgImgAlignTop         : "Дээд талд",\r
+DlgImgPreview          : "Уридчлан харах",\r
+DlgImgAlertUrl         : "Зурагны URL-ын төрлийн сонгоно уу",\r
+DlgImgLinkTab          : "Link",       //MISSING\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Properties",   //MISSING\r
+DlgFlashChkPlay                : "Auto Play",  //MISSING\r
+DlgFlashChkLoop                : "Loop",       //MISSING\r
+DlgFlashChkMenu                : "Enable Flash Menu",  //MISSING\r
+DlgFlashScale          : "Scale",      //MISSING\r
+DlgFlashScaleAll       : "Show all",   //MISSING\r
+DlgFlashScaleNoBorder  : "No Border",  //MISSING\r
+DlgFlashScaleFit       : "Exact Fit",  //MISSING\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Линк",\r
+DlgLnkInfoTab          : "Линкийн мэдээлэл",\r
+DlgLnkTargetTab                : "Байрлал",\r
+\r
+DlgLnkType                     : "Линкийн төрөл",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Энэ хуудасандах холбоос",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Протокол",\r
+DlgLnkProtoOther       : "<бусад>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Холбоос сонгох",\r
+DlgLnkAnchorByName     : "Холбоосын нэрээр",\r
+DlgLnkAnchorById       : "Элемэнт Id-гаар",\r
+DlgLnkNoAnchors                : "<Баримт бичиг холбоосгүй байна>",          //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail Хаяг",\r
+DlgLnkEMailSubject     : "Message Subject",\r
+DlgLnkEMailBody                : "Message-ийн агуулга",\r
+DlgLnkUpload           : "Хуулах",\r
+DlgLnkBtnUpload                : "Үүнийг серверрүү илгээ",\r
+\r
+DlgLnkTarget           : "Байрлал",\r
+DlgLnkTargetFrame      : "<Агуулах хүрээ>",\r
+DlgLnkTargetPopup      : "<popup цонх>",\r
+DlgLnkTargetBlank      : "Шинэ цонх (_blank)",\r
+DlgLnkTargetParent     : "Эцэг цонх (_parent)",\r
+DlgLnkTargetSelf       : "Төстэй цонх (_self)",\r
+DlgLnkTargetTop                : "Хамгийн түрүүн байх цонх (_top)",\r
+DlgLnkTargetFrameName  : "Target Frame Name",  //MISSING\r
+DlgLnkPopWinName       : "Popup цонхны нэр",\r
+DlgLnkPopWinFeat       : "Popup цонхны онцлог",\r
+DlgLnkPopResize                : "Хэмжээ өөрчлөх",\r
+DlgLnkPopLocation      : "Location хэсэг",\r
+DlgLnkPopMenu          : "Meню хэсэг",\r
+DlgLnkPopScroll                : "Скрол хэсэгүүд",\r
+DlgLnkPopStatus                : "Статус хэсэг",\r
+DlgLnkPopToolbar       : "Багажны хэсэг",\r
+DlgLnkPopFullScrn      : "Цонх дүүргэх (IE)",\r
+DlgLnkPopDependent     : "Хамаатай (Netscape)",\r
+DlgLnkPopWidth         : "Өргөн",\r
+DlgLnkPopHeight                : "Өндөр",\r
+DlgLnkPopLeft          : "Зүүн байрлал",\r
+DlgLnkPopTop           : "Дээд байрлал",\r
+\r
+DlnLnkMsgNoUrl         : "Линк URL-ээ төрөлжүүлнэ үү",\r
+DlnLnkMsgNoEMail       : "Е-mail хаягаа төрөлжүүлнэ үү",\r
+DlnLnkMsgNoAnchor      : "Холбоосоо сонгоно уу",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Өнгө сонгох",\r
+DlgColorBtnClear       : "Цэвэрлэх",\r
+DlgColorHighlight      : "Өнгө",\r
+DlgColorSelected       : "Сонгогдсон",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Тодорхойлолт оруулах",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Онцгой тэмдэгт сонгох",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Хүснэгт",\r
+DlgTableRows           : "Мөр",\r
+DlgTableColumns                : "Багана",\r
+DlgTableBorder         : "Хүрээний хэмжээ",\r
+DlgTableAlign          : "Эгнээ",\r
+DlgTableAlignNotSet    : "<Оноохгүй>",\r
+DlgTableAlignLeft      : "Зүүн талд",\r
+DlgTableAlignCenter    : "Төвд",\r
+DlgTableAlignRight     : "Баруун талд",\r
+DlgTableWidth          : "Өргөн",\r
+DlgTableWidthPx                : "цэг",\r
+DlgTableWidthPc                : "хувь",\r
+DlgTableHeight         : "Өндөр",\r
+DlgTableCellSpace      : "Нүх хоорондын зай",\r
+DlgTableCellPad                : "Нүх доторлох",\r
+DlgTableCaption                : "Тайлбар",\r
+DlgTableSummary                : "Summary",    //MISSING\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Хоосон зайн шинж чанар",\r
+DlgCellWidth           : "Өргөн",\r
+DlgCellWidthPx         : "цэг",\r
+DlgCellWidthPc         : "хувь",\r
+DlgCellHeight          : "Өндөр",\r
+DlgCellWordWrap                : "Үг таслах",\r
+DlgCellWordWrapNotSet  : "<Оноохгүй>",\r
+DlgCellWordWrapYes     : "Тийм",\r
+DlgCellWordWrapNo      : "Үгүй",\r
+DlgCellHorAlign                : "Босоо эгнээ",\r
+DlgCellHorAlignNotSet  : "<Оноохгүй>",\r
+DlgCellHorAlignLeft    : "Зүүн",\r
+DlgCellHorAlignCenter  : "Төв",\r
+DlgCellHorAlignRight: "Баруун",\r
+DlgCellVerAlign                : "Хөндлөн эгнээ",\r
+DlgCellVerAlignNotSet  : "<Оноохгүй>",\r
+DlgCellVerAlignTop     : "Дээд тал",\r
+DlgCellVerAlignMiddle  : "Дунд",\r
+DlgCellVerAlignBottom  : "Доод тал",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Нийт мөр",\r
+DlgCellCollSpan                : "Нийт багана",\r
+DlgCellBackColor       : "Фонны өнгө",\r
+DlgCellBorderColor     : "Хүрээний өнгө",\r
+DlgCellBtnSelect       : "Сонго...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Хайх",\r
+DlgFindFindBtn         : "Хайх",\r
+DlgFindNotFoundMsg     : "Хайсан текст олсонгүй.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Солих",\r
+DlgReplaceFindLbl              : "Хайх үг/үсэг:",\r
+DlgReplaceReplaceLbl   : "Солих үг:",\r
+DlgReplaceCaseChk              : "Тэнцэх төлөв",\r
+DlgReplaceReplaceBtn   : "Солих",\r
+DlgReplaceReplAllBtn   : "Бүгдийг нь Солих",\r
+DlgReplaceWordChk              : "Тэнцэх бүтэн үг",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Таны browser-ын хамгаалалтын тохиргоо editor-д автоматаар хайчлах үйлдэлийг зөвшөөрөхгүй байна. (Ctrl+X) товчны хослолыг ашиглана уу.",\r
+PasteErrorCopy : "Таны browser-ын хамгаалалтын тохиргоо editor-д автоматаар хуулах үйлдэлийг зөвшөөрөхгүй байна. (Ctrl+C) товчны хослолыг ашиглана уу.",\r
+\r
+PasteAsText            : "Plain Text-ээс буулгах",\r
+PasteFromWord  : "Word-оос буулгах",\r
+\r
+DlgPasteMsg2   : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",    //MISSING\r
+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\r
+DlgPasteIgnoreFont             : "Ignore Font Face definitions",       //MISSING\r
+DlgPasteRemoveStyles   : "Remove Styles definitions",  //MISSING\r
+DlgPasteCleanBox               : "Clean Up Box",       //MISSING\r
+\r
+// Color Picker\r
+ColorAutomatic : "Автоматаар",\r
+ColorMoreColors        : "Нэмэлт өнгөнүүд...",\r
+\r
+// Document Properties\r
+DocProps               : "Document Properties",        //MISSING\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Anchor Properties",  //MISSING\r
+DlgAnchorName          : "Anchor Name",        //MISSING\r
+DlgAnchorErrorName     : "Please type the anchor name",        //MISSING\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Not in dictionary",  //MISSING\r
+DlgSpellChangeTo               : "Change to",  //MISSING\r
+DlgSpellBtnIgnore              : "Ignore",     //MISSING\r
+DlgSpellBtnIgnoreAll   : "Ignore All", //MISSING\r
+DlgSpellBtnReplace             : "Replace",    //MISSING\r
+DlgSpellBtnReplaceAll  : "Replace All",        //MISSING\r
+DlgSpellBtnUndo                        : "Undo",       //MISSING\r
+DlgSpellNoSuggestions  : "- No suggestions -", //MISSING\r
+DlgSpellProgress               : "Spell check in progress...", //MISSING\r
+DlgSpellNoMispell              : "Spell check complete: No misspellings found",        //MISSING\r
+DlgSpellNoChanges              : "Spell check complete: No words changed",     //MISSING\r
+DlgSpellOneChange              : "Spell check complete: One word changed",     //MISSING\r
+DlgSpellManyChanges            : "Spell check complete: %1 words changed",     //MISSING\r
+\r
+IeSpellDownload                        : "Spell checker not installed. Do you want to download it now?",       //MISSING\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Value)",       //MISSING\r
+DlgButtonType          : "Type",       //MISSING\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Name",       //MISSING\r
+DlgCheckboxValue       : "Value",      //MISSING\r
+DlgCheckboxSelected    : "Selected",   //MISSING\r
+\r
+// Form Dialog\r
+DlgFormName            : "Name",       //MISSING\r
+DlgFormAction  : "Action",     //MISSING\r
+DlgFormMethod  : "Method",     //MISSING\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Name",       //MISSING\r
+DlgSelectValue         : "Value",      //MISSING\r
+DlgSelectSize          : "Size",       //MISSING\r
+DlgSelectLines         : "lines",      //MISSING\r
+DlgSelectChkMulti      : "Allow multiple selections",  //MISSING\r
+DlgSelectOpAvail       : "Available Options",  //MISSING\r
+DlgSelectOpText                : "Text",       //MISSING\r
+DlgSelectOpValue       : "Value",      //MISSING\r
+DlgSelectBtnAdd                : "Add",        //MISSING\r
+DlgSelectBtnModify     : "Modify",     //MISSING\r
+DlgSelectBtnUp         : "Up", //MISSING\r
+DlgSelectBtnDown       : "Down",       //MISSING\r
+DlgSelectBtnSetValue : "Set as selected value",        //MISSING\r
+DlgSelectBtnDelete     : "Delete",     //MISSING\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Name",       //MISSING\r
+DlgTextareaCols        : "Columns",    //MISSING\r
+DlgTextareaRows        : "Rows",       //MISSING\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Name",       //MISSING\r
+DlgTextValue           : "Value",      //MISSING\r
+DlgTextCharWidth       : "Character Width",    //MISSING\r
+DlgTextMaxChars                : "Maximum Characters", //MISSING\r
+DlgTextType                    : "Type",       //MISSING\r
+DlgTextTypeText                : "Text",       //MISSING\r
+DlgTextTypePass                : "Password",   //MISSING\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Name",       //MISSING\r
+DlgHiddenValue : "Value",      //MISSING\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Bulleted List Properties",   //MISSING\r
+NumberedListProp       : "Numbered List Properties",   //MISSING\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Type",       //MISSING\r
+DlgLstTypeCircle       : "Circle",     //MISSING\r
+DlgLstTypeDisc         : "Disc",       //MISSING\r
+DlgLstTypeSquare       : "Square",     //MISSING\r
+DlgLstTypeNumbers      : "Numbers (1, 2, 3)",  //MISSING\r
+DlgLstTypeLCase                : "Lowercase Letters (a, b, c)",        //MISSING\r
+DlgLstTypeUCase                : "Uppercase Letters (A, B, C)",        //MISSING\r
+DlgLstTypeSRoman       : "Small Roman Numerals (i, ii, iii)",  //MISSING\r
+DlgLstTypeLRoman       : "Large Roman Numerals (I, II, III)",  //MISSING\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",    //MISSING\r
+DlgDocBackTab          : "Background", //MISSING\r
+DlgDocColorsTab                : "Colors and Margins", //MISSING\r
+DlgDocMetaTab          : "Meta Data",  //MISSING\r
+\r
+DlgDocPageTitle                : "Page Title", //MISSING\r
+DlgDocLangDir          : "Language Direction", //MISSING\r
+DlgDocLangDirLTR       : "Left to Right (LTR)",        //MISSING\r
+DlgDocLangDirRTL       : "Right to Left (RTL)",        //MISSING\r
+DlgDocLangCode         : "Language Code",      //MISSING\r
+DlgDocCharSet          : "Character Set Encoding",     //MISSING\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Other Character Set Encoding",       //MISSING\r
+\r
+DlgDocDocType          : "Document Type Heading",      //MISSING\r
+DlgDocDocTypeOther     : "Other Document Type Heading",        //MISSING\r
+DlgDocIncXHTML         : "Include XHTML Declarations", //MISSING\r
+DlgDocBgColor          : "Background Color",   //MISSING\r
+DlgDocBgImage          : "Background Image URL",       //MISSING\r
+DlgDocBgNoScroll       : "Nonscrolling Background",    //MISSING\r
+DlgDocCText                    : "Text",       //MISSING\r
+DlgDocCLink                    : "Link",       //MISSING\r
+DlgDocCVisited         : "Visited Link",       //MISSING\r
+DlgDocCActive          : "Active Link",        //MISSING\r
+DlgDocMargins          : "Page Margins",       //MISSING\r
+DlgDocMaTop                    : "Top",        //MISSING\r
+DlgDocMaLeft           : "Left",       //MISSING\r
+DlgDocMaRight          : "Right",      //MISSING\r
+DlgDocMaBottom         : "Bottom",     //MISSING\r
+DlgDocMeIndex          : "Document Indexing Keywords (comma separated)",       //MISSING\r
+DlgDocMeDescr          : "Document Description",       //MISSING\r
+DlgDocMeAuthor         : "Author",     //MISSING\r
+DlgDocMeCopy           : "Copyright",  //MISSING\r
+DlgDocPreview          : "Preview",    //MISSING\r
+\r
+// Templates Dialog\r
+Templates                      : "Templates",  //MISSING\r
+DlgTemplatesTitle      : "Content Templates",  //MISSING\r
+DlgTemplatesSelMsg     : "Please select the template to open in the editor<br />(the actual contents will be lost):",  //MISSING\r
+DlgTemplatesLoading    : "Loading templates list. Please wait...",     //MISSING\r
+DlgTemplatesNoTpl      : "(No templates defined)",     //MISSING\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "About",      //MISSING\r
+DlgAboutBrowserInfoTab : "Browser Info",       //MISSING\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "Хувилбар",\r
+DlgAboutInfo           : "Мэдээллээр туслах"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/ms.js b/httemplate/elements/fckeditor/editor/lang/ms.js
new file mode 100644 (file)
index 0000000..efe0529
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Malay language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Collapse Toolbar",\r
+ToolbarExpand          : "Expand Toolbar",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Simpan",\r
+NewPage                                : "Helaian Baru",\r
+Preview                                : "Prebiu",\r
+Cut                                    : "Potong",\r
+Copy                           : "Salin",\r
+Paste                          : "Tampal",\r
+PasteText                      : "Tampal sebagai Text Biasa",\r
+PasteWord                      : "Tampal dari Word",\r
+Print                          : "Cetak",\r
+SelectAll                      : "Pilih Semua",\r
+RemoveFormat           : "Buang Format",\r
+InsertLinkLbl          : "Sambungan",\r
+InsertLink                     : "Masukkan/Sunting Sambungan",\r
+RemoveLink                     : "Buang Sambungan",\r
+Anchor                         : "Masukkan/Sunting Pautan",\r
+InsertImageLbl         : "Gambar",\r
+InsertImage                    : "Masukkan/Sunting Gambar",\r
+InsertFlashLbl         : "Flash",      //MISSING\r
+InsertFlash                    : "Insert/Edit Flash",  //MISSING\r
+InsertTableLbl         : "Jadual",\r
+InsertTable                    : "Masukkan/Sunting Jadual",\r
+InsertLineLbl          : "Garisan",\r
+InsertLine                     : "Masukkan Garisan Membujur",\r
+InsertSpecialCharLbl: "Huruf Istimewa",\r
+InsertSpecialChar      : "Masukkan Huruf Istimewa",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Masukkan Smiley",\r
+About                          : "Tentang FCKeditor",\r
+Bold                           : "Bold",\r
+Italic                         : "Italic",\r
+Underline                      : "Underline",\r
+StrikeThrough          : "Strike Through",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Jajaran Kiri",\r
+CenterJustify          : "Jajaran Tengah",\r
+RightJustify           : "Jajaran Kanan",\r
+BlockJustify           : "Jajaran Blok",\r
+DecreaseIndent         : "Kurangkan Inden",\r
+IncreaseIndent         : "Tambahkan Inden",\r
+Undo                           : "Batalkan",\r
+Redo                           : "Ulangkan",\r
+NumberedListLbl                : "Senarai bernombor",\r
+NumberedList           : "Masukkan/Sunting Senarai bernombor",\r
+BulletedListLbl                : "Senarai tidak bernombor",\r
+BulletedList           : "Masukkan/Sunting Senarai tidak bernombor",\r
+ShowTableBorders       : "Tunjukkan Border Jadual",\r
+ShowDetails                    : "Tunjukkan Butiran",\r
+Style                          : "Stail",\r
+FontFormat                     : "Format",\r
+Font                           : "Font",\r
+FontSize                       : "Saiz",\r
+TextColor                      : "Warna Text",\r
+BGColor                                : "Warna Latarbelakang",\r
+Source                         : "Sumber",\r
+Find                           : "Cari",\r
+Replace                                : "Ganti",\r
+SpellCheck                     : "Semak Ejaan",\r
+UniversalKeyboard      : "Papan Kekunci Universal",\r
+PageBreakLbl           : "Page Break", //MISSING\r
+PageBreak                      : "Insert Page Break",  //MISSING\r
+\r
+Form                   : "Borang",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Butang Radio",\r
+TextField              : "Text Field",\r
+Textarea               : "Textarea",\r
+HiddenField            : "Field Tersembunyi",\r
+Button                 : "Butang",\r
+SelectionField : "Field Pilihan",\r
+ImageButton            : "Butang Bergambar",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Sunting Sambungan",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Masukkan Baris",\r
+DeleteRows                     : "Buangkan Baris",\r
+InsertColumn           : "Masukkan Lajur",\r
+DeleteColumns          : "Buangkan Lajur",\r
+InsertCell                     : "Masukkan Sel",\r
+DeleteCells                    : "Buangkan Sel-sel",\r
+MergeCells                     : "Cantumkan Sel-sel",\r
+SplitCell                      : "Bahagikan Sel",\r
+TableDelete                    : "Delete Table",       //MISSING\r
+CellProperties         : "Ciri-ciri Sel",\r
+TableProperties                : "Ciri-ciri Jadual",\r
+ImageProperties                : "Ciri-ciri Gambar",\r
+FlashProperties                : "Flash Properties",   //MISSING\r
+\r
+AnchorProp                     : "Ciri-ciri Pautan",\r
+ButtonProp                     : "Ciri-ciri Butang",\r
+CheckboxProp           : "Ciri-ciri Checkbox",\r
+HiddenFieldProp                : "Ciri-ciri Field Tersembunyi",\r
+RadioButtonProp                : "Ciri-ciri Butang Radio",\r
+ImageButtonProp                : "Ciri-ciri Butang Bergambar",\r
+TextFieldProp          : "Ciri-ciri Text Field",\r
+SelectionFieldProp     : "Ciri-ciri Selection Field",\r
+TextareaProp           : "Ciri-ciri Textarea",\r
+FormProp                       : "Ciri-ciri Borang",\r
+\r
+FontFormats                    : "Normal;Telah Diformat;Alamat;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Perenggan (DIV)",           //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Memproses XHTML. Sila tunggu...",\r
+Done                           : "Siap",\r
+PasteWordConfirm       : "Text yang anda hendak tampal adalah berasal dari Word. Adakah anda mahu membuang semua format Word sebelum tampal ke dalam text?",\r
+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?",\r
+UnknownToolbarItem     : "Toolbar item tidak diketahui\"%1\"",\r
+UnknownCommand         : "Arahan tidak diketahui \"%1\"",\r
+NotImplemented         : "Arahan tidak terdapat didalam sistem",\r
+UnknownToolbarSet      : "Set toolbar \"%1\" tidak wujud",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Batal",\r
+DlgBtnClose                    : "Tutup",\r
+DlgBtnBrowseServer     : "Browse Server",\r
+DlgAdvancedTag         : "Advanced",\r
+DlgOpOther                     : "<Lain-lain>",\r
+DlgInfoTab                     : "Info",       //MISSING\r
+DlgAlertUrl                    : "Please insert the URL",      //MISSING\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<tidak di set>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Arah Tulisan",\r
+DlgGenLangDirLtr       : "Kiri ke Kanan (LTR)",\r
+DlgGenLangDirRtl       : "Kanan ke Kiri (RTL)",\r
+DlgGenLangCode         : "Kod Bahasa",\r
+DlgGenAccessKey                : "Kunci Akses",\r
+DlgGenName                     : "Nama",\r
+DlgGenTabIndex         : "Indeks Tab ",\r
+DlgGenLongDescr                : "Butiran Panjang URL",\r
+DlgGenClass                    : "Kelas-kelas Stylesheet",\r
+DlgGenTitle                    : "Tajuk Makluman",\r
+DlgGenContType         : "Jenis Kandungan Makluman",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Stail",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Ciri-ciri Imej",\r
+DlgImgInfoTab          : "Info Imej",\r
+DlgImgBtnUpload                : "Hantar ke Server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Muat Naik",\r
+DlgImgAlt                      : "Text Alternatif",\r
+DlgImgWidth                    : "Lebar",\r
+DlgImgHeight           : "Tinggi",\r
+DlgImgLockRatio                : "Tetapkan Nisbah",\r
+DlgBtnResetSize                : "Saiz Set Semula",\r
+DlgImgBorder           : "Border",\r
+DlgImgHSpace           : "Ruang Melintang",\r
+DlgImgVSpace           : "Ruang Menegak",\r
+DlgImgAlign                    : "Jajaran",\r
+DlgImgAlignLeft                : "Kiri",\r
+DlgImgAlignAbsBottom: "Bawah Mutlak",\r
+DlgImgAlignAbsMiddle: "Pertengahan Mutlak",\r
+DlgImgAlignBaseline    : "Garis Dasar",\r
+DlgImgAlignBottom      : "Bawah",\r
+DlgImgAlignMiddle      : "Pertengahan",\r
+DlgImgAlignRight       : "Kanan",\r
+DlgImgAlignTextTop     : "Atas Text",\r
+DlgImgAlignTop         : "Atas",\r
+DlgImgPreview          : "Prebiu",\r
+DlgImgAlertUrl         : "Sila taip URL untuk fail gambar",\r
+DlgImgLinkTab          : "Sambungan",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Properties",   //MISSING\r
+DlgFlashChkPlay                : "Auto Play",  //MISSING\r
+DlgFlashChkLoop                : "Loop",       //MISSING\r
+DlgFlashChkMenu                : "Enable Flash Menu",  //MISSING\r
+DlgFlashScale          : "Scale",      //MISSING\r
+DlgFlashScaleAll       : "Show all",   //MISSING\r
+DlgFlashScaleNoBorder  : "No Border",  //MISSING\r
+DlgFlashScaleFit       : "Exact Fit",  //MISSING\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Sambungan",\r
+DlgLnkInfoTab          : "Butiran Sambungan",\r
+DlgLnkTargetTab                : "Sasaran",\r
+\r
+DlgLnkType                     : "Jenis Sambungan",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Pautan dalam muka surat ini",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<lain-lain>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Sila pilih pautan",\r
+DlgLnkAnchorByName     : "dengan menggunakan nama pautan",\r
+DlgLnkAnchorById       : "dengan menggunakan ID elemen",\r
+DlgLnkNoAnchors                : "<Tiada pautan terdapat dalam dokumen ini>",          //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Alamat E-Mail",\r
+DlgLnkEMailSubject     : "Subjek Mesej",\r
+DlgLnkEMailBody                : "Isi Kandungan Mesej",\r
+DlgLnkUpload           : "Muat Naik",\r
+DlgLnkBtnUpload                : "Hantar ke Server",\r
+\r
+DlgLnkTarget           : "Sasaran",\r
+DlgLnkTargetFrame      : "<bingkai>",\r
+DlgLnkTargetPopup      : "<tetingkap popup>",\r
+DlgLnkTargetBlank      : "Tetingkap Baru (_blank)",\r
+DlgLnkTargetParent     : "Tetingkap Parent (_parent)",\r
+DlgLnkTargetSelf       : "Tetingkap yang Sama (_self)",\r
+DlgLnkTargetTop                : "Tetingkap yang paling atas (_top)",\r
+DlgLnkTargetFrameName  : "Nama Bingkai Sasaran",\r
+DlgLnkPopWinName       : "Nama Tetingkap Popup",\r
+DlgLnkPopWinFeat       : "Ciri Tetingkap Popup",\r
+DlgLnkPopResize                : "Saiz bolehubah",\r
+DlgLnkPopLocation      : "Bar Lokasi",\r
+DlgLnkPopMenu          : "Bar Menu",\r
+DlgLnkPopScroll                : "Bar-bar skrol",\r
+DlgLnkPopStatus                : "Bar Status",\r
+DlgLnkPopToolbar       : "Toolbar",\r
+DlgLnkPopFullScrn      : "Skrin Penuh (IE)",\r
+DlgLnkPopDependent     : "Bergantungan (Netscape)",\r
+DlgLnkPopWidth         : "Lebar",\r
+DlgLnkPopHeight                : "Tinggi",\r
+DlgLnkPopLeft          : "Posisi Kiri",\r
+DlgLnkPopTop           : "Posisi Atas",\r
+\r
+DlnLnkMsgNoUrl         : "Sila taip sambungan URL",\r
+DlnLnkMsgNoEMail       : "Sila taip alamat e-mail",\r
+DlnLnkMsgNoAnchor      : "Sila pilih pautan berkenaaan",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Pilihan Warna",\r
+DlgColorBtnClear       : "Nyahwarna",\r
+DlgColorHighlight      : "Terang",\r
+DlgColorSelected       : "Dipilih",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Masukkan Smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Sila pilih huruf istimewa",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Ciri-ciri Jadual",\r
+DlgTableRows           : "Barisan",\r
+DlgTableColumns                : "Jaluran",\r
+DlgTableBorder         : "Saiz Border",\r
+DlgTableAlign          : "Penjajaran",\r
+DlgTableAlignNotSet    : "<Tidak diset>",\r
+DlgTableAlignLeft      : "Kiri",\r
+DlgTableAlignCenter    : "Tengah",\r
+DlgTableAlignRight     : "Kanan",\r
+DlgTableWidth          : "Lebar",\r
+DlgTableWidthPx                : "piksel-piksel",\r
+DlgTableWidthPc                : "peratus",\r
+DlgTableHeight         : "Tinggi",\r
+DlgTableCellSpace      : "Ruangan Antara Sel",\r
+DlgTableCellPad                : "Tambahan Ruang Sel",\r
+DlgTableCaption                : "Keterangan",\r
+DlgTableSummary                : "Summary",    //MISSING\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Ciri-ciri Sel",\r
+DlgCellWidth           : "Lebar",\r
+DlgCellWidthPx         : "piksel-piksel",\r
+DlgCellWidthPc         : "peratus",\r
+DlgCellHeight          : "Tinggi",\r
+DlgCellWordWrap                : "Mengulung Perkataan",\r
+DlgCellWordWrapNotSet  : "<Tidak diset>",\r
+DlgCellWordWrapYes     : "Ya",\r
+DlgCellWordWrapNo      : "Tidak",\r
+DlgCellHorAlign                : "Jajaran Membujur",\r
+DlgCellHorAlignNotSet  : "<Tidak diset>",\r
+DlgCellHorAlignLeft    : "Kiri",\r
+DlgCellHorAlignCenter  : "Tengah",\r
+DlgCellHorAlignRight: "Kanan",\r
+DlgCellVerAlign                : "Jajaran Menegak",\r
+DlgCellVerAlignNotSet  : "<Tidak diset>",\r
+DlgCellVerAlignTop     : "Atas",\r
+DlgCellVerAlignMiddle  : "Tengah",\r
+DlgCellVerAlignBottom  : "Bawah",\r
+DlgCellVerAlignBaseline        : "Garis Dasar",\r
+DlgCellRowSpan         : "Penggunaan Baris",\r
+DlgCellCollSpan                : "Penggunaan Lajur",\r
+DlgCellBackColor       : "Warna Latarbelakang",\r
+DlgCellBorderColor     : "Warna Border",\r
+DlgCellBtnSelect       : "Pilih...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Carian",\r
+DlgFindFindBtn         : "Cari",\r
+DlgFindNotFoundMsg     : "Text yang dicari tidak dijumpai.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Gantian",\r
+DlgReplaceFindLbl              : "Perkataan yang dicari:",\r
+DlgReplaceReplaceLbl   : "Diganti dengan:",\r
+DlgReplaceCaseChk              : "Padanan case huruf",\r
+DlgReplaceReplaceBtn   : "Ganti",\r
+DlgReplaceReplAllBtn   : "Ganti semua",\r
+DlgReplaceWordChk              : "Padana Keseluruhan perkataan",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Keselamatan perisian browser anda tidak membenarkan operasi suntingan text/imej. Sila gunakan papan kekunci (Ctrl+X).",\r
+PasteErrorCopy : "Keselamatan perisian browser anda tidak membenarkan operasi salinan text/imej. Sila gunakan papan kekunci (Ctrl+C).",\r
+\r
+PasteAsText            : "Tampal sebagai text biasa",\r
+PasteFromWord  : "Tampal dari perisian \"Word\"",\r
+\r
+DlgPasteMsg2   : "Please paste inside the following box using the keyboard (<strong>Ctrl+V</strong>) and hit <strong>OK</strong>.",    //MISSING\r
+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\r
+DlgPasteIgnoreFont             : "Ignore Font Face definitions",       //MISSING\r
+DlgPasteRemoveStyles   : "Remove Styles definitions",  //MISSING\r
+DlgPasteCleanBox               : "Clean Up Box",       //MISSING\r
+\r
+// Color Picker\r
+ColorAutomatic : "Otomatik",\r
+ColorMoreColors        : "Warna lain-lain...",\r
+\r
+// Document Properties\r
+DocProps               : "Ciri-ciri dokumen",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ciri-ciri Pautan",\r
+DlgAnchorName          : "Nama Pautan",\r
+DlgAnchorErrorName     : "Sila taip nama pautan",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Tidak terdapat didalam kamus",\r
+DlgSpellChangeTo               : "Tukarkan kepada",\r
+DlgSpellBtnIgnore              : "Biar",\r
+DlgSpellBtnIgnoreAll   : "Biarkan semua",\r
+DlgSpellBtnReplace             : "Ganti",\r
+DlgSpellBtnReplaceAll  : "Gantikan Semua",\r
+DlgSpellBtnUndo                        : "Batalkan",\r
+DlgSpellNoSuggestions  : "- Tiada cadangan -",\r
+DlgSpellProgress               : "Pemeriksaan ejaan sedang diproses...",\r
+DlgSpellNoMispell              : "Pemeriksaan ejaan siap: Tiada salah ejaan",\r
+DlgSpellNoChanges              : "Pemeriksaan ejaan siap: Tiada perkataan diubah",\r
+DlgSpellOneChange              : "Pemeriksaan ejaan siap: Satu perkataan telah diubah",\r
+DlgSpellManyChanges            : "Pemeriksaan ejaan siap: %1 perkataan diubah",\r
+\r
+IeSpellDownload                        : "Pemeriksa ejaan tidak dipasang. Adakah anda mahu muat turun sekarang?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Teks (Nilai)",\r
+DlgButtonType          : "Jenis",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nama",\r
+DlgCheckboxValue       : "Nilai",\r
+DlgCheckboxSelected    : "Dipilih",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nama",\r
+DlgFormAction  : "Tindakan borang",\r
+DlgFormMethod  : "Cara borang dihantar",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nama",\r
+DlgSelectValue         : "Nilai",\r
+DlgSelectSize          : "Saiz",\r
+DlgSelectLines         : "garisan",\r
+DlgSelectChkMulti      : "Benarkan pilihan pelbagai",\r
+DlgSelectOpAvail       : "Pilihan sediada",\r
+DlgSelectOpText                : "Teks",\r
+DlgSelectOpValue       : "Nilai",\r
+DlgSelectBtnAdd                : "Tambah Pilihan",\r
+DlgSelectBtnModify     : "Ubah Pilihan",\r
+DlgSelectBtnUp         : "Naik ke atas",\r
+DlgSelectBtnDown       : "Turun ke bawah",\r
+DlgSelectBtnSetValue : "Set sebagai nilai terpilih",\r
+DlgSelectBtnDelete     : "Padam",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nama",\r
+DlgTextareaCols        : "Lajur",\r
+DlgTextareaRows        : "Baris",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nama",\r
+DlgTextValue           : "Nilai",\r
+DlgTextCharWidth       : "Lebar isian",\r
+DlgTextMaxChars                : "Isian Maksimum",\r
+DlgTextType                    : "Jenis",\r
+DlgTextTypeText                : "Teks",\r
+DlgTextTypePass                : "Kata Laluan",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nama",\r
+DlgHiddenValue : "Nilai",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Ciri-ciri senarai berpeluru",\r
+NumberedListProp       : "Ciri-ciri senarai bernombor",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Jenis",\r
+DlgLstTypeCircle       : "Circle",\r
+DlgLstTypeDisc         : "Disc",       //MISSING\r
+DlgLstTypeSquare       : "Square",\r
+DlgLstTypeNumbers      : "Nombor-nombor (1, 2, 3)",\r
+DlgLstTypeLCase                : "Huruf-huruf kecil (a, b, c)",\r
+DlgLstTypeUCase                : "Huruf-huruf besar (A, B, C)",\r
+DlgLstTypeSRoman       : "Nombor Roman Kecil (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Nombor Roman Besar (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Umum",\r
+DlgDocBackTab          : "Latarbelakang",\r
+DlgDocColorsTab                : "Warna dan margin",\r
+DlgDocMetaTab          : "Data Meta",\r
+\r
+DlgDocPageTitle                : "Tajuk Muka Surat",\r
+DlgDocLangDir          : "Arah Tulisan",\r
+DlgDocLangDirLTR       : "Kiri ke Kanan (LTR)",\r
+DlgDocLangDirRTL       : "Kanan ke Kiri (RTL)",\r
+DlgDocLangCode         : "Kod Bahasa",\r
+DlgDocCharSet          : "Enkod Set Huruf",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Enkod Set Huruf yang Lain",\r
+\r
+DlgDocDocType          : "Jenis Kepala Dokumen",\r
+DlgDocDocTypeOther     : "Jenis Kepala Dokumen yang Lain",\r
+DlgDocIncXHTML         : "Masukkan pemula kod XHTML",\r
+DlgDocBgColor          : "Warna Latarbelakang",\r
+DlgDocBgImage          : "URL Gambar Latarbelakang",\r
+DlgDocBgNoScroll       : "Imej Latarbelakang tanpa Skrol",\r
+DlgDocCText                    : "Teks",\r
+DlgDocCLink                    : "Sambungan",\r
+DlgDocCVisited         : "Sambungan telah Dilawati",\r
+DlgDocCActive          : "Sambungan Aktif",\r
+DlgDocMargins          : "Margin Muka Surat",\r
+DlgDocMaTop                    : "Atas",\r
+DlgDocMaLeft           : "Kiri",\r
+DlgDocMaRight          : "Kanan",\r
+DlgDocMaBottom         : "Bawah",\r
+DlgDocMeIndex          : "Kata Kunci Indeks Dokumen (dipisahkan oleh koma)",\r
+DlgDocMeDescr          : "Keterangan Dokumen",\r
+DlgDocMeAuthor         : "Penulis",\r
+DlgDocMeCopy           : "Hakcipta",\r
+DlgDocPreview          : "Prebiu",\r
+\r
+// Templates Dialog\r
+Templates                      : "Templat",\r
+DlgTemplatesTitle      : "Templat Kandungan",\r
+DlgTemplatesSelMsg     : "Sila pilih templat untuk dibuka oleh editor<br>(kandungan sebenar akan hilang):",\r
+DlgTemplatesLoading    : "Senarai Templat sedang diproses. Sila Tunggu...",\r
+DlgTemplatesNoTpl      : "(Tiada Templat Disimpan)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Tentang",\r
+DlgAboutBrowserInfoTab : "Maklumat Perisian Browser",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "versi",\r
+DlgAboutInfo           : "Untuk maklumat lanjut sila pergi ke"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/nb.js b/httemplate/elements/fckeditor/editor/lang/nb.js
new file mode 100644 (file)
index 0000000..ae9fa64
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Norwegian Bokmål language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Skjul verktøylinje",\r
+ToolbarExpand          : "Vis verktøylinje",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Lagre",\r
+NewPage                                : "Ny Side",\r
+Preview                                : "Forhåndsvis",\r
+Cut                                    : "Klipp ut",\r
+Copy                           : "Kopier",\r
+Paste                          : "Lim inn",\r
+PasteText                      : "Lim inn som ren tekst",\r
+PasteWord                      : "Lim inn fra Word",\r
+Print                          : "Skriv ut",\r
+SelectAll                      : "Merk alt",\r
+RemoveFormat           : "Fjern format",\r
+InsertLinkLbl          : "Lenke",\r
+InsertLink                     : "Sett inn/Rediger lenke",\r
+RemoveLink                     : "Fjern lenke",\r
+Anchor                         : "Sett inn/Rediger anker",\r
+InsertImageLbl         : "Bilde",\r
+InsertImage                    : "Sett inn/Rediger bilde",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Sett inn/Rediger Flash",\r
+InsertTableLbl         : "Tabell",\r
+InsertTable                    : "Sett inn/Rediger tabell",\r
+InsertLineLbl          : "Linje",\r
+InsertLine                     : "Sett inn horisontal linje",\r
+InsertSpecialCharLbl: "Spesielt tegn",\r
+InsertSpecialChar      : "Sett inn spesielt tegn",\r
+InsertSmileyLbl                : "Smil",\r
+InsertSmiley           : "Sett inn smil",\r
+About                          : "Om FCKeditor",\r
+Bold                           : "Fet",\r
+Italic                         : "Kursiv",\r
+Underline                      : "Understrek",\r
+StrikeThrough          : "Gjennomstrek",\r
+Subscript                      : "Senket skrift",\r
+Superscript                    : "Hevet skrift",\r
+LeftJustify                    : "Venstrejuster",\r
+CenterJustify          : "Midtjuster",\r
+RightJustify           : "Høyrejuster",\r
+BlockJustify           : "Blokkjuster",\r
+DecreaseIndent         : "Senk nivå",\r
+IncreaseIndent         : "Øk nivå",\r
+Undo                           : "Angre",\r
+Redo                           : "Gjør om",\r
+NumberedListLbl                : "Numrert liste",\r
+NumberedList           : "Sett inn/Fjern numrert liste",\r
+BulletedListLbl                : "Uordnet liste",\r
+BulletedList           : "Sett inn/Fjern uordnet liste",\r
+ShowTableBorders       : "Vis tabellrammer",\r
+ShowDetails                    : "Vis detaljer",\r
+Style                          : "Stil",\r
+FontFormat                     : "Format",\r
+Font                           : "Skrift",\r
+FontSize                       : "Størrelse",\r
+TextColor                      : "Tekstfarge",\r
+BGColor                                : "Bakgrunnsfarge",\r
+Source                         : "Kilde",\r
+Find                           : "Finn",\r
+Replace                                : "Erstatt",\r
+SpellCheck                     : "Stavekontroll",\r
+UniversalKeyboard      : "Universelt tastatur",\r
+PageBreakLbl           : "Sideskift",\r
+PageBreak                      : "Sett inn sideskift",\r
+\r
+Form                   : "Skjema",\r
+Checkbox               : "Sjekkboks",\r
+RadioButton            : "Radioknapp",\r
+TextField              : "Tekstfelt",\r
+Textarea               : "Tekstområde",\r
+HiddenField            : "Skjult felt",\r
+Button                 : "Knapp",\r
+SelectionField : "Dropdown meny",\r
+ImageButton            : "Bildeknapp",\r
+\r
+FitWindow              : "Maksimer størrelsen på redigeringsverktøyet",\r
+\r
+// Context Menu\r
+EditLink                       : "Rediger lenke",\r
+CellCM                         : "Celle",\r
+RowCM                          : "Rader",\r
+ColumnCM                       : "Kolonne",\r
+InsertRow                      : "Sett inn rad",\r
+DeleteRows                     : "Slett rader",\r
+InsertColumn           : "Sett inn kolonne",\r
+DeleteColumns          : "Slett kolonner",\r
+InsertCell                     : "Sett inn celle",\r
+DeleteCells                    : "Slett celler",\r
+MergeCells                     : "Slå sammen celler",\r
+SplitCell                      : "Splitt celler",\r
+TableDelete                    : "Slett tabell",\r
+CellProperties         : "Celleegenskaper",\r
+TableProperties                : "Tabellegenskaper",\r
+ImageProperties                : "Bildeegenskaper",\r
+FlashProperties                : "Flash Egenskaper",\r
+\r
+AnchorProp                     : "Ankeregenskaper",\r
+ButtonProp                     : "Knappegenskaper",\r
+CheckboxProp           : "Sjekkboksegenskaper",\r
+HiddenFieldProp                : "Skjult felt egenskaper",\r
+RadioButtonProp                : "Radioknappegenskaper",\r
+ImageButtonProp                : "Bildeknappegenskaper",\r
+TextFieldProp          : "Tekstfeltegenskaper",\r
+SelectionFieldProp     : "Dropdown menyegenskaper",\r
+TextareaProp           : "Tekstfeltegenskaper",\r
+FormProp                       : "Skjemaegenskaper",\r
+\r
+FontFormats                    : "Normal;Formatert;Adresse;Tittel 1;Tittel 2;Tittel 3;Tittel 4;Tittel 5;Tittel 6",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Lager XHTML. Vennligst vent...",\r
+Done                           : "Ferdig",\r
+PasteWordConfirm       : "Teksten du prøver å lime inn ser ut som om den kommer fra word , du bør rense den før du limer inn , vil du gjøre dette?",\r
+NotCompatiblePaste     : "Denne kommandoen er tilgjenglig kun for Internet Explorer version 5.5 eller bedre. Vil du fortsette uten å rense?(Du kan lime inn som ren tekst)",\r
+UnknownToolbarItem     : "Ukjent menyvalg \"%1\"",\r
+UnknownCommand         : "Ukjent kommando \"%1\"",\r
+NotImplemented         : "Kommando ikke ennå implimentert",\r
+UnknownToolbarSet      : "Verktøylinjesett \"%1\" finnes ikke",\r
+NoActiveX                      : "Din nettleser's sikkerhetsinstillinger kan begrense noen av funksjonene i redigeringsverktøyet. Du må aktivere \"Kjør ActiveXkontroller og plugins\". Du kan oppleve feil og advarsler om manglende funksjoner",\r
+BrowseServerBlocked : "Kunne ikke åpne dialogboksen for filarkiv. Pass på at du har slått av popupstoppere.",\r
+DialogBlocked          : "Kunne ikke åpne dialogboksen. Pass på at du har slått av popupstoppere.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Avbryt",\r
+DlgBtnClose                    : "Lukk",\r
+DlgBtnBrowseServer     : "Bla igjennom server",\r
+DlgAdvancedTag         : "Avansert",\r
+DlgOpOther                     : "<Annet>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Vennligst skriv inn URL'en",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<ikke satt>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Språkretning",\r
+DlgGenLangDirLtr       : "Venstre til høyre (VTH)",\r
+DlgGenLangDirRtl       : "Høyre til venstre (HTV)",\r
+DlgGenLangCode         : "Språk kode",\r
+DlgGenAccessKey                : "Aksessknapp",\r
+DlgGenName                     : "Navn",\r
+DlgGenTabIndex         : "Tab Indeks",\r
+DlgGenLongDescr                : "Utvidet beskrivelse",\r
+DlgGenClass                    : "Stilarkklasser",\r
+DlgGenTitle                    : "Tittel",\r
+DlgGenContType         : "Type",\r
+DlgGenLinkCharset      : "Lenket språkkart",\r
+DlgGenStyle                    : "Stil",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Bildeegenskaper",\r
+DlgImgInfoTab          : "Bildeinformasjon",\r
+DlgImgBtnUpload                : "Send det til serveren",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Last opp",\r
+DlgImgAlt                      : "Alternativ tekst",\r
+DlgImgWidth                    : "Bredde",\r
+DlgImgHeight           : "Høyde",\r
+DlgImgLockRatio                : "Lås forhold",\r
+DlgBtnResetSize                : "Tilbakestill størrelse",\r
+DlgImgBorder           : "Ramme",\r
+DlgImgHSpace           : "HMarg",\r
+DlgImgVSpace           : "VMarg",\r
+DlgImgAlign                    : "Juster",\r
+DlgImgAlignLeft                : "Venstre",\r
+DlgImgAlignAbsBottom: "Abs bunn",\r
+DlgImgAlignAbsMiddle: "Abs midten",\r
+DlgImgAlignBaseline    : "Bunnlinje",\r
+DlgImgAlignBottom      : "Bunn",\r
+DlgImgAlignMiddle      : "Midten",\r
+DlgImgAlignRight       : "Høyre",\r
+DlgImgAlignTextTop     : "Tekst topp",\r
+DlgImgAlignTop         : "Topp",\r
+DlgImgPreview          : "Forhåndsvis",\r
+DlgImgAlertUrl         : "Vennligst skriv bildeurlen",\r
+DlgImgLinkTab          : "Lenke",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Egenskaper",\r
+DlgFlashChkPlay                : "Auto Spill",\r
+DlgFlashChkLoop                : "Loop",\r
+DlgFlashChkMenu                : "Slå på Flash meny",\r
+DlgFlashScale          : "Skaler",\r
+DlgFlashScaleAll       : "Vis alt",\r
+DlgFlashScaleNoBorder  : "Ingen ramme",\r
+DlgFlashScaleFit       : "Skaler til å passeExact Fit",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Lenke",\r
+DlgLnkInfoTab          : "Lenkeinfo",\r
+DlgLnkTargetTab                : "Mål",\r
+\r
+DlgLnkType                     : "Lenketype",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Lenke til bokmerke i teksten",\r
+DlgLnkTypeEMail                : "E-Post",\r
+DlgLnkProto                    : "Protokoll",\r
+DlgLnkProtoOther       : "<annet>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Velg ett anker",\r
+DlgLnkAnchorByName     : "Anker etter navn",\r
+DlgLnkAnchorById       : "Element etter ID",\r
+DlgLnkNoAnchors                : "<Ingen anker i dokumentet>",         //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Post Addresse",\r
+DlgLnkEMailSubject     : "Meldingsemne",\r
+DlgLnkEMailBody                : "Melding",\r
+DlgLnkUpload           : "Last opp",\r
+DlgLnkBtnUpload                : "Send til server",\r
+\r
+DlgLnkTarget           : "Mål",\r
+DlgLnkTargetFrame      : "<ramme>",\r
+DlgLnkTargetPopup      : "<popup vindu>",\r
+DlgLnkTargetBlank      : "Nytt vindu (_blank)",\r
+DlgLnkTargetParent     : "Foreldre vindu (_parent)",\r
+DlgLnkTargetSelf       : "Samme vindu (_self)",\r
+DlgLnkTargetTop                : "Hele vindu (_top)",\r
+DlgLnkTargetFrameName  : "Målramme",\r
+DlgLnkPopWinName       : "Popup vindus navn",\r
+DlgLnkPopWinFeat       : "Popup vindus egenskaper",\r
+DlgLnkPopResize                : "Endre størrelse",\r
+DlgLnkPopLocation      : "Adresselinje",\r
+DlgLnkPopMenu          : "Menylinje",\r
+DlgLnkPopScroll                : "Scrollbar",\r
+DlgLnkPopStatus                : "Statuslinje",\r
+DlgLnkPopToolbar       : "Verktøylinje",\r
+DlgLnkPopFullScrn      : "Full skjerm (IE)",\r
+DlgLnkPopDependent     : "Avhenging (Netscape)",\r
+DlgLnkPopWidth         : "Bredde",\r
+DlgLnkPopHeight                : "Høyde",\r
+DlgLnkPopLeft          : "Venstre posisjon",\r
+DlgLnkPopTop           : "Topp posisjon",\r
+\r
+DlnLnkMsgNoUrl         : "Vennligst skriv inn lenkens url",\r
+DlnLnkMsgNoEMail       : "Vennligst skriv inn e-postadressen",\r
+DlnLnkMsgNoAnchor      : "Vennligst velg ett anker",\r
+DlnLnkMsgInvPopName    : "Popup vinduets navn må begynne med en bokstav, og kan ikke inneholde mellomrom",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Velg farge",\r
+DlgColorBtnClear       : "Tøm",\r
+DlgColorHighlight      : "Marker",\r
+DlgColorSelected       : "Velg",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Sett inn smil",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Velg spesielt tegn",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Tabellegenskaper",\r
+DlgTableRows           : "Rader",\r
+DlgTableColumns                : "Kolonner",\r
+DlgTableBorder         : "Rammestørrelse",\r
+DlgTableAlign          : "Justering",\r
+DlgTableAlignNotSet    : "<Ikke satt>",\r
+DlgTableAlignLeft      : "Venstre",\r
+DlgTableAlignCenter    : "Midtjuster",\r
+DlgTableAlignRight     : "Høyre",\r
+DlgTableWidth          : "Bredde",\r
+DlgTableWidthPx                : "pixler",\r
+DlgTableWidthPc                : "prosent",\r
+DlgTableHeight         : "Høyde",\r
+DlgTableCellSpace      : "Celle marg",\r
+DlgTableCellPad                : "Celle polstring",\r
+DlgTableCaption                : "Tittel",\r
+DlgTableSummary                : "Sammendrag",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Celle egenskaper",\r
+DlgCellWidth           : "Bredde",\r
+DlgCellWidthPx         : "pixeler",\r
+DlgCellWidthPc         : "prosent",\r
+DlgCellHeight          : "Høyde",\r
+DlgCellWordWrap                : "Tekstbrytning",\r
+DlgCellWordWrapNotSet  : "<Ikke satt>",\r
+DlgCellWordWrapYes     : "Ja",\r
+DlgCellWordWrapNo      : "Nei",\r
+DlgCellHorAlign                : "Horisontal justering",\r
+DlgCellHorAlignNotSet  : "<Ikke satt>",\r
+DlgCellHorAlignLeft    : "Venstre",\r
+DlgCellHorAlignCenter  : "Midtjuster",\r
+DlgCellHorAlignRight: "Høyre",\r
+DlgCellVerAlign                : "Vertikal justering",\r
+DlgCellVerAlignNotSet  : "<Ikke satt>",\r
+DlgCellVerAlignTop     : "Topp",\r
+DlgCellVerAlignMiddle  : "Midten",\r
+DlgCellVerAlignBottom  : "Bunn",\r
+DlgCellVerAlignBaseline        : "Bunnlinje",\r
+DlgCellRowSpan         : "Radspenn",\r
+DlgCellCollSpan                : "Kolonnespenn",\r
+DlgCellBackColor       : "Bakgrunnsfarge",\r
+DlgCellBorderColor     : "Rammefarge",\r
+DlgCellBtnSelect       : "Velg...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Finn",\r
+DlgFindFindBtn         : "Finn",\r
+DlgFindNotFoundMsg     : "Den spesifiserte teksten ble ikke funnet.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Erstatt",\r
+DlgReplaceFindLbl              : "Finn hva:",\r
+DlgReplaceReplaceLbl   : "Erstatt med:",\r
+DlgReplaceCaseChk              : "Riktig case",\r
+DlgReplaceReplaceBtn   : "Erstatt",\r
+DlgReplaceReplAllBtn   : "Erstatt alle",\r
+DlgReplaceWordChk              : "Finn hele ordet",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Din nettlesers sikkerhetsinstillinger tillater ikke automatisk klipping av tekst. Vennligst brukt snareveien (Ctrl+X).",\r
+PasteErrorCopy : "Din nettlesers sikkerhetsinstillinger tillater ikke automatisk kopiering av tekst. Vennligst brukt snareveien (Ctrl+C).",\r
+\r
+PasteAsText            : "Lim inn som ren tekst",\r
+PasteFromWord  : "Lim inn fra word",\r
+\r
+DlgPasteMsg2   : "Vennligst lim inn i den følgende boksen med tastaturet (<STRONG>Ctrl+V</STRONG>) og trykk <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Fjern skrifttyper",\r
+DlgPasteRemoveStyles   : "Fjern stildefinisjoner",\r
+DlgPasteCleanBox               : "Tøm boksen",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatisk",\r
+ColorMoreColors        : "Flere farger...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumentegenskaper",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ankeregenskaper",\r
+DlgAnchorName          : "Ankernavn",\r
+DlgAnchorErrorName     : "Vennligst skriv inn ankernavnet",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Ikke i ordboken",\r
+DlgSpellChangeTo               : "Endre til",\r
+DlgSpellBtnIgnore              : "Ignorer",\r
+DlgSpellBtnIgnoreAll   : "Ignorer alle",\r
+DlgSpellBtnReplace             : "Erstatt",\r
+DlgSpellBtnReplaceAll  : "Erstatt alle",\r
+DlgSpellBtnUndo                        : "Angre",\r
+DlgSpellNoSuggestions  : "- ingen forslag -",\r
+DlgSpellProgress               : "Stavekontroll pågår...",\r
+DlgSpellNoMispell              : "Stavekontroll fullført: ingen feilstavinger funnet",\r
+DlgSpellNoChanges              : "Stavekontroll fullført: ingen ord endret",\r
+DlgSpellOneChange              : "Stavekontroll fullført: Ett ord endret",\r
+DlgSpellManyChanges            : "Stavekontroll fullført: %1 ord endret",\r
+\r
+IeSpellDownload                        : "Stavekontroll ikke installert, vil du laste den ned nå?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekst",\r
+DlgButtonType          : "Type",\r
+DlgButtonTypeBtn       : "Knapp",\r
+DlgButtonTypeSbm       : "Send",\r
+DlgButtonTypeRst       : "Nullstill",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Navn",\r
+DlgCheckboxValue       : "Verdi",\r
+DlgCheckboxSelected    : "Valgt",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Navn",\r
+DlgFormAction  : "Handling",\r
+DlgFormMethod  : "Metode",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Navn",\r
+DlgSelectValue         : "Verdi",\r
+DlgSelectSize          : "Størrelse",\r
+DlgSelectLines         : "Linjer",\r
+DlgSelectChkMulti      : "Tillat flervalg",\r
+DlgSelectOpAvail       : "Tilgjenglige alternativer",\r
+DlgSelectOpText                : "Tekst",\r
+DlgSelectOpValue       : "Verdi",\r
+DlgSelectBtnAdd                : "Legg til",\r
+DlgSelectBtnModify     : "Endre",\r
+DlgSelectBtnUp         : "Opp",\r
+DlgSelectBtnDown       : "Ned",\r
+DlgSelectBtnSetValue : "Sett som valgt",\r
+DlgSelectBtnDelete     : "Slett",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Navn",\r
+DlgTextareaCols        : "Kolonner",\r
+DlgTextareaRows        : "Rader",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Navn",\r
+DlgTextValue           : "verdi",\r
+DlgTextCharWidth       : "Tegnbredde",\r
+DlgTextMaxChars                : "Maks antall tegn",\r
+DlgTextType                    : "Type",\r
+DlgTextTypeText                : "Tekst",\r
+DlgTextTypePass                : "Passord",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Navn",\r
+DlgHiddenValue : "Verdi",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Uordnet listeegenskaper",\r
+NumberedListProp       : "Ordnet listeegenskaper",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Type",\r
+DlgLstTypeCircle       : "Sirkel",\r
+DlgLstTypeDisc         : "Hel sirkel",\r
+DlgLstTypeSquare       : "Firkant",\r
+DlgLstTypeNumbers      : "Numre(1, 2, 3)",\r
+DlgLstTypeLCase                : "Små bokstaver (a, b, c)",\r
+DlgLstTypeUCase                : "Store bokstaver(A, B, C)",\r
+DlgLstTypeSRoman       : "Små romerske tall(i, ii, iii)",\r
+DlgLstTypeLRoman       : "Store romerske tall(I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Generalt",\r
+DlgDocBackTab          : "Bakgrunn",\r
+DlgDocColorsTab                : "Farger og marginer",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Sidetittel",\r
+DlgDocLangDir          : "Språkretning",\r
+DlgDocLangDirLTR       : "Venstre til høyre (LTR)",\r
+DlgDocLangDirRTL       : "Høyre til venstre (RTL)",\r
+DlgDocLangCode         : "Språkkode",\r
+DlgDocCharSet          : "Tegnsett",\r
+DlgDocCharSetCE                : "Sentraleuropeisk",\r
+DlgDocCharSetCT                : "Tradisonell kinesisk(Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Gresk",\r
+DlgDocCharSetJP                : "Japansk",\r
+DlgDocCharSetKR                : "Koreansk",\r
+DlgDocCharSetTR                : "Tyrkisk",\r
+DlgDocCharSetUN                : "Unikode (UTF-8)",\r
+DlgDocCharSetWE                : "Vesteuropeisk",\r
+DlgDocCharSetOther     : "Annet tegnsett",\r
+\r
+DlgDocDocType          : "Dokumenttype header",\r
+DlgDocDocTypeOther     : "Annet dokumenttype header",\r
+DlgDocIncXHTML         : "Inkulder XHTML deklarasjon",\r
+DlgDocBgColor          : "Bakgrunnsfarge",\r
+DlgDocBgImage          : "Bakgrunnsbilde url",\r
+DlgDocBgNoScroll       : "Ikke scroll bakgrunnsbilde",\r
+DlgDocCText                    : "Tekst",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Besøkt lenke",\r
+DlgDocCActive          : "Aktiv lenke",\r
+DlgDocMargins          : "Sidemargin",\r
+DlgDocMaTop                    : "Topp",\r
+DlgDocMaLeft           : "Venstre",\r
+DlgDocMaRight          : "Høyre",\r
+DlgDocMaBottom         : "Bunn",\r
+DlgDocMeIndex          : "Dokument nøkkelord (kommaseparert)",\r
+DlgDocMeDescr          : "Dokumentbeskrivelse",\r
+DlgDocMeAuthor         : "Forfatter",\r
+DlgDocMeCopy           : "Kopirett",\r
+DlgDocPreview          : "Forhåndsvising",\r
+\r
+// Templates Dialog\r
+Templates                      : "Maler",\r
+DlgTemplatesTitle      : "Innholdsmaler",\r
+DlgTemplatesSelMsg     : "Velg malen du vil åpne<br>(innholdet du har skrevet blir tapt!):",\r
+DlgTemplatesLoading    : "Laster malliste. Vennligst vent...",\r
+DlgTemplatesNoTpl      : "(Ingen maler definert)",\r
+DlgTemplatesReplace    : "Erstatt faktisk innold",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Om",\r
+DlgAboutBrowserInfoTab : "Nettleserinfo",\r
+DlgAboutLicenseTab     : "Lisens",\r
+DlgAboutVersion                : "versjon",\r
+DlgAboutInfo           : "For further information go to"       //MISSING\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/nl.js b/httemplate/elements/fckeditor/editor/lang/nl.js
new file mode 100644 (file)
index 0000000..f6b26b4
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Dutch language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Menubalk inklappen",\r
+ToolbarExpand          : "Menubalk uitklappen",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Opslaan",\r
+NewPage                                : "Nieuwe pagina",\r
+Preview                                : "Voorbeeld",\r
+Cut                                    : "Knippen",\r
+Copy                           : "Kopiëren",\r
+Paste                          : "Plakken",\r
+PasteText                      : "Plakken als platte tekst",\r
+PasteWord                      : "Plakken als Word-gegevens",\r
+Print                          : "Printen",\r
+SelectAll                      : "Alles selecteren",\r
+RemoveFormat           : "Opmaak verwijderen",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Link invoegen/wijzigen",\r
+RemoveLink                     : "Link verwijderen",\r
+Anchor                         : "Interne link",\r
+InsertImageLbl         : "Afbeelding",\r
+InsertImage                    : "Afbeelding invoegen/wijzigen",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Flash invoegen/wijzigen",\r
+InsertTableLbl         : "Tabel",\r
+InsertTable                    : "Tabel invoegen/wijzigen",\r
+InsertLineLbl          : "Lijn",\r
+InsertLine                     : "Invoegen horizontale lijn",\r
+InsertSpecialCharLbl: "Speciale tekens",\r
+InsertSpecialChar      : "Speciaal teken invoegen",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Smiley invoegen",\r
+About                          : "Over FCKeditor",\r
+Bold                           : "Vet",\r
+Italic                         : "Schuingedrukt",\r
+Underline                      : "Onderstreept",\r
+StrikeThrough          : "Doorhalen",\r
+Subscript                      : "Subscript",\r
+Superscript                    : "Superscript",\r
+LeftJustify                    : "Links uitlijnen",\r
+CenterJustify          : "Centreren",\r
+RightJustify           : "Rechts uitlijnen",\r
+BlockJustify           : "Uitvullen",\r
+DecreaseIndent         : "Inspringen verkleinen",\r
+IncreaseIndent         : "Inspringen vergroten",\r
+Undo                           : "Ongedaan maken",\r
+Redo                           : "Opnieuw uitvoeren",\r
+NumberedListLbl                : "Genummerde lijst",\r
+NumberedList           : "Genummerde lijst invoegen/verwijderen",\r
+BulletedListLbl                : "Opsomming",\r
+BulletedList           : "Opsomming invoegen/verwijderen",\r
+ShowTableBorders       : "Randen tabel weergeven",\r
+ShowDetails                    : "Details weergeven",\r
+Style                          : "Stijl",\r
+FontFormat                     : "Opmaak",\r
+Font                           : "Lettertype",\r
+FontSize                       : "Grootte",\r
+TextColor                      : "Tekstkleur",\r
+BGColor                                : "Achtergrondkleur",\r
+Source                         : "Code",\r
+Find                           : "Zoeken",\r
+Replace                                : "Vervangen",\r
+SpellCheck                     : "Spellingscontrole",\r
+UniversalKeyboard      : "Universeel toetsenbord",\r
+PageBreakLbl           : "Pagina-einde",\r
+PageBreak                      : "Pagina-einde invoegen",\r
+\r
+Form                   : "Formulier",\r
+Checkbox               : "Aanvinkvakje",\r
+RadioButton            : "Selectievakje",\r
+TextField              : "Tekstveld",\r
+Textarea               : "Tekstvak",\r
+HiddenField            : "Verborgen veld",\r
+Button                 : "Knop",\r
+SelectionField : "Selectieveld",\r
+ImageButton            : "Afbeeldingsknop",\r
+\r
+FitWindow              : "De editor maximaliseren",\r
+\r
+// Context Menu\r
+EditLink                       : "Link wijzigen",\r
+CellCM                         : "Cel",\r
+RowCM                          : "Rij",\r
+ColumnCM                       : "Kolom",\r
+InsertRow                      : "Rij invoegen",\r
+DeleteRows                     : "Rijen verwijderen",\r
+InsertColumn           : "Kolom invoegen",\r
+DeleteColumns          : "Kolommen verwijderen",\r
+InsertCell                     : "Cel",\r
+DeleteCells                    : "Cellen verwijderen",\r
+MergeCells                     : "Cellen samenvoegen",\r
+SplitCell                      : "Cellen splitsen",\r
+TableDelete                    : "Tabel verwijderen",\r
+CellProperties         : "Eigenschappen cel",\r
+TableProperties                : "Eigenschappen tabel",\r
+ImageProperties                : "Eigenschappen afbeelding",\r
+FlashProperties                : "Eigenschappen Flash",\r
+\r
+AnchorProp                     : "Eigenschappen interne link",\r
+ButtonProp                     : "Eigenschappen knop",\r
+CheckboxProp           : "Eigenschappen aanvinkvakje",\r
+HiddenFieldProp                : "Eigenschappen verborgen veld",\r
+RadioButtonProp                : "Eigenschappen selectievakje",\r
+ImageButtonProp                : "Eigenschappen afbeeldingsknop",\r
+TextFieldProp          : "Eigenschappen tekstveld",\r
+SelectionFieldProp     : "Eigenschappen selectieveld",\r
+TextareaProp           : "Eigenschappen tekstvak",\r
+FormProp                       : "Eigenschappen formulier",\r
+\r
+FontFormats                    : "Normaal;Met opmaak;Adres;Kop 1;Kop 2;Kop 3;Kop 4;Kop 5;Kop 6;Normaal (DIV)",         //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Bezig met verwerken XHTML. Even geduld aub...",\r
+Done                           : "Klaar",\r
+PasteWordConfirm       : "De tekst die je plakte lijkt gekopieerd uit te zijn Word. Wil je de tekst opschonen voordat deze geplakt wordt?",\r
+NotCompatiblePaste     : "Deze opdracht is beschikbaar voor Internet Explorer versie 5.5 of hoger. Wil je plakken zonder op te schonen?",\r
+UnknownToolbarItem     : "Onbekend item op menubalk \"%1\"",\r
+UnknownCommand         : "Onbekende opdrachtnaam: \"%1\"",\r
+NotImplemented         : "Opdracht niet geïmplementeerd.",\r
+UnknownToolbarSet      : "Menubalk \"%1\" bestaat niet.",\r
+NoActiveX                      : "De beveilingsinstellingen van je 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.",\r
+BrowseServerBlocked : "De bestandsbrowser kon niet geopend worden. Zorg ervoor dat pop-up-blokkeerders uit staan.",\r
+DialogBlocked          : "Kan het dialoogvenster niet weergeven. Zorg ervoor dat pop-up-blokkeerders uit staan.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Annuleren",\r
+DlgBtnClose                    : "Afsluiten",\r
+DlgBtnBrowseServer     : "Bladeren op server",\r
+DlgAdvancedTag         : "Geavanceerd",\r
+DlgOpOther                     : "<Anders>",\r
+DlgInfoTab                     : "Informatie",\r
+DlgAlertUrl                    : "Geef URL op",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<niet ingevuld>",\r
+DlgGenId                       : "Kenmerk",\r
+DlgGenLangDir          : "Schrijfrichting",\r
+DlgGenLangDirLtr       : "Links naar rechts (LTR)",\r
+DlgGenLangDirRtl       : "Rechts naar links (RTL)",\r
+DlgGenLangCode         : "Taalcode",\r
+DlgGenAccessKey                : "Toegangstoets",\r
+DlgGenName                     : "Naam",\r
+DlgGenTabIndex         : "Tabvolgorde",\r
+DlgGenLongDescr                : "Lange URL-omschrijving",\r
+DlgGenClass                    : "Stylesheet-klassen",\r
+DlgGenTitle                    : "Aanbevolen titel",\r
+DlgGenContType         : "Aanbevolen content-type",\r
+DlgGenLinkCharset      : "Karakterset van gelinkte bron",\r
+DlgGenStyle                    : "Stijl",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Eigenschappen afbeelding",\r
+DlgImgInfoTab          : "Informatie afbeelding",\r
+DlgImgBtnUpload                : "Naar server verzenden",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Upload",\r
+DlgImgAlt                      : "Alternatieve tekst",\r
+DlgImgWidth                    : "Breedte",\r
+DlgImgHeight           : "Hoogte",\r
+DlgImgLockRatio                : "Afmetingen vergrendelen",\r
+DlgBtnResetSize                : "Afmetingen resetten",\r
+DlgImgBorder           : "Rand",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Uitlijning",\r
+DlgImgAlignLeft                : "Links",\r
+DlgImgAlignAbsBottom: "Absoluut-onder",\r
+DlgImgAlignAbsMiddle: "Absoluut-midden",\r
+DlgImgAlignBaseline    : "Basislijn",\r
+DlgImgAlignBottom      : "Beneden",\r
+DlgImgAlignMiddle      : "Midden",\r
+DlgImgAlignRight       : "Rechts",\r
+DlgImgAlignTextTop     : "Boven tekst",\r
+DlgImgAlignTop         : "Boven",\r
+DlgImgPreview          : "Voorbeeld",\r
+DlgImgAlertUrl         : "Geef de URL van de afbeelding",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Eigenschappen Flash",\r
+DlgFlashChkPlay                : "Automatisch afspelen",\r
+DlgFlashChkLoop                : "Herhalen",\r
+DlgFlashChkMenu                : "Flashmenu\'s inschakelen",\r
+DlgFlashScale          : "Schaal",\r
+DlgFlashScaleAll       : "Alles tonen",\r
+DlgFlashScaleNoBorder  : "Geen rand",\r
+DlgFlashScaleFit       : "Precies passend",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Linkomschrijving",\r
+DlgLnkTargetTab                : "Doel",\r
+\r
+DlgLnkType                     : "Linktype",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Interne link in pagina",\r
+DlgLnkTypeEMail                : "E-mail",\r
+DlgLnkProto                    : "Protocol",\r
+DlgLnkProtoOther       : "<anders>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Kies een interne link",\r
+DlgLnkAnchorByName     : "Op naam interne link",\r
+DlgLnkAnchorById       : "Op kenmerk interne link",\r
+DlgLnkNoAnchors                : "(Geen interne links in document gevonden)",          //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-mailadres",\r
+DlgLnkEMailSubject     : "Onderwerp bericht",\r
+DlgLnkEMailBody                : "Inhoud bericht",\r
+DlgLnkUpload           : "Upload",\r
+DlgLnkBtnUpload                : "Naar de server versturen",\r
+\r
+DlgLnkTarget           : "Doel",\r
+DlgLnkTargetFrame      : "<frame>",\r
+DlgLnkTargetPopup      : "<popup window>",\r
+DlgLnkTargetBlank      : "Nieuw venster (_blank)",\r
+DlgLnkTargetParent     : "Origineel venster (_parent)",\r
+DlgLnkTargetSelf       : "Zelfde venster (_self)",\r
+DlgLnkTargetTop                : "Hele venster (_top)",\r
+DlgLnkTargetFrameName  : "Naam doelframe",\r
+DlgLnkPopWinName       : "Naam popupvenster",\r
+DlgLnkPopWinFeat       : "Instellingen popupvenster",\r
+DlgLnkPopResize                : "Grootte wijzigen",\r
+DlgLnkPopLocation      : "Locatiemenu",\r
+DlgLnkPopMenu          : "Menubalk",\r
+DlgLnkPopScroll                : "Schuifbalken",\r
+DlgLnkPopStatus                : "Statusbalk",\r
+DlgLnkPopToolbar       : "Menubalk",\r
+DlgLnkPopFullScrn      : "Volledig scherm (IE)",\r
+DlgLnkPopDependent     : "Afhankelijk (Netscape)",\r
+DlgLnkPopWidth         : "Breedte",\r
+DlgLnkPopHeight                : "Hoogte",\r
+DlgLnkPopLeft          : "Positie links",\r
+DlgLnkPopTop           : "Positie boven",\r
+\r
+DlnLnkMsgNoUrl         : "Geef de link van de URL",\r
+DlnLnkMsgNoEMail       : "Geef een e-mailadres",\r
+DlnLnkMsgNoAnchor      : "Selecteer een interne link",\r
+DlnLnkMsgInvPopName    : "De naam van de popup moet met een alfa-numerieke waarde beginnen, en mag geen spaties bevatten.",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Selecteer kleur",\r
+DlgColorBtnClear       : "Opschonen",\r
+DlgColorHighlight      : "Accentueren",\r
+DlgColorSelected       : "Geselecteerd",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Smiley invoegen",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Selecteer speciaal teken",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Eigenschappen tabel",\r
+DlgTableRows           : "Rijen",\r
+DlgTableColumns                : "Kolommen",\r
+DlgTableBorder         : "Breedte rand",\r
+DlgTableAlign          : "Uitlijning",\r
+DlgTableAlignNotSet    : "<Niet ingevoerd>",\r
+DlgTableAlignLeft      : "Links",\r
+DlgTableAlignCenter    : "Centreren",\r
+DlgTableAlignRight     : "Rechts",\r
+DlgTableWidth          : "Breedte",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "procent",\r
+DlgTableHeight         : "Hoogte",\r
+DlgTableCellSpace      : "Afstand tussen cellen",\r
+DlgTableCellPad                : "Afstand vanaf rand cel",\r
+DlgTableCaption                : "Naam",\r
+DlgTableSummary                : "Samenvatting",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Eigenschappen cel",\r
+DlgCellWidth           : "Breedte",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "procent",\r
+DlgCellHeight          : "Hoogte",\r
+DlgCellWordWrap                : "Afbreken woorden",\r
+DlgCellWordWrapNotSet  : "<Niet ingevoerd>",\r
+DlgCellWordWrapYes     : "Ja",\r
+DlgCellWordWrapNo      : "Nee",\r
+DlgCellHorAlign                : "Horizontale uitlijning",\r
+DlgCellHorAlignNotSet  : "<Niet ingevoerd>",\r
+DlgCellHorAlignLeft    : "Links",\r
+DlgCellHorAlignCenter  : "Centreren",\r
+DlgCellHorAlignRight: "Rechts",\r
+DlgCellVerAlign                : "Verticale uitlijning",\r
+DlgCellVerAlignNotSet  : "<Niet ingevoerd>",\r
+DlgCellVerAlignTop     : "Boven",\r
+DlgCellVerAlignMiddle  : "Midden",\r
+DlgCellVerAlignBottom  : "Beneden",\r
+DlgCellVerAlignBaseline        : "Basislijn",\r
+DlgCellRowSpan         : "Overkoepeling rijen",\r
+DlgCellCollSpan                : "Overkoepeling kolommen",\r
+DlgCellBackColor       : "Achtergrondkleur",\r
+DlgCellBorderColor     : "Randkleur",\r
+DlgCellBtnSelect       : "Selecteren...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Zoeken",\r
+DlgFindFindBtn         : "Zoeken",\r
+DlgFindNotFoundMsg     : "De opgegeven tekst is niet gevonden.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Vervangen",\r
+DlgReplaceFindLbl              : "Zoeken naar:",\r
+DlgReplaceReplaceLbl   : "Vervangen met:",\r
+DlgReplaceCaseChk              : "Hoofdlettergevoelig",\r
+DlgReplaceReplaceBtn   : "Vervangen",\r
+DlgReplaceReplAllBtn   : "Alles vervangen",\r
+DlgReplaceWordChk              : "Hele woord moet voorkomen",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "De beveiligingsinstelling van de browser verhinderen het automatisch knippen. Gebruik de sneltoets Ctrl+X van het toetsenbord.",\r
+PasteErrorCopy : "De beveiligingsinstelling van de browser verhinderen het automatisch kopiëren. Gebruik de sneltoets Ctrl+C van het toetsenbord.",\r
+\r
+PasteAsText            : "Plakken als platte tekst",\r
+PasteFromWord  : "Plakken als Word-gegevens",\r
+\r
+DlgPasteMsg2   : "Plak de tekst in het volgende vak gebruik makend van je toetstenbord (<STRONG>Ctrl+V</STRONG>) en klik op <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Negeer \"Font Face\"-definities",\r
+DlgPasteRemoveStyles   : "Verwijder \"Style\"-definities",\r
+DlgPasteCleanBox               : "Vak opschonen",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatisch",\r
+ColorMoreColors        : "Meer kleuren...",\r
+\r
+// Document Properties\r
+DocProps               : "Eigenschappen document",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Eigenschappen interne link",\r
+DlgAnchorName          : "Naam interne link",\r
+DlgAnchorErrorName     : "Geef de naam van de interne link op",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Niet in het woordenboek",\r
+DlgSpellChangeTo               : "Wijzig in",\r
+DlgSpellBtnIgnore              : "Negeren",\r
+DlgSpellBtnIgnoreAll   : "Alles negeren",\r
+DlgSpellBtnReplace             : "Vervangen",\r
+DlgSpellBtnReplaceAll  : "Alles vervangen",\r
+DlgSpellBtnUndo                        : "Ongedaan maken",\r
+DlgSpellNoSuggestions  : "-Geen suggesties-",\r
+DlgSpellProgress               : "Bezig met spellingscontrole...",\r
+DlgSpellNoMispell              : "Klaar met spellingscontrole: geen fouten gevonden",\r
+DlgSpellNoChanges              : "Klaar met spellingscontrole: geen woorden aangepast",\r
+DlgSpellOneChange              : "Klaar met spellingscontrole: één woord aangepast",\r
+DlgSpellManyChanges            : "Klaar met spellingscontrole: %1 woorden aangepast",\r
+\r
+IeSpellDownload                        : "De spellingscontrole niet geïnstalleerd. Wil je deze nu downloaden?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekst (waarde)",\r
+DlgButtonType          : "Soort",\r
+DlgButtonTypeBtn       : "Knop",\r
+DlgButtonTypeSbm       : "Versturen",\r
+DlgButtonTypeRst       : "Leegmaken",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Naam",\r
+DlgCheckboxValue       : "Waarde",\r
+DlgCheckboxSelected    : "Geselecteerd",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Naam",\r
+DlgFormAction  : "Actie",\r
+DlgFormMethod  : "Methode",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Naam",\r
+DlgSelectValue         : "Waarde",\r
+DlgSelectSize          : "Grootte",\r
+DlgSelectLines         : "Regels",\r
+DlgSelectChkMulti      : "Gecombineerde selecties toestaan",\r
+DlgSelectOpAvail       : "Beschikbare opties",\r
+DlgSelectOpText                : "Tekst",\r
+DlgSelectOpValue       : "Waarde",\r
+DlgSelectBtnAdd                : "Toevoegen",\r
+DlgSelectBtnModify     : "Wijzigen",\r
+DlgSelectBtnUp         : "Omhoog",\r
+DlgSelectBtnDown       : "Omlaag",\r
+DlgSelectBtnSetValue : "Als geselecteerde waarde instellen",\r
+DlgSelectBtnDelete     : "Verwijderen",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Naam",\r
+DlgTextareaCols        : "Kolommen",\r
+DlgTextareaRows        : "Rijen",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Naam",\r
+DlgTextValue           : "Waarde",\r
+DlgTextCharWidth       : "Breedte (tekens)",\r
+DlgTextMaxChars                : "Maximum aantal tekens",\r
+DlgTextType                    : "Soort",\r
+DlgTextTypeText                : "Tekst",\r
+DlgTextTypePass                : "Wachtwoord",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Naam",\r
+DlgHiddenValue : "Waarde",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Eigenschappen opsommingslijst",\r
+NumberedListProp       : "Eigenschappen genummerde opsommingslijst",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Soort",\r
+DlgLstTypeCircle       : "Cirkel",\r
+DlgLstTypeDisc         : "Schijf",\r
+DlgLstTypeSquare       : "Vierkant",\r
+DlgLstTypeNumbers      : "Nummers (1, 2, 3)",\r
+DlgLstTypeLCase                : "Kleine letters (a, b, c)",\r
+DlgLstTypeUCase                : "Hoofdletters (A, B, C)",\r
+DlgLstTypeSRoman       : "Klein Romeins (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Groot Romeins (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Algemeen",\r
+DlgDocBackTab          : "Achtergrond",\r
+DlgDocColorsTab                : "Kleuring en marges",\r
+DlgDocMetaTab          : "META-data",\r
+\r
+DlgDocPageTitle                : "Paginatitel",\r
+DlgDocLangDir          : "Schrijfrichting",\r
+DlgDocLangDirLTR       : "Links naar rechts",\r
+DlgDocLangDirRTL       : "Rechts naar links",\r
+DlgDocLangCode         : "Taalcode",\r
+DlgDocCharSet          : "Karakterset-encoding",\r
+DlgDocCharSetCE                : "Centraal Europees",\r
+DlgDocCharSetCT                : "Traditioneel Chinees (Big5)",\r
+DlgDocCharSetCR                : "Cyriliaans",\r
+DlgDocCharSetGR                : "Grieks",\r
+DlgDocCharSetJP                : "Japans",\r
+DlgDocCharSetKR                : "Koreaans",\r
+DlgDocCharSetTR                : "Turks",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "West europees",\r
+DlgDocCharSetOther     : "Andere karakterset-encoding",\r
+\r
+DlgDocDocType          : "Opschrift documentsoort",\r
+DlgDocDocTypeOther     : "Ander opschrift documentsoort",\r
+DlgDocIncXHTML         : "XHTML-declaraties meenemen",\r
+DlgDocBgColor          : "Achtergrondkleur",\r
+DlgDocBgImage          : "URL achtergrondplaatje",\r
+DlgDocBgNoScroll       : "Vaste achtergrond",\r
+DlgDocCText                    : "Tekst",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Bezochte link",\r
+DlgDocCActive          : "Active link",\r
+DlgDocMargins          : "Afstandsinstellingen document",\r
+DlgDocMaTop                    : "Boven",\r
+DlgDocMaLeft           : "Links",\r
+DlgDocMaRight          : "Rechts",\r
+DlgDocMaBottom         : "Onder",\r
+DlgDocMeIndex          : "Trefwoorden betreffende document (kommagescheiden)",\r
+DlgDocMeDescr          : "Beschrijving document",\r
+DlgDocMeAuthor         : "Auteur",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Voorbeeld",\r
+\r
+// Templates Dialog\r
+Templates                      : "Sjablonen",\r
+DlgTemplatesTitle      : "Inhoud sjabonen",\r
+DlgTemplatesSelMsg     : "Selecteer het sjabloon dat in de editor geopend moet worden (de actuele inhoud gaat verloren):",\r
+DlgTemplatesLoading    : "Bezig met laden sjabonen. Even geduld alstublieft...",\r
+DlgTemplatesNoTpl      : "(Geen sjablonen gedefinieerd)",\r
+DlgTemplatesReplace    : "Vervang de huidige inhoud",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Over",\r
+DlgAboutBrowserInfoTab : "Browserinformatie",\r
+DlgAboutLicenseTab     : "Licentie",\r
+DlgAboutVersion                : "Versie",\r
+DlgAboutInfo           : "Voor meer informatie ga naar "\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/no.js b/httemplate/elements/fckeditor/editor/lang/no.js
new file mode 100644 (file)
index 0000000..d3b237d
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Norwegian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Skjul verktøylinje",\r
+ToolbarExpand          : "Vis verktøylinje",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Lagre",\r
+NewPage                                : "Ny Side",\r
+Preview                                : "Forhåndsvis",\r
+Cut                                    : "Klipp ut",\r
+Copy                           : "Kopier",\r
+Paste                          : "Lim inn",\r
+PasteText                      : "Lim inn som ren tekst",\r
+PasteWord                      : "Lim inn fra Word",\r
+Print                          : "Skriv ut",\r
+SelectAll                      : "Merk alt",\r
+RemoveFormat           : "Fjern format",\r
+InsertLinkLbl          : "Lenke",\r
+InsertLink                     : "Sett inn/Rediger lenke",\r
+RemoveLink                     : "Fjern lenke",\r
+Anchor                         : "Sett inn/Rediger anker",\r
+InsertImageLbl         : "Bilde",\r
+InsertImage                    : "Sett inn/Rediger bilde",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Sett inn/Rediger Flash",\r
+InsertTableLbl         : "Tabell",\r
+InsertTable                    : "Sett inn/Rediger tabell",\r
+InsertLineLbl          : "Linje",\r
+InsertLine                     : "Sett inn horisontal linje",\r
+InsertSpecialCharLbl: "Spesielt tegn",\r
+InsertSpecialChar      : "Sett inn spesielt tegn",\r
+InsertSmileyLbl                : "Smil",\r
+InsertSmiley           : "Sett inn smil",\r
+About                          : "Om FCKeditor",\r
+Bold                           : "Fet",\r
+Italic                         : "Kursiv",\r
+Underline                      : "Understrek",\r
+StrikeThrough          : "Gjennomstrek",\r
+Subscript                      : "Senket skrift",\r
+Superscript                    : "Hevet skrift",\r
+LeftJustify                    : "Venstrejuster",\r
+CenterJustify          : "Midtjuster",\r
+RightJustify           : "Høyrejuster",\r
+BlockJustify           : "Blokkjuster",\r
+DecreaseIndent         : "Senk nivå",\r
+IncreaseIndent         : "Øk nivå",\r
+Undo                           : "Angre",\r
+Redo                           : "Gjør om",\r
+NumberedListLbl                : "Numrert liste",\r
+NumberedList           : "Sett inn/Fjern numrert liste",\r
+BulletedListLbl                : "Uordnet liste",\r
+BulletedList           : "Sett inn/Fjern uordnet liste",\r
+ShowTableBorders       : "Vis tabellrammer",\r
+ShowDetails                    : "Vis detaljer",\r
+Style                          : "Stil",\r
+FontFormat                     : "Format",\r
+Font                           : "Skrift",\r
+FontSize                       : "Størrelse",\r
+TextColor                      : "Tekstfarge",\r
+BGColor                                : "Bakgrunnsfarge",\r
+Source                         : "Kilde",\r
+Find                           : "Finn",\r
+Replace                                : "Erstatt",\r
+SpellCheck                     : "Stavekontroll",\r
+UniversalKeyboard      : "Universelt tastatur",\r
+PageBreakLbl           : "Sideskift",\r
+PageBreak                      : "Sett inn sideskift",\r
+\r
+Form                   : "Skjema",\r
+Checkbox               : "Sjekkboks",\r
+RadioButton            : "Radioknapp",\r
+TextField              : "Tekstfelt",\r
+Textarea               : "Tekstområde",\r
+HiddenField            : "Skjult felt",\r
+Button                 : "Knapp",\r
+SelectionField : "Dropdown meny",\r
+ImageButton            : "Bildeknapp",\r
+\r
+FitWindow              : "Maksimer størrelsen på redigeringsverktøyet",\r
+\r
+// Context Menu\r
+EditLink                       : "Rediger lenke",\r
+CellCM                         : "Celle",\r
+RowCM                          : "Rader",\r
+ColumnCM                       : "Kolonne",\r
+InsertRow                      : "Sett inn rad",\r
+DeleteRows                     : "Slett rader",\r
+InsertColumn           : "Sett inn kolonne",\r
+DeleteColumns          : "Slett kolonner",\r
+InsertCell                     : "Sett inn celle",\r
+DeleteCells                    : "Slett celler",\r
+MergeCells                     : "Slå sammen celler",\r
+SplitCell                      : "Splitt celler",\r
+TableDelete                    : "Slett tabell",\r
+CellProperties         : "Celleegenskaper",\r
+TableProperties                : "Tabellegenskaper",\r
+ImageProperties                : "Bildeegenskaper",\r
+FlashProperties                : "Flash Egenskaper",\r
+\r
+AnchorProp                     : "Ankeregenskaper",\r
+ButtonProp                     : "Knappegenskaper",\r
+CheckboxProp           : "Sjekkboksegenskaper",\r
+HiddenFieldProp                : "Skjult felt egenskaper",\r
+RadioButtonProp                : "Radioknappegenskaper",\r
+ImageButtonProp                : "Bildeknappegenskaper",\r
+TextFieldProp          : "Tekstfeltegenskaper",\r
+SelectionFieldProp     : "Dropdown menyegenskaper",\r
+TextareaProp           : "Tekstfeltegenskaper",\r
+FormProp                       : "Skjemaegenskaper",\r
+\r
+FontFormats                    : "Normal;Formatert;Adresse;Tittel 1;Tittel 2;Tittel 3;Tittel 4;Tittel 5;Tittel 6",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Lager XHTML. Vennligst vent...",\r
+Done                           : "Ferdig",\r
+PasteWordConfirm       : "Teksten du prøver å lime inn ser ut som om den kommer fra word , du bør rense den før du limer inn , vil du gjøre dette?",\r
+NotCompatiblePaste     : "Denne kommandoen er tilgjenglig kun for Internet Explorer version 5.5 eller bedre. Vil du fortsette uten å rense?(Du kan lime inn som ren tekst)",\r
+UnknownToolbarItem     : "Ukjent menyvalg \"%1\"",\r
+UnknownCommand         : "Ukjent kommando \"%1\"",\r
+NotImplemented         : "Kommando ikke ennå implimentert",\r
+UnknownToolbarSet      : "Verktøylinjesett \"%1\" finnes ikke",\r
+NoActiveX                      : "Din nettleser's sikkerhetsinstillinger kan begrense noen av funksjonene i redigeringsverktøyet. Du må aktivere \"Kjør ActiveXkontroller og plugins\". Du kan oppleve feil og advarsler om manglende funksjoner",\r
+BrowseServerBlocked : "Kunne ikke åpne dialogboksen for filarkiv. Pass på at du har slått av popupstoppere.",\r
+DialogBlocked          : "Kunne ikke åpne dialogboksen. Pass på at du har slått av popupstoppere.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Avbryt",\r
+DlgBtnClose                    : "Lukk",\r
+DlgBtnBrowseServer     : "Bla igjennom server",\r
+DlgAdvancedTag         : "Avansert",\r
+DlgOpOther                     : "<Annet>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Vennligst skriv inn URL'en",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<ikke satt>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Språkretning",\r
+DlgGenLangDirLtr       : "Venstre til høyre (VTH)",\r
+DlgGenLangDirRtl       : "Høyre til venstre (HTV)",\r
+DlgGenLangCode         : "Språk kode",\r
+DlgGenAccessKey                : "Aksessknapp",\r
+DlgGenName                     : "Navn",\r
+DlgGenTabIndex         : "Tab Indeks",\r
+DlgGenLongDescr                : "Utvidet beskrivelse",\r
+DlgGenClass                    : "Stilarkklasser",\r
+DlgGenTitle                    : "Tittel",\r
+DlgGenContType         : "Type",\r
+DlgGenLinkCharset      : "Lenket språkkart",\r
+DlgGenStyle                    : "Stil",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Bildeegenskaper",\r
+DlgImgInfoTab          : "Bildeinformasjon",\r
+DlgImgBtnUpload                : "Send det til serveren",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Last opp",\r
+DlgImgAlt                      : "Alternativ tekst",\r
+DlgImgWidth                    : "Bredde",\r
+DlgImgHeight           : "Høyde",\r
+DlgImgLockRatio                : "Lås forhold",\r
+DlgBtnResetSize                : "Tilbakestill størrelse",\r
+DlgImgBorder           : "Ramme",\r
+DlgImgHSpace           : "HMarg",\r
+DlgImgVSpace           : "VMarg",\r
+DlgImgAlign                    : "Juster",\r
+DlgImgAlignLeft                : "Venstre",\r
+DlgImgAlignAbsBottom: "Abs bunn",\r
+DlgImgAlignAbsMiddle: "Abs midten",\r
+DlgImgAlignBaseline    : "Bunnlinje",\r
+DlgImgAlignBottom      : "Bunn",\r
+DlgImgAlignMiddle      : "Midten",\r
+DlgImgAlignRight       : "Høyre",\r
+DlgImgAlignTextTop     : "Tekst topp",\r
+DlgImgAlignTop         : "Topp",\r
+DlgImgPreview          : "Forhåndsvis",\r
+DlgImgAlertUrl         : "Vennligst skriv bildeurlen",\r
+DlgImgLinkTab          : "Lenke",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Egenskaper",\r
+DlgFlashChkPlay                : "Auto Spill",\r
+DlgFlashChkLoop                : "Loop",\r
+DlgFlashChkMenu                : "Slå på Flash meny",\r
+DlgFlashScale          : "Skaler",\r
+DlgFlashScaleAll       : "Vis alt",\r
+DlgFlashScaleNoBorder  : "Ingen ramme",\r
+DlgFlashScaleFit       : "Skaler til å passeExact Fit",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Lenke",\r
+DlgLnkInfoTab          : "Lenkeinfo",\r
+DlgLnkTargetTab                : "Mål",\r
+\r
+DlgLnkType                     : "Lenketype",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Lenke til bokmerke i teksten",\r
+DlgLnkTypeEMail                : "E-Post",\r
+DlgLnkProto                    : "Protokoll",\r
+DlgLnkProtoOther       : "<annet>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Velg ett anker",\r
+DlgLnkAnchorByName     : "Anker etter navn",\r
+DlgLnkAnchorById       : "Element etter ID",\r
+DlgLnkNoAnchors                : "<Ingen anker i dokumentet>",         //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Post Addresse",\r
+DlgLnkEMailSubject     : "Meldingsemne",\r
+DlgLnkEMailBody                : "Melding",\r
+DlgLnkUpload           : "Last opp",\r
+DlgLnkBtnUpload                : "Send til server",\r
+\r
+DlgLnkTarget           : "Mål",\r
+DlgLnkTargetFrame      : "<ramme>",\r
+DlgLnkTargetPopup      : "<popup vindu>",\r
+DlgLnkTargetBlank      : "Nytt vindu (_blank)",\r
+DlgLnkTargetParent     : "Foreldre vindu (_parent)",\r
+DlgLnkTargetSelf       : "Samme vindu (_self)",\r
+DlgLnkTargetTop                : "Hele vindu (_top)",\r
+DlgLnkTargetFrameName  : "Målramme",\r
+DlgLnkPopWinName       : "Popup vindus navn",\r
+DlgLnkPopWinFeat       : "Popup vindus egenskaper",\r
+DlgLnkPopResize                : "Endre størrelse",\r
+DlgLnkPopLocation      : "Adresselinje",\r
+DlgLnkPopMenu          : "Menylinje",\r
+DlgLnkPopScroll                : "Scrollbar",\r
+DlgLnkPopStatus                : "Statuslinje",\r
+DlgLnkPopToolbar       : "Verktøylinje",\r
+DlgLnkPopFullScrn      : "Full skjerm (IE)",\r
+DlgLnkPopDependent     : "Avhenging (Netscape)",\r
+DlgLnkPopWidth         : "Bredde",\r
+DlgLnkPopHeight                : "Høyde",\r
+DlgLnkPopLeft          : "Venstre posisjon",\r
+DlgLnkPopTop           : "Topp posisjon",\r
+\r
+DlnLnkMsgNoUrl         : "Vennligst skriv inn lenkens url",\r
+DlnLnkMsgNoEMail       : "Vennligst skriv inn e-postadressen",\r
+DlnLnkMsgNoAnchor      : "Vennligst velg ett anker",\r
+DlnLnkMsgInvPopName    : "Popup vinduets navn må begynne med en bokstav, og kan ikke inneholde mellomrom",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Velg farge",\r
+DlgColorBtnClear       : "Tøm",\r
+DlgColorHighlight      : "Marker",\r
+DlgColorSelected       : "Velg",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Sett inn smil",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Velg spesielt tegn",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Tabellegenskaper",\r
+DlgTableRows           : "Rader",\r
+DlgTableColumns                : "Kolonner",\r
+DlgTableBorder         : "Rammestørrelse",\r
+DlgTableAlign          : "Justering",\r
+DlgTableAlignNotSet    : "<Ikke satt>",\r
+DlgTableAlignLeft      : "Venstre",\r
+DlgTableAlignCenter    : "Midtjuster",\r
+DlgTableAlignRight     : "Høyre",\r
+DlgTableWidth          : "Bredde",\r
+DlgTableWidthPx                : "pixler",\r
+DlgTableWidthPc                : "prosent",\r
+DlgTableHeight         : "Høyde",\r
+DlgTableCellSpace      : "Celle marg",\r
+DlgTableCellPad                : "Celle polstring",\r
+DlgTableCaption                : "Tittel",\r
+DlgTableSummary                : "Sammendrag",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Celle egenskaper",\r
+DlgCellWidth           : "Bredde",\r
+DlgCellWidthPx         : "pixeler",\r
+DlgCellWidthPc         : "prosent",\r
+DlgCellHeight          : "Høyde",\r
+DlgCellWordWrap                : "Tekstbrytning",\r
+DlgCellWordWrapNotSet  : "<Ikke satt>",\r
+DlgCellWordWrapYes     : "Ja",\r
+DlgCellWordWrapNo      : "Nei",\r
+DlgCellHorAlign                : "Horisontal justering",\r
+DlgCellHorAlignNotSet  : "<Ikke satt>",\r
+DlgCellHorAlignLeft    : "Venstre",\r
+DlgCellHorAlignCenter  : "Midtjuster",\r
+DlgCellHorAlignRight: "Høyre",\r
+DlgCellVerAlign                : "Vertikal justering",\r
+DlgCellVerAlignNotSet  : "<Ikke satt>",\r
+DlgCellVerAlignTop     : "Topp",\r
+DlgCellVerAlignMiddle  : "Midten",\r
+DlgCellVerAlignBottom  : "Bunn",\r
+DlgCellVerAlignBaseline        : "Bunnlinje",\r
+DlgCellRowSpan         : "Radspenn",\r
+DlgCellCollSpan                : "Kolonnespenn",\r
+DlgCellBackColor       : "Bakgrunnsfarge",\r
+DlgCellBorderColor     : "Rammefarge",\r
+DlgCellBtnSelect       : "Velg...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Finn",\r
+DlgFindFindBtn         : "Finn",\r
+DlgFindNotFoundMsg     : "Den spesifiserte teksten ble ikke funnet.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Erstatt",\r
+DlgReplaceFindLbl              : "Finn hva:",\r
+DlgReplaceReplaceLbl   : "Erstatt med:",\r
+DlgReplaceCaseChk              : "Riktig case",\r
+DlgReplaceReplaceBtn   : "Erstatt",\r
+DlgReplaceReplAllBtn   : "Erstatt alle",\r
+DlgReplaceWordChk              : "Finn hele ordet",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Din nettlesers sikkerhetsinstillinger tillater ikke automatisk klipping av tekst. Vennligst brukt snareveien (Ctrl+X).",\r
+PasteErrorCopy : "Din nettlesers sikkerhetsinstillinger tillater ikke automatisk kopiering av tekst. Vennligst brukt snareveien (Ctrl+C).",\r
+\r
+PasteAsText            : "Lim inn som ren tekst",\r
+PasteFromWord  : "Lim inn fra word",\r
+\r
+DlgPasteMsg2   : "Vennligst lim inn i den følgende boksen med tastaturet (<STRONG>Ctrl+V</STRONG>) og trykk <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Fjern skrifttyper",\r
+DlgPasteRemoveStyles   : "Fjern stildefinisjoner",\r
+DlgPasteCleanBox               : "Tøm boksen",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatisk",\r
+ColorMoreColors        : "Flere farger...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumentegenskaper",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ankeregenskaper",\r
+DlgAnchorName          : "Ankernavn",\r
+DlgAnchorErrorName     : "Vennligst skriv inn ankernavnet",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Ikke i ordboken",\r
+DlgSpellChangeTo               : "Endre til",\r
+DlgSpellBtnIgnore              : "Ignorer",\r
+DlgSpellBtnIgnoreAll   : "Ignorer alle",\r
+DlgSpellBtnReplace             : "Erstatt",\r
+DlgSpellBtnReplaceAll  : "Erstatt alle",\r
+DlgSpellBtnUndo                        : "Angre",\r
+DlgSpellNoSuggestions  : "- ingen forslag -",\r
+DlgSpellProgress               : "Stavekontroll pågår...",\r
+DlgSpellNoMispell              : "Stavekontroll fullført: ingen feilstavinger funnet",\r
+DlgSpellNoChanges              : "Stavekontroll fullført: ingen ord endret",\r
+DlgSpellOneChange              : "Stavekontroll fullført: Ett ord endret",\r
+DlgSpellManyChanges            : "Stavekontroll fullført: %1 ord endret",\r
+\r
+IeSpellDownload                        : "Stavekontroll ikke installert, vil du laste den ned nå?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekst",\r
+DlgButtonType          : "Type",\r
+DlgButtonTypeBtn       : "Knapp",\r
+DlgButtonTypeSbm       : "Send",\r
+DlgButtonTypeRst       : "Nullstill",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Navn",\r
+DlgCheckboxValue       : "Verdi",\r
+DlgCheckboxSelected    : "Valgt",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Navn",\r
+DlgFormAction  : "Handling",\r
+DlgFormMethod  : "Metode",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Navn",\r
+DlgSelectValue         : "Verdi",\r
+DlgSelectSize          : "Størrelse",\r
+DlgSelectLines         : "Linjer",\r
+DlgSelectChkMulti      : "Tillat flervalg",\r
+DlgSelectOpAvail       : "Tilgjenglige alternativer",\r
+DlgSelectOpText                : "Tekst",\r
+DlgSelectOpValue       : "Verdi",\r
+DlgSelectBtnAdd                : "Legg til",\r
+DlgSelectBtnModify     : "Endre",\r
+DlgSelectBtnUp         : "Opp",\r
+DlgSelectBtnDown       : "Ned",\r
+DlgSelectBtnSetValue : "Sett som valgt",\r
+DlgSelectBtnDelete     : "Slett",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Navn",\r
+DlgTextareaCols        : "Kolonner",\r
+DlgTextareaRows        : "Rader",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Navn",\r
+DlgTextValue           : "verdi",\r
+DlgTextCharWidth       : "Tegnbredde",\r
+DlgTextMaxChars                : "Maks antall tegn",\r
+DlgTextType                    : "Type",\r
+DlgTextTypeText                : "Tekst",\r
+DlgTextTypePass                : "Passord",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Navn",\r
+DlgHiddenValue : "Verdi",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Uordnet listeegenskaper",\r
+NumberedListProp       : "Ordnet listeegenskaper",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Type",\r
+DlgLstTypeCircle       : "Sirkel",\r
+DlgLstTypeDisc         : "Hel sirkel",\r
+DlgLstTypeSquare       : "Firkant",\r
+DlgLstTypeNumbers      : "Numre(1, 2, 3)",\r
+DlgLstTypeLCase                : "Små bokstaver (a, b, c)",\r
+DlgLstTypeUCase                : "Store bokstaver(A, B, C)",\r
+DlgLstTypeSRoman       : "Små romerske tall(i, ii, iii)",\r
+DlgLstTypeLRoman       : "Store romerske tall(I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Generalt",\r
+DlgDocBackTab          : "Bakgrunn",\r
+DlgDocColorsTab                : "Farger og marginer",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Sidetittel",\r
+DlgDocLangDir          : "Språkretning",\r
+DlgDocLangDirLTR       : "Venstre til høyre (LTR)",\r
+DlgDocLangDirRTL       : "Høyre til venstre (RTL)",\r
+DlgDocLangCode         : "Språkkode",\r
+DlgDocCharSet          : "Tegnsett",\r
+DlgDocCharSetCE                : "Sentraleuropeisk",\r
+DlgDocCharSetCT                : "Tradisonell kinesisk(Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Gresk",\r
+DlgDocCharSetJP                : "Japansk",\r
+DlgDocCharSetKR                : "Koreansk",\r
+DlgDocCharSetTR                : "Tyrkisk",\r
+DlgDocCharSetUN                : "Unikode (UTF-8)",\r
+DlgDocCharSetWE                : "Vesteuropeisk",\r
+DlgDocCharSetOther     : "Annet tegnsett",\r
+\r
+DlgDocDocType          : "Dokumenttype header",\r
+DlgDocDocTypeOther     : "Annet dokumenttype header",\r
+DlgDocIncXHTML         : "Inkulder XHTML deklarasjon",\r
+DlgDocBgColor          : "Bakgrunnsfarge",\r
+DlgDocBgImage          : "Bakgrunnsbilde url",\r
+DlgDocBgNoScroll       : "Ikke scroll bakgrunnsbilde",\r
+DlgDocCText                    : "Tekst",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Besøkt lenke",\r
+DlgDocCActive          : "Aktiv lenke",\r
+DlgDocMargins          : "Sidemargin",\r
+DlgDocMaTop                    : "Topp",\r
+DlgDocMaLeft           : "Venstre",\r
+DlgDocMaRight          : "Høyre",\r
+DlgDocMaBottom         : "Bunn",\r
+DlgDocMeIndex          : "Dokument nøkkelord (kommaseparert)",\r
+DlgDocMeDescr          : "Dokumentbeskrivelse",\r
+DlgDocMeAuthor         : "Forfatter",\r
+DlgDocMeCopy           : "Kopirett",\r
+DlgDocPreview          : "Forhåndsvising",\r
+\r
+// Templates Dialog\r
+Templates                      : "Maler",\r
+DlgTemplatesTitle      : "Innholdsmaler",\r
+DlgTemplatesSelMsg     : "Velg malen du vil åpne<br>(innholdet du har skrevet blir tapt!):",\r
+DlgTemplatesLoading    : "Laster malliste. Vennligst vent...",\r
+DlgTemplatesNoTpl      : "(Ingen maler definert)",\r
+DlgTemplatesReplace    : "Erstatt faktisk innold",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Om",\r
+DlgAboutBrowserInfoTab : "Nettleserinfo",\r
+DlgAboutLicenseTab     : "Lisens",\r
+DlgAboutVersion                : "versjon",\r
+DlgAboutInfo           : "For further information go to"       //MISSING\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/pl.js b/httemplate/elements/fckeditor/editor/lang/pl.js
new file mode 100644 (file)
index 0000000..f01994d
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Polish language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Zwiń pasek narzędzi",\r
+ToolbarExpand          : "Rozwiń pasek narzędzi",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Zapisz",\r
+NewPage                                : "Nowa strona",\r
+Preview                                : "Podgląd",\r
+Cut                                    : "Wytnij",\r
+Copy                           : "Kopiuj",\r
+Paste                          : "Wklej",\r
+PasteText                      : "Wklej jako czysty tekst",\r
+PasteWord                      : "Wklej z Worda",\r
+Print                          : "Drukuj",\r
+SelectAll                      : "Zaznacz wszystko",\r
+RemoveFormat           : "Usuń formatowanie",\r
+InsertLinkLbl          : "Hiperłącze",\r
+InsertLink                     : "Wstaw/edytuj hiperłącze",\r
+RemoveLink                     : "Usuń hiperłącze",\r
+Anchor                         : "Wstaw/edytuj kotwicę",\r
+InsertImageLbl         : "Obrazek",\r
+InsertImage                    : "Wstaw/edytuj obrazek",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Dodaj/Edytuj element Flash",\r
+InsertTableLbl         : "Tabela",\r
+InsertTable                    : "Wstaw/edytuj tabelę",\r
+InsertLineLbl          : "Linia pozioma",\r
+InsertLine                     : "Wstaw poziomą linię",\r
+InsertSpecialCharLbl: "Znak specjalny",\r
+InsertSpecialChar      : "Wstaw znak specjalny",\r
+InsertSmileyLbl                : "Emotikona",\r
+InsertSmiley           : "Wstaw emotikonę",\r
+About                          : "O programie FCKeditor",\r
+Bold                           : "Pogrubienie",\r
+Italic                         : "Kursywa",\r
+Underline                      : "Podkreślenie",\r
+StrikeThrough          : "Przekreślenie",\r
+Subscript                      : "Indeks dolny",\r
+Superscript                    : "Indeks górny",\r
+LeftJustify                    : "Wyrównaj do lewej",\r
+CenterJustify          : "Wyrównaj do środka",\r
+RightJustify           : "Wyrównaj do prawej",\r
+BlockJustify           : "Wyrównaj do lewej i prawej",\r
+DecreaseIndent         : "Zmniejsz wcięcie",\r
+IncreaseIndent         : "Zwiększ wcięcie",\r
+Undo                           : "Cofnij",\r
+Redo                           : "Ponów",\r
+NumberedListLbl                : "Lista numerowana",\r
+NumberedList           : "Wstaw/usuń numerowanie listy",\r
+BulletedListLbl                : "Lista wypunktowana",\r
+BulletedList           : "Wstaw/usuń wypunktowanie listy",\r
+ShowTableBorders       : "Pokazuj ramkę tabeli",\r
+ShowDetails                    : "Pokaż szczegóły",\r
+Style                          : "Styl",\r
+FontFormat                     : "Format",\r
+Font                           : "Czcionka",\r
+FontSize                       : "Rozmiar",\r
+TextColor                      : "Kolor tekstu",\r
+BGColor                                : "Kolor tła",\r
+Source                         : "Źródło dokumentu",\r
+Find                           : "Znajdź",\r
+Replace                                : "Zamień",\r
+SpellCheck                     : "Sprawdź pisownię",\r
+UniversalKeyboard      : "Klawiatura Uniwersalna",\r
+PageBreakLbl           : "Odstęp",\r
+PageBreak                      : "Wstaw odstęp",\r
+\r
+Form                   : "Formularz",\r
+Checkbox               : "Checkbox",\r
+RadioButton            : "Pole wyboru",\r
+TextField              : "Pole tekstowe",\r
+Textarea               : "Obszar tekstowy",\r
+HiddenField            : "Pole ukryte",\r
+Button                 : "Przycisk",\r
+SelectionField : "Lista wyboru",\r
+ImageButton            : "Przycisk obrazek",\r
+\r
+FitWindow              : "Maksymalizuj rozmiar edytora",\r
+\r
+// Context Menu\r
+EditLink                       : "Edytuj hiperłącze",\r
+CellCM                         : "Komórka",\r
+RowCM                          : "Wiersz",\r
+ColumnCM                       : "Kolumna",\r
+InsertRow                      : "Wstaw wiersz",\r
+DeleteRows                     : "Usuń wiersze",\r
+InsertColumn           : "Wstaw kolumnę",\r
+DeleteColumns          : "Usuń kolumny",\r
+InsertCell                     : "Wstaw komórkę",\r
+DeleteCells                    : "Usuń komórki",\r
+MergeCells                     : "Połącz komórki",\r
+SplitCell                      : "Podziel komórkę",\r
+TableDelete                    : "Usuń tabelę",\r
+CellProperties         : "Właściwości komórki",\r
+TableProperties                : "Właściwości tabeli",\r
+ImageProperties                : "Właściwości obrazka",\r
+FlashProperties                : "Właściwości elementu Flash",\r
+\r
+AnchorProp                     : "Właściwości kotwicy",\r
+ButtonProp                     : "Właściwości przycisku",\r
+CheckboxProp           : "Checkbox - właściwości",\r
+HiddenFieldProp                : "Właściwości pola ukrytego",\r
+RadioButtonProp                : "Właściwości pola wyboru",\r
+ImageButtonProp                : "Właściwości przycisku obrazka",\r
+TextFieldProp          : "Właściwości pola tekstowego",\r
+SelectionFieldProp     : "Właściwości listy wyboru",\r
+TextareaProp           : "Właściwości obszaru tekstowego",\r
+FormProp                       : "Właściwości formularza",\r
+\r
+FontFormats                    : "Normalny;Tekst sformatowany;Adres;Nagłówek 1;Nagłówek 2;Nagłówek 3;Nagłówek 4;Nagłówek 5;Nagłówek 6",            //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Przetwarzanie XHTML. Proszę czekać...",\r
+Done                           : "Gotowe",\r
+PasteWordConfirm       : "Tekst, który chcesz wkleić, prawdopodobnie pochodzi z programu Word. Czy chcesz go wyczyścic przed wklejeniem?",\r
+NotCompatiblePaste     : "Ta funkcja jest dostępna w programie Internet Explorer w wersji 5.5 lub wyższej. Czy chcesz wkleić tekst bez czyszczenia?",\r
+UnknownToolbarItem     : "Nieznany element paska narzędzi \"%1\"",\r
+UnknownCommand         : "Nieznana komenda \"%1\"",\r
+NotImplemented         : "Komenda niezaimplementowana",\r
+UnknownToolbarSet      : "Pasek narzędzi \"%1\" nie istnieje",\r
+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.",\r
+BrowseServerBlocked : "Okno menadżera plików nie może zostać otwarte. Upewnij się, że wszystkie blokady popup są wyłączone.",\r
+DialogBlocked          : "Nie można otworzyć okna dialogowego. Upewnij się, że wszystkie blokady popup są wyłączone.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Anuluj",\r
+DlgBtnClose                    : "Zamknij",\r
+DlgBtnBrowseServer     : "Przeglądaj",\r
+DlgAdvancedTag         : "Zaawansowane",\r
+DlgOpOther                     : "<Inny>",\r
+DlgInfoTab                     : "Informacje",\r
+DlgAlertUrl                    : "Proszę podać URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nieustawione>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Kierunek tekstu",\r
+DlgGenLangDirLtr       : "Od lewej do prawej (LTR)",\r
+DlgGenLangDirRtl       : "Od prawej do lewej (RTL)",\r
+DlgGenLangCode         : "Kod języka",\r
+DlgGenAccessKey                : "Klawisz dostępu",\r
+DlgGenName                     : "Nazwa",\r
+DlgGenTabIndex         : "Indeks tabeli",\r
+DlgGenLongDescr                : "Long Description URL",\r
+DlgGenClass                    : "Stylesheet Classes",\r
+DlgGenTitle                    : "Advisory Title",\r
+DlgGenContType         : "Advisory Content Type",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Styl",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Właściwości obrazka",\r
+DlgImgInfoTab          : "Informacje o obrazku",\r
+DlgImgBtnUpload                : "Syślij",\r
+DlgImgURL                      : "Adres URL",\r
+DlgImgUpload           : "Wyślij",\r
+DlgImgAlt                      : "Tekst zastępczy",\r
+DlgImgWidth                    : "Szerokość",\r
+DlgImgHeight           : "Wysokość",\r
+DlgImgLockRatio                : "Zablokuj proporcje",\r
+DlgBtnResetSize                : "Przywróć rozmiar",\r
+DlgImgBorder           : "Ramka",\r
+DlgImgHSpace           : "Odstęp poziomy",\r
+DlgImgVSpace           : "Odstęp pionowy",\r
+DlgImgAlign                    : "Wyrównaj",\r
+DlgImgAlignLeft                : "Do lewej",\r
+DlgImgAlignAbsBottom: "Do dołu",\r
+DlgImgAlignAbsMiddle: "Do środka w pionie",\r
+DlgImgAlignBaseline    : "Do linii bazowej",\r
+DlgImgAlignBottom      : "Do dołu",\r
+DlgImgAlignMiddle      : "Do środka",\r
+DlgImgAlignRight       : "Do prawej",\r
+DlgImgAlignTextTop     : "Do góry tekstu",\r
+DlgImgAlignTop         : "Do góry",\r
+DlgImgPreview          : "Podgląd",\r
+DlgImgAlertUrl         : "Podaj adres obrazka.",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Właściwości elementu Flash",\r
+DlgFlashChkPlay                : "Auto Odtwarzanie",\r
+DlgFlashChkLoop                : "Pętla",\r
+DlgFlashChkMenu                : "Włącz menu",\r
+DlgFlashScale          : "Skaluj",\r
+DlgFlashScaleAll       : "Pokaż wszystko",\r
+DlgFlashScaleNoBorder  : "Bez Ramki",\r
+DlgFlashScaleFit       : "Dokładne dopasowanie",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Hiperłącze",\r
+DlgLnkInfoTab          : "Informacje ",\r
+DlgLnkTargetTab                : "Cel",\r
+\r
+DlgLnkType                     : "Typ hiperłącza",\r
+DlgLnkTypeURL          : "Adres URL",\r
+DlgLnkTypeAnchor       : "Odnośnik wewnątrz strony",\r
+DlgLnkTypeEMail                : "Adres e-mail",\r
+DlgLnkProto                    : "Protokół",\r
+DlgLnkProtoOther       : "<inny>",\r
+DlgLnkURL                      : "Adres URL",\r
+DlgLnkAnchorSel                : "Wybierz etykietę",\r
+DlgLnkAnchorByName     : "Wg etykiety",\r
+DlgLnkAnchorById       : "Wg identyfikatora elementu",\r
+DlgLnkNoAnchors                : "<W dokumencie nie zdefiniowano żadnych etykiet>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Adres e-mail",\r
+DlgLnkEMailSubject     : "Temat",\r
+DlgLnkEMailBody                : "Treść",\r
+DlgLnkUpload           : "Upload",\r
+DlgLnkBtnUpload                : "Wyślij",\r
+\r
+DlgLnkTarget           : "Cel",\r
+DlgLnkTargetFrame      : "<ramka>",\r
+DlgLnkTargetPopup      : "<wyskakujące okno>",\r
+DlgLnkTargetBlank      : "Nowe okno (_blank)",\r
+DlgLnkTargetParent     : "Okno nadrzędne (_parent)",\r
+DlgLnkTargetSelf       : "To samo okno (_self)",\r
+DlgLnkTargetTop                : "Okno najwyższe w hierarchii (_top)",\r
+DlgLnkTargetFrameName  : "Nazwa Ramki Docelowej",\r
+DlgLnkPopWinName       : "Nazwa wyskakującego okna",\r
+DlgLnkPopWinFeat       : "Właściwości wyskakującego okna",\r
+DlgLnkPopResize                : "Możliwa zmiana rozmiaru",\r
+DlgLnkPopLocation      : "Pasek adresu",\r
+DlgLnkPopMenu          : "Pasek menu",\r
+DlgLnkPopScroll                : "Paski przewijania",\r
+DlgLnkPopStatus                : "Pasek statusu",\r
+DlgLnkPopToolbar       : "Pasek narzędzi",\r
+DlgLnkPopFullScrn      : "Pełny ekran (IE)",\r
+DlgLnkPopDependent     : "Okno zależne (Netscape)",\r
+DlgLnkPopWidth         : "Szerokość",\r
+DlgLnkPopHeight                : "Wysokość",\r
+DlgLnkPopLeft          : "Pozycja w poziomie",\r
+DlgLnkPopTop           : "Pozycja w pionie",\r
+\r
+DlnLnkMsgNoUrl         : "Podaj adres URL",\r
+DlnLnkMsgNoEMail       : "Podaj adres e-mail",\r
+DlnLnkMsgNoAnchor      : "Wybierz etykietę",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Wybierz kolor",\r
+DlgColorBtnClear       : "Wyczyść",\r
+DlgColorHighlight      : "Podgląd",\r
+DlgColorSelected       : "Wybrane",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Wstaw emotikonę",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Wybierz znak specjalny",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Właściwości tabeli",\r
+DlgTableRows           : "Liczba wierszy",\r
+DlgTableColumns                : "Liczba kolumn",\r
+DlgTableBorder         : "Grubość ramki",\r
+DlgTableAlign          : "Wyrównanie",\r
+DlgTableAlignNotSet    : "<brak ustawień>",\r
+DlgTableAlignLeft      : "Do lewej",\r
+DlgTableAlignCenter    : "Do środka",\r
+DlgTableAlignRight     : "Do prawej",\r
+DlgTableWidth          : "Szerokość",\r
+DlgTableWidthPx                : "piksele",\r
+DlgTableWidthPc                : "%",\r
+DlgTableHeight         : "Wysokość",\r
+DlgTableCellSpace      : "Odstęp pomiędzy komórkami",\r
+DlgTableCellPad                : "Margines wewnętrzny komórek",\r
+DlgTableCaption                : "Tytuł",\r
+DlgTableSummary                : "Podsumowanie",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Właściwości komórki",\r
+DlgCellWidth           : "Szerokość",\r
+DlgCellWidthPx         : "piksele",\r
+DlgCellWidthPc         : "%",\r
+DlgCellHeight          : "Wysokość",\r
+DlgCellWordWrap                : "Zawijanie tekstu",\r
+DlgCellWordWrapNotSet  : "<brak ustawień>",\r
+DlgCellWordWrapYes     : "Tak",\r
+DlgCellWordWrapNo      : "Nie",\r
+DlgCellHorAlign                : "Wyrównanie poziome",\r
+DlgCellHorAlignNotSet  : "<brak ustawień>",\r
+DlgCellHorAlignLeft    : "Do lewej",\r
+DlgCellHorAlignCenter  : "Do środka",\r
+DlgCellHorAlignRight: "Do prawej",\r
+DlgCellVerAlign                : "Wyrównanie pionowe",\r
+DlgCellVerAlignNotSet  : "<brak ustawień>",\r
+DlgCellVerAlignTop     : "Do góry",\r
+DlgCellVerAlignMiddle  : "Do środka",\r
+DlgCellVerAlignBottom  : "Do dołu",\r
+DlgCellVerAlignBaseline        : "Do linii bazowej",\r
+DlgCellRowSpan         : "Zajętość wierszy",\r
+DlgCellCollSpan                : "Zajętość kolumn",\r
+DlgCellBackColor       : "Kolor tła",\r
+DlgCellBorderColor     : "Kolor ramki",\r
+DlgCellBtnSelect       : "Wybierz...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Znajdź",\r
+DlgFindFindBtn         : "Znajdź",\r
+DlgFindNotFoundMsg     : "Nie znaleziono szukanego hasła.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Zamień",\r
+DlgReplaceFindLbl              : "Znajdź:",\r
+DlgReplaceReplaceLbl   : "Zastąp przez:",\r
+DlgReplaceCaseChk              : "Uwzględnij wielkość liter",\r
+DlgReplaceReplaceBtn   : "Zastąp",\r
+DlgReplaceReplAllBtn   : "Zastąp wszystko",\r
+DlgReplaceWordChk              : "Całe słowa",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Ustawienia bezpieczeństwa Twojej przeglądarki nie pozwalają na automatyczne wycinanie tekstu. Użyj skrótu klawiszowego Ctrl+X.",\r
+PasteErrorCopy : "Ustawienia bezpieczeństwa Twojej przeglądarki nie pozwalają na automatyczne kopiowanie tekstu. Użyj skrótu klawiszowego Ctrl+C.",\r
+\r
+PasteAsText            : "Wklej jako czysty tekst",\r
+PasteFromWord  : "Wklej z Worda",\r
+\r
+DlgPasteMsg2   : "Proszę wkleić w poniższym polu używając klawiaturowego skrótu (<STRONG>Ctrl+V</STRONG>) i kliknąć <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignoruj definicje 'Font Face'",\r
+DlgPasteRemoveStyles   : "Usuń definicje Stylów",\r
+DlgPasteCleanBox               : "Wyczyść",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatycznie",\r
+ColorMoreColors        : "Więcej kolorów...",\r
+\r
+// Document Properties\r
+DocProps               : "Właściwości dokumentu",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Właściwości kotwicy",\r
+DlgAnchorName          : "Nazwa kotwicy",\r
+DlgAnchorErrorName     : "Wpisz nazwę kotwicy",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Słowa nie ma w słowniku",\r
+DlgSpellChangeTo               : "Zmień na",\r
+DlgSpellBtnIgnore              : "Ignoruj",\r
+DlgSpellBtnIgnoreAll   : "Ignoruj wszystkie",\r
+DlgSpellBtnReplace             : "Zmień",\r
+DlgSpellBtnReplaceAll  : "Zmień wszystkie",\r
+DlgSpellBtnUndo                        : "Undo",\r
+DlgSpellNoSuggestions  : "- Brak sugestii -",\r
+DlgSpellProgress               : "Trwa sprawdzanie ...",\r
+DlgSpellNoMispell              : "Sprawdzanie zakończone: nie znaleziono błędów",\r
+DlgSpellNoChanges              : "Sprawdzanie zakończone: nie zmieniono żadnego słowa",\r
+DlgSpellOneChange              : "Sprawdzanie zakończone: zmieniono jedno słowo",\r
+DlgSpellManyChanges            : "Sprawdzanie zakończone: zmieniono %l słów",\r
+\r
+IeSpellDownload                        : "Słownik nie jest zainstalowany. Chcesz go ściągnąć?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekst (Wartość)",\r
+DlgButtonType          : "Typ",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nazwa",\r
+DlgCheckboxValue       : "Wartość",\r
+DlgCheckboxSelected    : "Zaznaczony",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nazwa",\r
+DlgFormAction  : "Akcja",\r
+DlgFormMethod  : "Metoda",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nazwa",\r
+DlgSelectValue         : "Wartość",\r
+DlgSelectSize          : "Rozmiar",\r
+DlgSelectLines         : "linii",\r
+DlgSelectChkMulti      : "Wielokrotny wybór",\r
+DlgSelectOpAvail       : "Dostępne opcje",\r
+DlgSelectOpText                : "Tekst",\r
+DlgSelectOpValue       : "Wartość",\r
+DlgSelectBtnAdd                : "Dodaj",\r
+DlgSelectBtnModify     : "Zmień",\r
+DlgSelectBtnUp         : "Do góry",\r
+DlgSelectBtnDown       : "Do dołu",\r
+DlgSelectBtnSetValue : "Ustaw wartość zaznaczoną",\r
+DlgSelectBtnDelete     : "Usuń",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nazwa",\r
+DlgTextareaCols        : "Kolumnu",\r
+DlgTextareaRows        : "Wiersze",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nazwa",\r
+DlgTextValue           : "Wartość",\r
+DlgTextCharWidth       : "Szerokość w znakach",\r
+DlgTextMaxChars                : "Max. szerokość",\r
+DlgTextType                    : "Typ",\r
+DlgTextTypeText                : "Tekst",\r
+DlgTextTypePass                : "Hasło",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nazwa",\r
+DlgHiddenValue : "Wartość",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Właściwości listy punktowanej",\r
+NumberedListProp       : "Właściwości listy numerowanej",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Typ",\r
+DlgLstTypeCircle       : "Koło",\r
+DlgLstTypeDisc         : "Dysk",\r
+DlgLstTypeSquare       : "Kwadrat",\r
+DlgLstTypeNumbers      : "Cyfry (1, 2, 3)",\r
+DlgLstTypeLCase                : "Małe litery (a, b, c)",\r
+DlgLstTypeUCase                : "Duże litery (A, B, C)",\r
+DlgLstTypeSRoman       : "Numeracja rzymska (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Numeracja rzymska (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Ogólne",\r
+DlgDocBackTab          : "Tło",\r
+DlgDocColorsTab                : "Kolory i marginesy",\r
+DlgDocMetaTab          : "Meta Dane",\r
+\r
+DlgDocPageTitle                : "Tytuł strony",\r
+DlgDocLangDir          : "Kierunek pisania",\r
+DlgDocLangDirLTR       : "Od lewej do prawej (LTR)",\r
+DlgDocLangDirRTL       : "Od prawej do lewej (RTL)",\r
+DlgDocLangCode         : "Kod języka",\r
+DlgDocCharSet          : "Kodowanie znaków",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Inne kodowanie znaków",\r
+\r
+DlgDocDocType          : "Nagłowek typu dokumentu",\r
+DlgDocDocTypeOther     : "Inny typ dokumentu",\r
+DlgDocIncXHTML         : "Dołącz deklarację XHTML",\r
+DlgDocBgColor          : "Kolor tła",\r
+DlgDocBgImage          : "Obrazek tła",\r
+DlgDocBgNoScroll       : "Tło nieruchome",\r
+DlgDocCText                    : "Tekst",\r
+DlgDocCLink                    : "Hiperłącze",\r
+DlgDocCVisited         : "Odwiedzane hiperłącze",\r
+DlgDocCActive          : "Aktywne hiperłącze",\r
+DlgDocMargins          : "Marginesy strony",\r
+DlgDocMaTop                    : "Górny",\r
+DlgDocMaLeft           : "Lewy",\r
+DlgDocMaRight          : "Prawy",\r
+DlgDocMaBottom         : "Dolny",\r
+DlgDocMeIndex          : "Słowa kluczowe (oddzielone przecinkami)",\r
+DlgDocMeDescr          : "Opis dokumentu",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Copyright",\r
+DlgDocPreview          : "Podgląd",\r
+\r
+// Templates Dialog\r
+Templates                      : "Sablony",\r
+DlgTemplatesTitle      : "Szablony zawartości",\r
+DlgTemplatesSelMsg     : "Wybierz szablon do otwarcia w edytorze<br>(obecna zawartość okna edytora zostanie utracona):",\r
+DlgTemplatesLoading    : "Ładowanie listy szablonów. Proszę czekać...",\r
+DlgTemplatesNoTpl      : "(Brak zdefiniowanych szablonów)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "O ...",\r
+DlgAboutBrowserInfoTab : "O przeglądarce",\r
+DlgAboutLicenseTab     : "Licencja",\r
+DlgAboutVersion                : "wersja",\r
+DlgAboutInfo           : "Więcej informacji uzyskasz pod adresem"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/pt-br.js b/httemplate/elements/fckeditor/editor/lang/pt-br.js
new file mode 100644 (file)
index 0000000..53a2b5d
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Brazilian Portuguese language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Ocultar Barra de Ferramentas",\r
+ToolbarExpand          : "Exibir Barra de Ferramentas",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Salvar",\r
+NewPage                                : "Novo",\r
+Preview                                : "Visualizar",\r
+Cut                                    : "Recortar",\r
+Copy                           : "Copiar",\r
+Paste                          : "Colar",\r
+PasteText                      : "Colar como Texto sem Formatação",\r
+PasteWord                      : "Colar do Word",\r
+Print                          : "Imprimir",\r
+SelectAll                      : "Selecionar Tudo",\r
+RemoveFormat           : "Remover Formatação",\r
+InsertLinkLbl          : "Hiperlink",\r
+InsertLink                     : "Inserir/Editar Hiperlink",\r
+RemoveLink                     : "Remover Hiperlink",\r
+Anchor                         : "Inserir/Editar Âncora",\r
+InsertImageLbl         : "Figura",\r
+InsertImage                    : "Inserir/Editar Figura",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Insere/Edita Flash",\r
+InsertTableLbl         : "Tabela",\r
+InsertTable                    : "Inserir/Editar Tabela",\r
+InsertLineLbl          : "Linha",\r
+InsertLine                     : "Inserir Linha Horizontal",\r
+InsertSpecialCharLbl: "Caracteres Especiais",\r
+InsertSpecialChar      : "Inserir Caractere Especial",\r
+InsertSmileyLbl                : "Emoticon",\r
+InsertSmiley           : "Inserir Emoticon",\r
+About                          : "Sobre FCKeditor",\r
+Bold                           : "Negrito",\r
+Italic                         : "Itálico",\r
+Underline                      : "Sublinhado",\r
+StrikeThrough          : "Tachado",\r
+Subscript                      : "Subscrito",\r
+Superscript                    : "Sobrescrito",\r
+LeftJustify                    : "Alinhar Esquerda",\r
+CenterJustify          : "Centralizar",\r
+RightJustify           : "Alinhar Direita",\r
+BlockJustify           : "Justificado",\r
+DecreaseIndent         : "Diminuir Recuo",\r
+IncreaseIndent         : "Aumentar Recuo",\r
+Undo                           : "Desfazer",\r
+Redo                           : "Refazer",\r
+NumberedListLbl                : "Numeração",\r
+NumberedList           : "Inserir/Remover Numeração",\r
+BulletedListLbl                : "Marcadores",\r
+BulletedList           : "Inserir/Remover Marcadores",\r
+ShowTableBorders       : "Exibir Bordas da Tabela",\r
+ShowDetails                    : "Exibir Detalhes",\r
+Style                          : "Estilo",\r
+FontFormat                     : "Formatação",\r
+Font                           : "Fonte",\r
+FontSize                       : "Tamanho",\r
+TextColor                      : "Cor do Texto",\r
+BGColor                                : "Cor do Plano de Fundo",\r
+Source                         : "Código-Fonte",\r
+Find                           : "Localizar",\r
+Replace                                : "Substituir",\r
+SpellCheck                     : "Verificar Ortografia",\r
+UniversalKeyboard      : "Teclado Universal",\r
+PageBreakLbl           : "Quebra de Página",\r
+PageBreak                      : "Inserir Quebra de Página",\r
+\r
+Form                   : "Formulário",\r
+Checkbox               : "Caixa de Seleção",\r
+RadioButton            : "Botão de Opção",\r
+TextField              : "Caixa de Texto",\r
+Textarea               : "Área de Texto",\r
+HiddenField            : "Campo Oculto",\r
+Button                 : "Botão",\r
+SelectionField : "Caixa de Listagem",\r
+ImageButton            : "Botão de Imagem",\r
+\r
+FitWindow              : "Maximizar o tamanho do editor",\r
+\r
+// Context Menu\r
+EditLink                       : "Editar Hiperlink",\r
+CellCM                         : "Célula",\r
+RowCM                          : "Linha",\r
+ColumnCM                       : "Coluna",\r
+InsertRow                      : "Inserir Linha",\r
+DeleteRows                     : "Remover Linhas",\r
+InsertColumn           : "Inserir Coluna",\r
+DeleteColumns          : "Remover Colunas",\r
+InsertCell                     : "Inserir Células",\r
+DeleteCells                    : "Remover Células",\r
+MergeCells                     : "Mesclar Células",\r
+SplitCell                      : "Dividir Célular",\r
+TableDelete                    : "Apagar Tabela",\r
+CellProperties         : "Formatar Célula",\r
+TableProperties                : "Formatar Tabela",\r
+ImageProperties                : "Formatar Figura",\r
+FlashProperties                : "Propriedades Flash",\r
+\r
+AnchorProp                     : "Formatar Âncora",\r
+ButtonProp                     : "Formatar Botão",\r
+CheckboxProp           : "Formatar Caixa de Seleção",\r
+HiddenFieldProp                : "Formatar Campo Oculto",\r
+RadioButtonProp                : "Formatar Botão de Opção",\r
+ImageButtonProp                : "Formatar Botão de Imagem",\r
+TextFieldProp          : "Formatar Caixa de Texto",\r
+SelectionFieldProp     : "Formatar Caixa de Listagem",\r
+TextareaProp           : "Formatar Área de Texto",\r
+FormProp                       : "Formatar Formulário",\r
+\r
+FontFormats                    : "Normal;Formatado;Endereço;Título 1;Título 2;Título 3;Título 4;Título 5;Título 6",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Processando XHTML. Por favor, aguarde...",\r
+Done                           : "Pronto",\r
+PasteWordConfirm       : "O texto que você deseja colar parece ter sido copiado do Word. Você gostaria de remover a formatação antes de colar?",\r
+NotCompatiblePaste     : "Este comando está disponível para o navegador Internet Explorer 5.5 ou superior. Você gostaria de colar sem remover a formatação?",\r
+UnknownToolbarItem     : "O item da barra de ferramentas \"%1\" não é reconhecido",\r
+UnknownCommand         : "O comando \"%1\" não é reconhecido",\r
+NotImplemented         : "O comando não foi implementado",\r
+UnknownToolbarSet      : "A barra de ferramentas \"%1\" não existe",\r
+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.",\r
+BrowseServerBlocked : "Os recursos do browser não puderam ser abertos. Tenha certeza que todos os bloqueadores de popup estão desabilitados.",\r
+DialogBlocked          : "Não foi possível abrir a janela de diálogo. Tenha certeza que todos os bloqueadores de popup estão desabilitados.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Cancelar",\r
+DlgBtnClose                    : "Fechar",\r
+DlgBtnBrowseServer     : "Localizar no Servidor",\r
+DlgAdvancedTag         : "Avançado",\r
+DlgOpOther                     : "<Outros>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Inserir a URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<não ajustado>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Direção do idioma",\r
+DlgGenLangDirLtr       : "Esquerda para Direita (LTR)",\r
+DlgGenLangDirRtl       : "Direita para Esquerda (RTL)",\r
+DlgGenLangCode         : "Idioma",\r
+DlgGenAccessKey                : "Chave de Acesso",\r
+DlgGenName                     : "Nome",\r
+DlgGenTabIndex         : "Índice de Tabulação",\r
+DlgGenLongDescr                : "Descrição da URL",\r
+DlgGenClass                    : "Classe de Folhas de Estilo",\r
+DlgGenTitle                    : "Título",\r
+DlgGenContType         : "Tipo de Conteúdo",\r
+DlgGenLinkCharset      : "Conjunto de Caracteres do Hiperlink",\r
+DlgGenStyle                    : "Estilos",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Formatar Figura",\r
+DlgImgInfoTab          : "Informações da Figura",\r
+DlgImgBtnUpload                : "Enviar para o Servidor",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Submeter",\r
+DlgImgAlt                      : "Texto Alternativo",\r
+DlgImgWidth                    : "Largura",\r
+DlgImgHeight           : "Altura",\r
+DlgImgLockRatio                : "Manter proporções",\r
+DlgBtnResetSize                : "Redefinir para o Tamanho Original",\r
+DlgImgBorder           : "Borda",\r
+DlgImgHSpace           : "Horizontal",\r
+DlgImgVSpace           : "Vertical",\r
+DlgImgAlign                    : "Alinhamento",\r
+DlgImgAlignLeft                : "Esquerda",\r
+DlgImgAlignAbsBottom: "Inferior Absoluto",\r
+DlgImgAlignAbsMiddle: "Centralizado Absoluto",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Inferior",\r
+DlgImgAlignMiddle      : "Centralizado",\r
+DlgImgAlignRight       : "Direita",\r
+DlgImgAlignTextTop     : "Superior Absoluto",\r
+DlgImgAlignTop         : "Superior",\r
+DlgImgPreview          : "Visualização",\r
+DlgImgAlertUrl         : "Por favor, digite o URL da figura.",\r
+DlgImgLinkTab          : "Hiperlink",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Propriedades Flash",\r
+DlgFlashChkPlay                : "Tocar Automaticamente",\r
+DlgFlashChkLoop                : "Loop",\r
+DlgFlashChkMenu                : "Habilita Menu Flash",\r
+DlgFlashScale          : "Escala",\r
+DlgFlashScaleAll       : "Mostrar tudo",\r
+DlgFlashScaleNoBorder  : "Sem Borda",\r
+DlgFlashScaleFit       : "Escala Exata",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Hiperlink",\r
+DlgLnkInfoTab          : "Informações",\r
+DlgLnkTargetTab                : "Destino",\r
+\r
+DlgLnkType                     : "Tipo de hiperlink",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Âncora nesta página",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocolo",\r
+DlgLnkProtoOther       : "<outro>",\r
+DlgLnkURL                      : "URL do hiperlink",\r
+DlgLnkAnchorSel                : "Selecione uma âncora",\r
+DlgLnkAnchorByName     : "Pelo Nome da âncora",\r
+DlgLnkAnchorById       : "Pelo Id do Elemento",\r
+DlgLnkNoAnchors                : "(Não há âncoras disponíveis neste documento)",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Endereço E-Mail",\r
+DlgLnkEMailSubject     : "Assunto da Mensagem",\r
+DlgLnkEMailBody                : "Corpo da Mensagem",\r
+DlgLnkUpload           : "Enviar ao Servidor",\r
+DlgLnkBtnUpload                : "Enviar ao Servidor",\r
+\r
+DlgLnkTarget           : "Destino",\r
+DlgLnkTargetFrame      : "<frame>",\r
+DlgLnkTargetPopup      : "<janela popup>",\r
+DlgLnkTargetBlank      : "Nova Janela (_blank)",\r
+DlgLnkTargetParent     : "Janela Pai (_parent)",\r
+DlgLnkTargetSelf       : "Mesma Janela (_self)",\r
+DlgLnkTargetTop                : "Janela Superior (_top)",\r
+DlgLnkTargetFrameName  : "Nome do Frame de Destino",\r
+DlgLnkPopWinName       : "Nome da Janela Pop-up",\r
+DlgLnkPopWinFeat       : "Atributos da Janela Pop-up",\r
+DlgLnkPopResize                : "Redimensionável",\r
+DlgLnkPopLocation      : "Barra de Endereços",\r
+DlgLnkPopMenu          : "Barra de Menus",\r
+DlgLnkPopScroll                : "Barras de Rolagem",\r
+DlgLnkPopStatus                : "Barra de Status",\r
+DlgLnkPopToolbar       : "Barra de Ferramentas",\r
+DlgLnkPopFullScrn      : "Modo Tela Cheia (IE)",\r
+DlgLnkPopDependent     : "Dependente (Netscape)",\r
+DlgLnkPopWidth         : "Largura",\r
+DlgLnkPopHeight                : "Altura",\r
+DlgLnkPopLeft          : "Esquerda",\r
+DlgLnkPopTop           : "Superior",\r
+\r
+DlnLnkMsgNoUrl         : "Por favor, digite o endereço do Hiperlink",\r
+DlnLnkMsgNoEMail       : "Por favor, digite o endereço de e-mail",\r
+DlnLnkMsgNoAnchor      : "Por favor, selecione uma âncora",\r
+DlnLnkMsgInvPopName    : "O nome da janela popup deve começar com uma letra ou sublinhado (_) e não pode conter espaços",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Selecione uma Cor",\r
+DlgColorBtnClear       : "Limpar",\r
+DlgColorHighlight      : "Visualização",\r
+DlgColorSelected       : "Selecionada",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Inserir Emoticon",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Selecione um Caractere Especial",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Formatar Tabela",\r
+DlgTableRows           : "Linhas",\r
+DlgTableColumns                : "Colunas",\r
+DlgTableBorder         : "Borda",\r
+DlgTableAlign          : "Alinhamento",\r
+DlgTableAlignNotSet    : "<Não ajustado>",\r
+DlgTableAlignLeft      : "Esquerda",\r
+DlgTableAlignCenter    : "Centralizado",\r
+DlgTableAlignRight     : "Direita",\r
+DlgTableWidth          : "Largura",\r
+DlgTableWidthPx                : "pixels",\r
+DlgTableWidthPc                : "%",\r
+DlgTableHeight         : "Altura",\r
+DlgTableCellSpace      : "Espaçamento",\r
+DlgTableCellPad                : "Enchimento",\r
+DlgTableCaption                : "Legenda",\r
+DlgTableSummary                : "Resumo",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Formatar célula",\r
+DlgCellWidth           : "Largura",\r
+DlgCellWidthPx         : "pixels",\r
+DlgCellWidthPc         : "%",\r
+DlgCellHeight          : "Altura",\r
+DlgCellWordWrap                : "Quebra de Linha",\r
+DlgCellWordWrapNotSet  : "<Não ajustado>",\r
+DlgCellWordWrapYes     : "Sim",\r
+DlgCellWordWrapNo      : "Não",\r
+DlgCellHorAlign                : "Alinhamento Horizontal",\r
+DlgCellHorAlignNotSet  : "<Não ajustado>",\r
+DlgCellHorAlignLeft    : "Esquerda",\r
+DlgCellHorAlignCenter  : "Centralizado",\r
+DlgCellHorAlignRight: "Direita",\r
+DlgCellVerAlign                : "Alinhamento Vertical",\r
+DlgCellVerAlignNotSet  : "<Não ajustado>",\r
+DlgCellVerAlignTop     : "Superior",\r
+DlgCellVerAlignMiddle  : "Centralizado",\r
+DlgCellVerAlignBottom  : "Inferior",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Transpor Linhas",\r
+DlgCellCollSpan                : "Transpor Colunas",\r
+DlgCellBackColor       : "Cor do Plano de Fundo",\r
+DlgCellBorderColor     : "Cor da Borda",\r
+DlgCellBtnSelect       : "Selecionar...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Localizar...",\r
+DlgFindFindBtn         : "Localizar",\r
+DlgFindNotFoundMsg     : "O texto especificado não foi encontrado.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Substituir",\r
+DlgReplaceFindLbl              : "Procurar por:",\r
+DlgReplaceReplaceLbl   : "Substituir por:",\r
+DlgReplaceCaseChk              : "Coincidir Maiúsculas/Minúsculas",\r
+DlgReplaceReplaceBtn   : "Substituir",\r
+DlgReplaceReplAllBtn   : "Substituir Tudo",\r
+DlgReplaceWordChk              : "Coincidir a palavra inteira",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Colar como Texto sem Formatação",\r
+PasteFromWord  : "Colar do Word",\r
+\r
+DlgPasteMsg2   : "Transfira o link usado no box usando o teclado com (<STRONG>Ctrl+V</STRONG>) e <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorar definições de fonte",\r
+DlgPasteRemoveStyles   : "Remove definições de estilo",\r
+DlgPasteCleanBox               : "Limpar Box",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automático",\r
+ColorMoreColors        : "Mais Cores...",\r
+\r
+// Document Properties\r
+DocProps               : "Propriedades Documento",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Formatar Âncora",\r
+DlgAnchorName          : "Nome da Âncora",\r
+DlgAnchorErrorName     : "Por favor, digite o nome da âncora",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Não encontrada",\r
+DlgSpellChangeTo               : "Alterar para",\r
+DlgSpellBtnIgnore              : "Ignorar uma vez",\r
+DlgSpellBtnIgnoreAll   : "Ignorar Todas",\r
+DlgSpellBtnReplace             : "Alterar",\r
+DlgSpellBtnReplaceAll  : "Alterar Todas",\r
+DlgSpellBtnUndo                        : "Desfazer",\r
+DlgSpellNoSuggestions  : "-sem sugestões de ortografia-",\r
+DlgSpellProgress               : "Verificação ortográfica em andamento...",\r
+DlgSpellNoMispell              : "Verificação encerrada: Não foram encontrados erros de ortografia",\r
+DlgSpellNoChanges              : "Verificação ortográfica encerrada: Não houve alterações",\r
+DlgSpellOneChange              : "Verificação ortográfica encerrada: Uma palavra foi alterada",\r
+DlgSpellManyChanges            : "Verificação ortográfica encerrada: %1 foram alteradas",\r
+\r
+IeSpellDownload                        : "A verificação ortográfica não foi instalada. Você gostaria de realizar o download agora?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Texto (Valor)",\r
+DlgButtonType          : "Tipo",\r
+DlgButtonTypeBtn       : "Botão",\r
+DlgButtonTypeSbm       : "Enviar",\r
+DlgButtonTypeRst       : "Limpar",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nome",\r
+DlgCheckboxValue       : "Valor",\r
+DlgCheckboxSelected    : "Selecionado",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nome",\r
+DlgFormAction  : "Action",\r
+DlgFormMethod  : "Método",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nome",\r
+DlgSelectValue         : "Valor",\r
+DlgSelectSize          : "Tamanho",\r
+DlgSelectLines         : "linhas",\r
+DlgSelectChkMulti      : "Permitir múltiplas seleções",\r
+DlgSelectOpAvail       : "Opções disponíveis",\r
+DlgSelectOpText                : "Texto",\r
+DlgSelectOpValue       : "Valor",\r
+DlgSelectBtnAdd                : "Adicionar",\r
+DlgSelectBtnModify     : "Modificar",\r
+DlgSelectBtnUp         : "Para cima",\r
+DlgSelectBtnDown       : "Para baixo",\r
+DlgSelectBtnSetValue : "Definir como selecionado",\r
+DlgSelectBtnDelete     : "Remover",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nome",\r
+DlgTextareaCols        : "Colunas",\r
+DlgTextareaRows        : "Linhas",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nome",\r
+DlgTextValue           : "Valor",\r
+DlgTextCharWidth       : "Comprimento (em caracteres)",\r
+DlgTextMaxChars                : "Número Máximo de Caracteres",\r
+DlgTextType                    : "Tipo",\r
+DlgTextTypeText                : "Texto",\r
+DlgTextTypePass                : "Senha",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nome",\r
+DlgHiddenValue : "Valor",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Formatar Marcadores",\r
+NumberedListProp       : "Formatar Numeração",\r
+DlgLstStart                    : "Iniciar",\r
+DlgLstType                     : "Tipo",\r
+DlgLstTypeCircle       : "Círculo",\r
+DlgLstTypeDisc         : "Disco",\r
+DlgLstTypeSquare       : "Quadrado",\r
+DlgLstTypeNumbers      : "Números (1, 2, 3)",\r
+DlgLstTypeLCase                : "Letras Minúsculas (a, b, c)",\r
+DlgLstTypeUCase                : "Letras Maiúsculas (A, B, C)",\r
+DlgLstTypeSRoman       : "Números Romanos Minúsculos (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Números Romanos Maiúsculos (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Geral",\r
+DlgDocBackTab          : "Plano de Fundo",\r
+DlgDocColorsTab                : "Cores e Margens",\r
+DlgDocMetaTab          : "Meta Dados",\r
+\r
+DlgDocPageTitle                : "Título da Página",\r
+DlgDocLangDir          : "Direção do Idioma",\r
+DlgDocLangDirLTR       : "Esquerda para Direita (LTR)",\r
+DlgDocLangDirRTL       : "Direita para Esquerda (RTL)",\r
+DlgDocLangCode         : "Código do Idioma",\r
+DlgDocCharSet          : "Codificação de Caracteres",\r
+DlgDocCharSetCE                : "Europa Central",\r
+DlgDocCharSetCT                : "Chinês Tradicional (Big5)",\r
+DlgDocCharSetCR                : "Cirílico",\r
+DlgDocCharSetGR                : "Grego",\r
+DlgDocCharSetJP                : "Japonês",\r
+DlgDocCharSetKR                : "Coreano",\r
+DlgDocCharSetTR                : "Turco",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Europa Ocidental",\r
+DlgDocCharSetOther     : "Outra Codificação de Caracteres",\r
+\r
+DlgDocDocType          : "Cabeçalho Tipo de Documento",\r
+DlgDocDocTypeOther     : "Other Document Type Heading",\r
+DlgDocIncXHTML         : "Incluir Declarações XHTML",\r
+DlgDocBgColor          : "Cor do Plano de Fundo",\r
+DlgDocBgImage          : "URL da Imagem de Plano de Fundo",\r
+DlgDocBgNoScroll       : "Plano de Fundo Fixo",\r
+DlgDocCText                    : "Texto",\r
+DlgDocCLink                    : "Hiperlink",\r
+DlgDocCVisited         : "Hiperlink Visitado",\r
+DlgDocCActive          : "Hiperlink Ativo",\r
+DlgDocMargins          : "Margens da Página",\r
+DlgDocMaTop                    : "Superior",\r
+DlgDocMaLeft           : "Inferior",\r
+DlgDocMaRight          : "Direita",\r
+DlgDocMaBottom         : "Inferior",\r
+DlgDocMeIndex          : "Palavras-chave de Indexação do Documento (separadas por vírgula)",\r
+DlgDocMeDescr          : "Descrição do Documento",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Direitos Autorais",\r
+DlgDocPreview          : "Visualizar",\r
+\r
+// Templates Dialog\r
+Templates                      : "Modelos de layout",\r
+DlgTemplatesTitle      : "Modelo de layout do conteúdo",\r
+DlgTemplatesSelMsg     : "Selecione um modelo de layout para ser aberto no editor<br>(o conteúdo atual será perdido):",\r
+DlgTemplatesLoading    : "Carregando a lista de modelos de layout. Aguarde...",\r
+DlgTemplatesNoTpl      : "(Não foram definidos modelos de layout)",\r
+DlgTemplatesReplace    : "Substituir o conteúdo atual",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Sobre",\r
+DlgAboutBrowserInfoTab : "Informações do Navegador",\r
+DlgAboutLicenseTab     : "Licença",\r
+DlgAboutVersion                : "versão",\r
+DlgAboutInfo           : "Para maiores informações visite"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/pt.js b/httemplate/elements/fckeditor/editor/lang/pt.js
new file mode 100644 (file)
index 0000000..23bab35
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Portuguese language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Fechar Barra",\r
+ToolbarExpand          : "Expandir Barra",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Guardar",\r
+NewPage                                : "Nova Página",\r
+Preview                                : "Pré-visualizar",\r
+Cut                                    : "Cortar",\r
+Copy                           : "Copiar",\r
+Paste                          : "Colar",\r
+PasteText                      : "Colar como texto não formatado",\r
+PasteWord                      : "Colar do Word",\r
+Print                          : "Imprimir",\r
+SelectAll                      : "Seleccionar Tudo",\r
+RemoveFormat           : "Eliminar Formato",\r
+InsertLinkLbl          : "Hiperligação",\r
+InsertLink                     : "Inserir/Editar Hiperligação",\r
+RemoveLink                     : "Eliminar Hiperligação",\r
+Anchor                         : " Inserir/Editar Âncora",\r
+InsertImageLbl         : "Imagem",\r
+InsertImage                    : "Inserir/Editar Imagem",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Inserir/Editar Flash",\r
+InsertTableLbl         : "Tabela",\r
+InsertTable                    : "Inserir/Editar Tabela",\r
+InsertLineLbl          : "Linha",\r
+InsertLine                     : "Inserir Linha Horizontal",\r
+InsertSpecialCharLbl: "Caracter Especial",\r
+InsertSpecialChar      : "Inserir Caracter Especial",\r
+InsertSmileyLbl                : "Emoticons",\r
+InsertSmiley           : "Inserir Emoticons",\r
+About                          : "Acerca do FCKeditor",\r
+Bold                           : "Negrito",\r
+Italic                         : "Itálico",\r
+Underline                      : "Sublinhado",\r
+StrikeThrough          : "Rasurado",\r
+Subscript                      : "Superior à Linha",\r
+Superscript                    : "Inferior à Linha",\r
+LeftJustify                    : "Alinhar à Esquerda",\r
+CenterJustify          : "Alinhar ao Centro",\r
+RightJustify           : "Alinhar à Direita",\r
+BlockJustify           : "Justificado",\r
+DecreaseIndent         : "Diminuir Avanço",\r
+IncreaseIndent         : "Aumentar Avanço",\r
+Undo                           : "Anular",\r
+Redo                           : "Repetir",\r
+NumberedListLbl                : "Numeração",\r
+NumberedList           : "Inserir/Eliminar Numeração",\r
+BulletedListLbl                : "Marcas",\r
+BulletedList           : "Inserir/Eliminar Marcas",\r
+ShowTableBorders       : "Mostrar Limites da Tabelas",\r
+ShowDetails                    : "Mostrar Parágrafo",\r
+Style                          : "Estilo",\r
+FontFormat                     : "Formato",\r
+Font                           : "Tipo de Letra",\r
+FontSize                       : "Tamanho",\r
+TextColor                      : "Cor do Texto",\r
+BGColor                                : "Cor de Fundo",\r
+Source                         : "Fonte",\r
+Find                           : "Procurar",\r
+Replace                                : "Substituir",\r
+SpellCheck                     : "Verificação Ortográfica",\r
+UniversalKeyboard      : "Teclado Universal",\r
+PageBreakLbl           : "Quebra de Página",\r
+PageBreak                      : "Inserir Quebra de Página",\r
+\r
+Form                   : "Formulário",\r
+Checkbox               : "Caixa de Verificação",\r
+RadioButton            : "Botão de Opção",\r
+TextField              : "Campo de Texto",\r
+Textarea               : "Área de Texto",\r
+HiddenField            : "Campo Escondido",\r
+Button                 : "Botão",\r
+SelectionField : "Caixa de Combinação",\r
+ImageButton            : "Botão de Imagem",\r
+\r
+FitWindow              : "Maximizar o tamanho do editor",\r
+\r
+// Context Menu\r
+EditLink                       : "Editar Hiperligação",\r
+CellCM                         : "Célula",\r
+RowCM                          : "Linha",\r
+ColumnCM                       : "Coluna",\r
+InsertRow                      : "Inserir Linha",\r
+DeleteRows                     : "Eliminar Linhas",\r
+InsertColumn           : "Inserir Coluna",\r
+DeleteColumns          : "Eliminar Coluna",\r
+InsertCell                     : "Inserir Célula",\r
+DeleteCells                    : "Eliminar Célula",\r
+MergeCells                     : "Unir Células",\r
+SplitCell                      : "Dividir Célula",\r
+TableDelete                    : "Eliminar Tabela",\r
+CellProperties         : "Propriedades da Célula",\r
+TableProperties                : "Propriedades da Tabela",\r
+ImageProperties                : "Propriedades da Imagem",\r
+FlashProperties                : "Propriedades do Flash",\r
+\r
+AnchorProp                     : "Propriedades da Âncora",\r
+ButtonProp                     : "Propriedades do Botão",\r
+CheckboxProp           : "Propriedades da Caixa de Verificação",\r
+HiddenFieldProp                : "Propriedades do Campo Escondido",\r
+RadioButtonProp                : "Propriedades do Botão de Opção",\r
+ImageButtonProp                : "Propriedades do Botão de imagens",\r
+TextFieldProp          : "Propriedades do Campo de Texto",\r
+SelectionFieldProp     : "Propriedades da Caixa de Combinação",\r
+TextareaProp           : "Propriedades da Área de Texto",\r
+FormProp                       : "Propriedades do Formulário",\r
+\r
+FontFormats                    : "Normal;Formatado;Endereço;Título 1;Título 2;Título 3;Título 4;Título 5;Título 6",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "A Processar XHTML. Por favor, espere...",\r
+Done                           : "Concluído",\r
+PasteWordConfirm       : "O texto que deseja parece ter sido copiado do Word. Deseja limpar a formatação antes de colar?",\r
+NotCompatiblePaste     : "Este comando só está disponível para Internet Explorer versão 5.5 ou superior. Deseja colar sem limpar a formatação?",\r
+UnknownToolbarItem     : "Item de barra desconhecido \"%1\"",\r
+UnknownCommand         : "Nome de comando desconhecido \"%1\"",\r
+NotImplemented         : "Comando não implementado",\r
+UnknownToolbarSet      : "Nome de barra \"%1\" não definido",\r
+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.",\r
+BrowseServerBlocked : "Não foi possível abrir o navegador de recursos. Certifique-se que todos os bloqueadores de popup estão desactivados.",\r
+DialogBlocked          : "Não foi possível abrir a janela de diálogo. Certifique-se que todos os bloqueadores de popup estão desactivados.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Cancelar",\r
+DlgBtnClose                    : "Fechar",\r
+DlgBtnBrowseServer     : "Navegar no Servidor",\r
+DlgAdvancedTag         : "Avançado",\r
+DlgOpOther                     : "<Outro>",\r
+DlgInfoTab                     : "Informação",\r
+DlgAlertUrl                    : "Por favor introduza o URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<Não definido>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Orientação de idioma",\r
+DlgGenLangDirLtr       : "Esquerda à Direita (LTR)",\r
+DlgGenLangDirRtl       : "Direita a Esquerda (RTL)",\r
+DlgGenLangCode         : "Código de Idioma",\r
+DlgGenAccessKey                : "Chave de Acesso",\r
+DlgGenName                     : "Nome",\r
+DlgGenTabIndex         : "Índice de Tubulação",\r
+DlgGenLongDescr                : "Descrição Completa do URL",\r
+DlgGenClass                    : "Classes de Estilo de Folhas Classes",\r
+DlgGenTitle                    : "Título",\r
+DlgGenContType         : "Tipo de Conteúdo",\r
+DlgGenLinkCharset      : "Fonte de caracteres vinculado",\r
+DlgGenStyle                    : "Estilo",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Propriedades da Imagem",\r
+DlgImgInfoTab          : "Informação da Imagem",\r
+DlgImgBtnUpload                : "Enviar para o Servidor",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Carregar",\r
+DlgImgAlt                      : "Texto Alternativo",\r
+DlgImgWidth                    : "Largura",\r
+DlgImgHeight           : "Altura",\r
+DlgImgLockRatio                : "Proporcional",\r
+DlgBtnResetSize                : "Tamanho Original",\r
+DlgImgBorder           : "Limite",\r
+DlgImgHSpace           : "Esp.Horiz",\r
+DlgImgVSpace           : "Esp.Vert",\r
+DlgImgAlign                    : "Alinhamento",\r
+DlgImgAlignLeft                : "Esquerda",\r
+DlgImgAlignAbsBottom: "Abs inferior",\r
+DlgImgAlignAbsMiddle: "Abs centro",\r
+DlgImgAlignBaseline    : "Linha de base",\r
+DlgImgAlignBottom      : "Fundo",\r
+DlgImgAlignMiddle      : "Centro",\r
+DlgImgAlignRight       : "Direita",\r
+DlgImgAlignTextTop     : "Topo do texto",\r
+DlgImgAlignTop         : "Topo",\r
+DlgImgPreview          : "Pré-visualizar",\r
+DlgImgAlertUrl         : "Por favor introduza o URL da imagem",\r
+DlgImgLinkTab          : "Hiperligação",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Propriedades do Flash",\r
+DlgFlashChkPlay                : "Reproduzir automaticamente",\r
+DlgFlashChkLoop                : "Loop",\r
+DlgFlashChkMenu                : "Permitir Menu do Flash",\r
+DlgFlashScale          : "Escala",\r
+DlgFlashScaleAll       : "Mostrar tudo",\r
+DlgFlashScaleNoBorder  : "Sem Limites",\r
+DlgFlashScaleFit       : "Tamanho Exacto",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Hiperligação",\r
+DlgLnkInfoTab          : "Informação de Hiperligação",\r
+DlgLnkTargetTab                : "Destino",\r
+\r
+DlgLnkType                     : "Tipo de Hiperligação",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Referência a esta página",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocolo",\r
+DlgLnkProtoOther       : "<outro>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Seleccionar una referência",\r
+DlgLnkAnchorByName     : "Por Nome de Referência",\r
+DlgLnkAnchorById       : "Por ID de elemento",\r
+DlgLnkNoAnchors                : "<Não há referências disponíveis no documento>",          //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Endereço de E-Mail",\r
+DlgLnkEMailSubject     : "Título de Mensagem",\r
+DlgLnkEMailBody                : "Corpo da Mensagem",\r
+DlgLnkUpload           : "Carregar",\r
+DlgLnkBtnUpload                : "Enviar ao Servidor",\r
+\r
+DlgLnkTarget           : "Destino",\r
+DlgLnkTargetFrame      : "<Frame>",\r
+DlgLnkTargetPopup      : "<Janela de popup>",\r
+DlgLnkTargetBlank      : "Nova Janela(_blank)",\r
+DlgLnkTargetParent     : "Janela Pai (_parent)",\r
+DlgLnkTargetSelf       : "Mesma janela (_self)",\r
+DlgLnkTargetTop                : "Janela primaria (_top)",\r
+DlgLnkTargetFrameName  : "Nome do Frame Destino",\r
+DlgLnkPopWinName       : "Nome da Janela de Popup",\r
+DlgLnkPopWinFeat       : "Características de Janela de Popup",\r
+DlgLnkPopResize                : "Ajustável",\r
+DlgLnkPopLocation      : "Barra de localização",\r
+DlgLnkPopMenu          : "Barra de Menu",\r
+DlgLnkPopScroll                : "Barras de deslocamento",\r
+DlgLnkPopStatus                : "Barra de Estado",\r
+DlgLnkPopToolbar       : "Barra de Ferramentas",\r
+DlgLnkPopFullScrn      : "Janela Completa (IE)",\r
+DlgLnkPopDependent     : "Dependente (Netscape)",\r
+DlgLnkPopWidth         : "Largura",\r
+DlgLnkPopHeight                : "Altura",\r
+DlgLnkPopLeft          : "Posição Esquerda",\r
+DlgLnkPopTop           : "Posição Direita",\r
+\r
+DlnLnkMsgNoUrl         : "Por favor introduza a hiperligação URL",\r
+DlnLnkMsgNoEMail       : "Por favor introduza o endereço de e-mail",\r
+DlnLnkMsgNoAnchor      : "Por favor seleccione uma referência",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Seleccionar Cor",\r
+DlgColorBtnClear       : "Nenhuma",\r
+DlgColorHighlight      : "Destacado",\r
+DlgColorSelected       : "Seleccionado",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Inserir um Emoticon",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Seleccione um caracter especial",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Propriedades da Tabela",\r
+DlgTableRows           : "Linhas",\r
+DlgTableColumns                : "Colunas",\r
+DlgTableBorder         : "Tamanho do Limite",\r
+DlgTableAlign          : "Alinhamento",\r
+DlgTableAlignNotSet    : "<Não definido>",\r
+DlgTableAlignLeft      : "Esquerda",\r
+DlgTableAlignCenter    : "Centrado",\r
+DlgTableAlignRight     : "Direita",\r
+DlgTableWidth          : "Largura",\r
+DlgTableWidthPx                : "pixeis",\r
+DlgTableWidthPc                : "percentagem",\r
+DlgTableHeight         : "Altura",\r
+DlgTableCellSpace      : "Esp. e/células",\r
+DlgTableCellPad                : "Esp. interior",\r
+DlgTableCaption                : "Título",\r
+DlgTableSummary                : "Sumário",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Propriedades da Célula",\r
+DlgCellWidth           : "Largura",\r
+DlgCellWidthPx         : "pixeis",\r
+DlgCellWidthPc         : "percentagem",\r
+DlgCellHeight          : "Altura",\r
+DlgCellWordWrap                : "Moldar Texto",\r
+DlgCellWordWrapNotSet  : "<Não definido>",\r
+DlgCellWordWrapYes     : "Sim",\r
+DlgCellWordWrapNo      : "Não",\r
+DlgCellHorAlign                : "Alinhamento Horizontal",\r
+DlgCellHorAlignNotSet  : "<Não definido>",\r
+DlgCellHorAlignLeft    : "Esquerda",\r
+DlgCellHorAlignCenter  : "Centrado",\r
+DlgCellHorAlignRight: "Direita",\r
+DlgCellVerAlign                : "Alinhamento Vertical",\r
+DlgCellVerAlignNotSet  : "<Não definido>",\r
+DlgCellVerAlignTop     : "Topo",\r
+DlgCellVerAlignMiddle  : "Médio",\r
+DlgCellVerAlignBottom  : "Fundi",\r
+DlgCellVerAlignBaseline        : "Linha de Base",\r
+DlgCellRowSpan         : "Unir Linhas",\r
+DlgCellCollSpan                : "Unir Colunas",\r
+DlgCellBackColor       : "Cor do Fundo",\r
+DlgCellBorderColor     : "Cor do Limite",\r
+DlgCellBtnSelect       : "Seleccione...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Procurar",\r
+DlgFindFindBtn         : "Procurar",\r
+DlgFindNotFoundMsg     : "O texto especificado não foi encontrado.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Substituir",\r
+DlgReplaceFindLbl              : "Texto a Procurar:",\r
+DlgReplaceReplaceLbl   : "Substituir por:",\r
+DlgReplaceCaseChk              : "Maiúsculas/Minúsculas",\r
+DlgReplaceReplaceBtn   : "Substituir",\r
+DlgReplaceReplAllBtn   : "Substituir Tudo",\r
+DlgReplaceWordChk              : "Coincidir com toda a palavra",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Colar como Texto Simples",\r
+PasteFromWord  : "Colar do Word",\r
+\r
+DlgPasteMsg2   : "Por favor, cole dentro da seguinte caixa usando o teclado (<STRONG>Ctrl+V</STRONG>) e prima <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorar da definições do Tipo de Letra ",\r
+DlgPasteRemoveStyles   : "Remover as definições de Estilos",\r
+DlgPasteCleanBox               : "Caixa de Limpeza",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automático",\r
+ColorMoreColors        : "Mais Cores...",\r
+\r
+// Document Properties\r
+DocProps               : "Propriedades do Documento",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Propriedades da Âncora",\r
+DlgAnchorName          : "Nome da Âncora",\r
+DlgAnchorErrorName     : "Por favor, introduza o nome da âncora",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Não está num directório",\r
+DlgSpellChangeTo               : "Mudar para",\r
+DlgSpellBtnIgnore              : "Ignorar",\r
+DlgSpellBtnIgnoreAll   : "Ignorar Tudo",\r
+DlgSpellBtnReplace             : "Substituir",\r
+DlgSpellBtnReplaceAll  : "Substituir Tudo",\r
+DlgSpellBtnUndo                        : "Anular",\r
+DlgSpellNoSuggestions  : "- Sem sugestões -",\r
+DlgSpellProgress               : "Verificação ortográfica em progresso…",\r
+DlgSpellNoMispell              : "Verificação ortográfica completa: não foram encontrados erros",\r
+DlgSpellNoChanges              : "Verificação ortográfica completa: não houve alteração de palavras",\r
+DlgSpellOneChange              : "Verificação ortográfica completa: uma palavra alterada",\r
+DlgSpellManyChanges            : "Verificação ortográfica completa: %1 palavras alteradas",\r
+\r
+IeSpellDownload                        : " Verificação ortográfica não instalada. Quer descarregar agora?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Texto (Valor)",\r
+DlgButtonType          : "Tipo",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nome",\r
+DlgCheckboxValue       : "Valor",\r
+DlgCheckboxSelected    : "Seleccionado",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nome",\r
+DlgFormAction  : "Acção",\r
+DlgFormMethod  : "Método",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nome",\r
+DlgSelectValue         : "Valor",\r
+DlgSelectSize          : "Tamanho",\r
+DlgSelectLines         : "linhas",\r
+DlgSelectChkMulti      : "Permitir selecções múltiplas",\r
+DlgSelectOpAvail       : "Opções Possíveis",\r
+DlgSelectOpText                : "Texto",\r
+DlgSelectOpValue       : "Valor",\r
+DlgSelectBtnAdd                : "Adicionar",\r
+DlgSelectBtnModify     : "Modificar",\r
+DlgSelectBtnUp         : "Para cima",\r
+DlgSelectBtnDown       : "Para baixo",\r
+DlgSelectBtnSetValue : "Definir um valor por defeito",\r
+DlgSelectBtnDelete     : "Apagar",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nome",\r
+DlgTextareaCols        : "Colunas",\r
+DlgTextareaRows        : "Linhas",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nome",\r
+DlgTextValue           : "Valor",\r
+DlgTextCharWidth       : "Tamanho do caracter",\r
+DlgTextMaxChars                : "Nr. Máximo de Caracteres",\r
+DlgTextType                    : "Tipo",\r
+DlgTextTypeText                : "Texto",\r
+DlgTextTypePass                : "Palavra-chave",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nome",\r
+DlgHiddenValue : "Valor",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Propriedades da Marca",\r
+NumberedListProp       : "Propriedades da Numeração",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Tipo",\r
+DlgLstTypeCircle       : "Circulo",\r
+DlgLstTypeDisc         : "Disco",\r
+DlgLstTypeSquare       : "Quadrado",\r
+DlgLstTypeNumbers      : "Números (1, 2, 3)",\r
+DlgLstTypeLCase                : "Letras Minúsculas (a, b, c)",\r
+DlgLstTypeUCase                : "Letras Maiúsculas (A, B, C)",\r
+DlgLstTypeSRoman       : "Numeração Romana em Minúsculas (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Numeração Romana em Maiúsculas (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Geral",\r
+DlgDocBackTab          : "Fundo",\r
+DlgDocColorsTab                : "Cores e Margens",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Título da Página",\r
+DlgDocLangDir          : "Orientação de idioma",\r
+DlgDocLangDirLTR       : "Esquerda à Direita (LTR)",\r
+DlgDocLangDirRTL       : "Direita à Esquerda (RTL)",\r
+DlgDocLangCode         : "Código de Idioma",\r
+DlgDocCharSet          : "Codificação de Caracteres",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Outra Codificação de Caracteres",\r
+\r
+DlgDocDocType          : "Tipo de Cabeçalho do Documento",\r
+DlgDocDocTypeOther     : "Outro Tipo de Cabeçalho do Documento",\r
+DlgDocIncXHTML         : "Incluir Declarações XHTML",\r
+DlgDocBgColor          : "Cor de Fundo",\r
+DlgDocBgImage          : "Caminho para a Imagem de Fundo",\r
+DlgDocBgNoScroll       : "Fundo Fixo",\r
+DlgDocCText                    : "Texto",\r
+DlgDocCLink                    : "Hiperligação",\r
+DlgDocCVisited         : "Hiperligação Visitada",\r
+DlgDocCActive          : "Hiperligação Activa",\r
+DlgDocMargins          : "Margem das Páginas",\r
+DlgDocMaTop                    : "Topo",\r
+DlgDocMaLeft           : "Esquerda",\r
+DlgDocMaRight          : "Direita",\r
+DlgDocMaBottom         : "Fundo",\r
+DlgDocMeIndex          : "Palavras de Indexação do Documento (separadas por virgula)",\r
+DlgDocMeDescr          : "Descrição do Documento",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Direitos de Autor",\r
+DlgDocPreview          : "Pré-visualizar",\r
+\r
+// Templates Dialog\r
+Templates                      : "Modelos",\r
+DlgTemplatesTitle      : "Modelo de Conteúdo",\r
+DlgTemplatesSelMsg     : "Por favor, seleccione o modelo a abrir no editor<br>(o conteúdo actual será perdido):",\r
+DlgTemplatesLoading    : "A carregar a lista de modelos. Aguarde por favor...",\r
+DlgTemplatesNoTpl      : "(Sem modelos definidos)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Acerca",\r
+DlgAboutBrowserInfoTab : "Informação do Nevegador",\r
+DlgAboutLicenseTab     : "Licença",\r
+DlgAboutVersion                : "versão",\r
+DlgAboutInfo           : "Para mais informações por favor dirija-se a"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/ro.js b/httemplate/elements/fckeditor/editor/lang/ro.js
new file mode 100644 (file)
index 0000000..1f36961
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Romanian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Ascunde bara cu opţiuni",\r
+ToolbarExpand          : "Expandează bara cu opţiuni",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Salvează",\r
+NewPage                                : "Pagină nouă",\r
+Preview                                : "Previzualizare",\r
+Cut                                    : "Taie",\r
+Copy                           : "Copiază",\r
+Paste                          : "Adaugă",\r
+PasteText                      : "Adaugă ca text simplu",\r
+PasteWord                      : "Adaugă din Word",\r
+Print                          : "Printează",\r
+SelectAll                      : "Selectează tot",\r
+RemoveFormat           : "Înlătură formatarea",\r
+InsertLinkLbl          : "Link (Legătură web)",\r
+InsertLink                     : "Inserează/Editează link (legătură web)",\r
+RemoveLink                     : "Înlătură link (legătură web)",\r
+Anchor                         : "Inserează/Editează ancoră",\r
+InsertImageLbl         : "Imagine",\r
+InsertImage                    : "Inserează/Editează imagine",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Inserează/Editează flash",\r
+InsertTableLbl         : "Tabel",\r
+InsertTable                    : "Inserează/Editează tabel",\r
+InsertLineLbl          : "Linie",\r
+InsertLine                     : "Inserează linie orizontă",\r
+InsertSpecialCharLbl: "Caracter special",\r
+InsertSpecialChar      : "Inserează caracter special",\r
+InsertSmileyLbl                : "Figură expresivă (Emoticon)",\r
+InsertSmiley           : "Inserează Figură expresivă (Emoticon)",\r
+About                          : "Despre FCKeditor",\r
+Bold                           : "Îngroşat (bold)",\r
+Italic                         : "Înclinat (italic)",\r
+Underline                      : "Subliniat (underline)",\r
+StrikeThrough          : "Tăiat (strike through)",\r
+Subscript                      : "Indice (subscript)",\r
+Superscript                    : "Putere (superscript)",\r
+LeftJustify                    : "Aliniere la stânga",\r
+CenterJustify          : "Aliniere centrală",\r
+RightJustify           : "Aliniere la dreapta",\r
+BlockJustify           : "Aliniere în bloc (Block Justify)",\r
+DecreaseIndent         : "Scade indentarea",\r
+IncreaseIndent         : "Creşte indentarea",\r
+Undo                           : "Starea anterioară (undo)",\r
+Redo                           : "Starea ulterioară (redo)",\r
+NumberedListLbl                : "Listă numerotată",\r
+NumberedList           : "Inserează/Şterge listă numerotată",\r
+BulletedListLbl                : "Listă cu puncte",\r
+BulletedList           : "Inserează/Şterge listă cu puncte",\r
+ShowTableBorders       : "Arată marginile tabelului",\r
+ShowDetails                    : "Arată detalii",\r
+Style                          : "Stil",\r
+FontFormat                     : "Formatare",\r
+Font                           : "Font",\r
+FontSize                       : "Mărime",\r
+TextColor                      : "Culoarea textului",\r
+BGColor                                : "Coloarea fundalului",\r
+Source                         : "Sursa",\r
+Find                           : "Găseşte",\r
+Replace                                : "Înlocuieşte",\r
+SpellCheck                     : "Verifică text",\r
+UniversalKeyboard      : "Tastatură universală",\r
+PageBreakLbl           : "Separator de pagină (Page Break)",\r
+PageBreak                      : "Inserează separator de pagină (Page Break)",\r
+\r
+Form                   : "Formular (Form)",\r
+Checkbox               : "Bifă (Checkbox)",\r
+RadioButton            : "Buton radio (RadioButton)",\r
+TextField              : "Câmp text (TextField)",\r
+Textarea               : "Suprafaţă text (Textarea)",\r
+HiddenField            : "Câmp ascuns (HiddenField)",\r
+Button                 : "Buton",\r
+SelectionField : "Câmp selecţie (SelectionField)",\r
+ImageButton            : "Buton imagine (ImageButton)",\r
+\r
+FitWindow              : "Maximizează mărimea editorului",\r
+\r
+// Context Menu\r
+EditLink                       : "Editează Link",\r
+CellCM                         : "Celulă",\r
+RowCM                          : "Linie",\r
+ColumnCM                       : "Coloană",\r
+InsertRow                      : "Inserează linie",\r
+DeleteRows                     : "Şterge linii",\r
+InsertColumn           : "Inserează coloană",\r
+DeleteColumns          : "Şterge celule",\r
+InsertCell                     : "Inserează celulă",\r
+DeleteCells                    : "Şterge celule",\r
+MergeCells                     : "Uneşte celule",\r
+SplitCell                      : "Împarte celulă",\r
+TableDelete                    : "Şterge tabel",\r
+CellProperties         : "Proprietăţile celulei",\r
+TableProperties                : "Proprietăţile tabelului",\r
+ImageProperties                : "Proprietăţile imaginii",\r
+FlashProperties                : "Proprietăţile flash-ului",\r
+\r
+AnchorProp                     : "Proprietăţi ancoră",\r
+ButtonProp                     : "Proprietăţi buton",\r
+CheckboxProp           : "Proprietăţi bifă (Checkbox)",\r
+HiddenFieldProp                : "Proprietăţi câmp ascuns (Hidden Field)",\r
+RadioButtonProp                : "Proprietăţi buton radio (Radio Button)",\r
+ImageButtonProp                : "Proprietăţi buton imagine (Image Button)",\r
+TextFieldProp          : "Proprietăţi câmp text (Text Field)",\r
+SelectionFieldProp     : "Proprietăţi câmp selecţie (Selection Field)",\r
+TextareaProp           : "Proprietăţi suprafaţă text (Textarea)",\r
+FormProp                       : "Proprietăţi formular (Form)",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html    //MISSING\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Procesăm XHTML. Vă rugăm aşteptaţi...",\r
+Done                           : "Am terminat",\r
+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?",\r
+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?",\r
+UnknownToolbarItem     : "Obiectul \"%1\" din bara cu opţiuni necunoscut",\r
+UnknownCommand         : "Comanda \"%1\" necunoscută",\r
+NotImplemented         : "Comandă neimplementată",\r
+UnknownToolbarSet      : "Grupul din bara cu opţiuni \"%1\" nu există",\r
+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ă.",\r
+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).",\r
+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).",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "Bine",\r
+DlgBtnCancel           : "Anulare",\r
+DlgBtnClose                    : "Închidere",\r
+DlgBtnBrowseServer     : "Răsfoieşte server",\r
+DlgAdvancedTag         : "Avansat",\r
+DlgOpOther                     : "<Altul>",\r
+DlgInfoTab                     : "Informaţii",\r
+DlgAlertUrl                    : "Vă rugăm să scrieţi URL-ul",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nesetat>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Direcţia cuvintelor",\r
+DlgGenLangDirLtr       : "stânga-dreapta (LTR)",\r
+DlgGenLangDirRtl       : "dreapta-stânga (RTL)",\r
+DlgGenLangCode         : "Codul limbii",\r
+DlgGenAccessKey                : "Tasta de acces",\r
+DlgGenName                     : "Nume",\r
+DlgGenTabIndex         : "Indexul tabului",\r
+DlgGenLongDescr                : "Descrierea lungă URL",\r
+DlgGenClass                    : "Clasele cu stilul paginii (CSS)",\r
+DlgGenTitle                    : "Titlul consultativ",\r
+DlgGenContType         : "Tipul consultativ al titlului",\r
+DlgGenLinkCharset      : "Setul de caractere al resursei legate",\r
+DlgGenStyle                    : "Stil",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Proprietăţile imaginii",\r
+DlgImgInfoTab          : "Informaţii despre imagine",\r
+DlgImgBtnUpload                : "Trimite la server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Încarcă",\r
+DlgImgAlt                      : "Text alternativ",\r
+DlgImgWidth                    : "Lăţime",\r
+DlgImgHeight           : "Înălţime",\r
+DlgImgLockRatio                : "Păstrează proporţiile",\r
+DlgBtnResetSize                : "Resetează mărimea",\r
+DlgImgBorder           : "Margine",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Aliniere",\r
+DlgImgAlignLeft                : "Stânga",\r
+DlgImgAlignAbsBottom: "Jos absolut (Abs Bottom)",\r
+DlgImgAlignAbsMiddle: "Mijloc absolut (Abs Middle)",\r
+DlgImgAlignBaseline    : "Linia de jos (Baseline)",\r
+DlgImgAlignBottom      : "Jos",\r
+DlgImgAlignMiddle      : "Mijloc",\r
+DlgImgAlignRight       : "Dreapta",\r
+DlgImgAlignTextTop     : "Text sus",\r
+DlgImgAlignTop         : "Sus",\r
+DlgImgPreview          : "Previzualizare",\r
+DlgImgAlertUrl         : "Vă rugăm să scrieţi URL-ul imaginii",\r
+DlgImgLinkTab          : "Link (Legătură web)",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Proprietăţile flash-ului",\r
+DlgFlashChkPlay                : "Rulează automat",\r
+DlgFlashChkLoop                : "Repetă (Loop)",\r
+DlgFlashChkMenu                : "Activează meniul flash",\r
+DlgFlashScale          : "Scală",\r
+DlgFlashScaleAll       : "Arată tot",\r
+DlgFlashScaleNoBorder  : "Fără margini (No border)",\r
+DlgFlashScaleFit       : "Potriveşte",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link (Legătură web)",\r
+DlgLnkInfoTab          : "Informaţii despre link (Legătură web)",\r
+DlgLnkTargetTab                : "Ţintă (Target)",\r
+\r
+DlgLnkType                     : "Tipul link-ului (al legăturii web)",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Ancoră în această pagină",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protocol",\r
+DlgLnkProtoOther       : "<altul>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Selectaţi o ancoră",\r
+DlgLnkAnchorByName     : "după numele ancorei",\r
+DlgLnkAnchorById       : "după Id-ul elementului",\r
+DlgLnkNoAnchors                : "<Nicio ancoră disponibilă în document>",          //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Adresă de e-mail",\r
+DlgLnkEMailSubject     : "Subiectul mesajului",\r
+DlgLnkEMailBody                : "Conţinutul mesajului",\r
+DlgLnkUpload           : "Încarcă",\r
+DlgLnkBtnUpload                : "Trimite la server",\r
+\r
+DlgLnkTarget           : "Ţintă (Target)",\r
+DlgLnkTargetFrame      : "<frame>",\r
+DlgLnkTargetPopup      : "<fereastra popup>",\r
+DlgLnkTargetBlank      : "Fereastră nouă (_blank)",\r
+DlgLnkTargetParent     : "Fereastra părinte (_parent)",\r
+DlgLnkTargetSelf       : "Aceeaşi fereastră (_self)",\r
+DlgLnkTargetTop                : "Fereastra din topul ierarhiei (_top)",\r
+DlgLnkTargetFrameName  : "Numele frame-ului ţintă",\r
+DlgLnkPopWinName       : "Numele ferestrei popup",\r
+DlgLnkPopWinFeat       : "Proprietăţile ferestrei popup",\r
+DlgLnkPopResize                : "Scalabilă",\r
+DlgLnkPopLocation      : "Bara de locaţie",\r
+DlgLnkPopMenu          : "Bara de meniu",\r
+DlgLnkPopScroll                : "Scroll Bars",\r
+DlgLnkPopStatus                : "Bara de status",\r
+DlgLnkPopToolbar       : "Bara de opţiuni",\r
+DlgLnkPopFullScrn      : "Tot ecranul (Full Screen)(IE)",\r
+DlgLnkPopDependent     : "Dependent (Netscape)",\r
+DlgLnkPopWidth         : "Lăţime",\r
+DlgLnkPopHeight                : "Înălţime",\r
+DlgLnkPopLeft          : "Poziţia la stânga",\r
+DlgLnkPopTop           : "Poziţia la dreapta",\r
+\r
+DlnLnkMsgNoUrl         : "Vă rugăm să scrieţi URL-ul",\r
+DlnLnkMsgNoEMail       : "Vă rugăm să scrieţi adresa de e-mail",\r
+DlnLnkMsgNoAnchor      : "Vă rugăm să selectaţi o ancoră",\r
+DlnLnkMsgInvPopName    : "Numele 'popup'-ului trebuie să înceapă cu un caracter alfabetic şi trebuie să nu conţină spaţii",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Selectează culoare",\r
+DlgColorBtnClear       : "Curăţă",\r
+DlgColorHighlight      : "Subliniază (Highlight)",\r
+DlgColorSelected       : "Selectat",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Inserează o figură expresivă (Emoticon)",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Selectează caracter special",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Proprietăţile tabelului",\r
+DlgTableRows           : "Linii",\r
+DlgTableColumns                : "Coloane",\r
+DlgTableBorder         : "Mărimea marginii",\r
+DlgTableAlign          : "Aliniament",\r
+DlgTableAlignNotSet    : "<Nesetat>",\r
+DlgTableAlignLeft      : "Stânga",\r
+DlgTableAlignCenter    : "Centru",\r
+DlgTableAlignRight     : "Dreapta",\r
+DlgTableWidth          : "Lăţime",\r
+DlgTableWidthPx                : "pixeli",\r
+DlgTableWidthPc                : "procente",\r
+DlgTableHeight         : "Înălţime",\r
+DlgTableCellSpace      : "Spaţiu între celule",\r
+DlgTableCellPad                : "Spaţiu în cadrul celulei",\r
+DlgTableCaption                : "Titlu (Caption)",\r
+DlgTableSummary                : "Rezumat",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Proprietăţile celulei",\r
+DlgCellWidth           : "Lăţime",\r
+DlgCellWidthPx         : "pixeli",\r
+DlgCellWidthPc         : "procente",\r
+DlgCellHeight          : "Înălţime",\r
+DlgCellWordWrap                : "Desparte cuvintele (Wrap)",\r
+DlgCellWordWrapNotSet  : "<Nesetat>",\r
+DlgCellWordWrapYes     : "Da",\r
+DlgCellWordWrapNo      : "Nu",\r
+DlgCellHorAlign                : "Aliniament orizontal",\r
+DlgCellHorAlignNotSet  : "<Nesetat>",\r
+DlgCellHorAlignLeft    : "Stânga",\r
+DlgCellHorAlignCenter  : "Centru",\r
+DlgCellHorAlignRight: "Dreapta",\r
+DlgCellVerAlign                : "Aliniament vertical",\r
+DlgCellVerAlignNotSet  : "<Nesetat>",\r
+DlgCellVerAlignTop     : "Sus",\r
+DlgCellVerAlignMiddle  : "Mijloc",\r
+DlgCellVerAlignBottom  : "Jos",\r
+DlgCellVerAlignBaseline        : "Linia de jos (Baseline)",\r
+DlgCellRowSpan         : "Lungimea în linii (Span)",\r
+DlgCellCollSpan                : "Lungimea în coloane (Span)",\r
+DlgCellBackColor       : "Culoarea fundalului",\r
+DlgCellBorderColor     : "Culoarea marginii",\r
+DlgCellBtnSelect       : "Selectaţi...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Găseşte",\r
+DlgFindFindBtn         : "Găseşte",\r
+DlgFindNotFoundMsg     : "Textul specificat nu a fost găsit.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Replace",\r
+DlgReplaceFindLbl              : "Găseşte:",\r
+DlgReplaceReplaceLbl   : "Înlocuieşte cu:",\r
+DlgReplaceCaseChk              : "Deosebeşte majuscule de minuscule (Match case)",\r
+DlgReplaceReplaceBtn   : "Înlocuieşte",\r
+DlgReplaceReplAllBtn   : "Înlocuieşte tot",\r
+DlgReplaceWordChk              : "Doar cuvintele întregi",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Adaugă ca text simplu (Plain Text)",\r
+PasteFromWord  : "Adaugă din Word",\r
+\r
+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>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignoră definiţiile Font Face",\r
+DlgPasteRemoveStyles   : "Şterge definiţiile stilurilor",\r
+DlgPasteCleanBox               : "Şterge căsuţa",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatic",\r
+ColorMoreColors        : "Mai multe culori...",\r
+\r
+// Document Properties\r
+DocProps               : "Proprietăţile documentului",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Proprietăţile ancorei",\r
+DlgAnchorName          : "Numele ancorei",\r
+DlgAnchorErrorName     : "Vă rugăm scrieţi numele ancorei",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Nu e în dicţionar",\r
+DlgSpellChangeTo               : "Schimbă în",\r
+DlgSpellBtnIgnore              : "Ignoră",\r
+DlgSpellBtnIgnoreAll   : "Ignoră toate",\r
+DlgSpellBtnReplace             : "Înlocuieşte",\r
+DlgSpellBtnReplaceAll  : "Înlocuieşte tot",\r
+DlgSpellBtnUndo                        : "Starea anterioară (undo)",\r
+DlgSpellNoSuggestions  : "- Fără sugestii -",\r
+DlgSpellProgress               : "Verificarea textului în desfăşurare...",\r
+DlgSpellNoMispell              : "Verificarea textului terminată: Nicio greşeală găsită",\r
+DlgSpellNoChanges              : "Verificarea textului terminată: Niciun cuvânt modificat",\r
+DlgSpellOneChange              : "Verificarea textului terminată: Un cuvânt modificat",\r
+DlgSpellManyChanges            : "Verificarea textului terminată: 1% cuvinte modificate",\r
+\r
+IeSpellDownload                        : "Unealta pentru verificat textul (Spell checker) neinstalată. Doriţi să o descărcaţi acum?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Valoare)",\r
+DlgButtonType          : "Tip",\r
+DlgButtonTypeBtn       : "Button",\r
+DlgButtonTypeSbm       : "Submit",\r
+DlgButtonTypeRst       : "Reset",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Nume",\r
+DlgCheckboxValue       : "Valoare",\r
+DlgCheckboxSelected    : "Selectat",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Nume",\r
+DlgFormAction  : "Acţiune",\r
+DlgFormMethod  : "Metodă",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Nume",\r
+DlgSelectValue         : "Valoare",\r
+DlgSelectSize          : "Mărime",\r
+DlgSelectLines         : "linii",\r
+DlgSelectChkMulti      : "Permite selecţii multiple",\r
+DlgSelectOpAvail       : "Opţiuni disponibile",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Valoare",\r
+DlgSelectBtnAdd                : "Adaugă",\r
+DlgSelectBtnModify     : "Modifică",\r
+DlgSelectBtnUp         : "Sus",\r
+DlgSelectBtnDown       : "Jos",\r
+DlgSelectBtnSetValue : "Setează ca valoare selectată",\r
+DlgSelectBtnDelete     : "Şterge",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Nume",\r
+DlgTextareaCols        : "Coloane",\r
+DlgTextareaRows        : "Linii",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Nume",\r
+DlgTextValue           : "Valoare",\r
+DlgTextCharWidth       : "Lărgimea caracterului",\r
+DlgTextMaxChars                : "Caractere maxime",\r
+DlgTextType                    : "Tip",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Parolă",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Nume",\r
+DlgHiddenValue : "Valoare",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Proprietăţile listei punctate (Bulleted List)",\r
+NumberedListProp       : "Proprietăţile listei numerotate (Numbered List)",\r
+DlgLstStart                    : "Start",\r
+DlgLstType                     : "Tip",\r
+DlgLstTypeCircle       : "Cerc",\r
+DlgLstTypeDisc         : "Disc",\r
+DlgLstTypeSquare       : "Pătrat",\r
+DlgLstTypeNumbers      : "Numere (1, 2, 3)",\r
+DlgLstTypeLCase                : "Minuscule-litere mici (a, b, c)",\r
+DlgLstTypeUCase                : "Majuscule (A, B, C)",\r
+DlgLstTypeSRoman       : "Cifre romane mici (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Cifre romane mari (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "General",\r
+DlgDocBackTab          : "Fundal",\r
+DlgDocColorsTab                : "Culori si margini",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Titlul paginii",\r
+DlgDocLangDir          : "Descrierea limbii",\r
+DlgDocLangDirLTR       : "stânga-dreapta (LTR)",\r
+DlgDocLangDirRTL       : "dreapta-stânga (RTL)",\r
+DlgDocLangCode         : "Codul limbii",\r
+DlgDocCharSet          : "Encoding setului de caractere",\r
+DlgDocCharSetCE                : "Central european",\r
+DlgDocCharSetCT                : "Chinezesc tradiţional (Big5)",\r
+DlgDocCharSetCR                : "Chirilic",\r
+DlgDocCharSetGR                : "Grecesc",\r
+DlgDocCharSetJP                : "Japonez",\r
+DlgDocCharSetKR                : "Corean",\r
+DlgDocCharSetTR                : "Turcesc",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Vest european",\r
+DlgDocCharSetOther     : "Alt encoding al setului de caractere",\r
+\r
+DlgDocDocType          : "Document Type Heading",\r
+DlgDocDocTypeOther     : "Alt Document Type Heading",\r
+DlgDocIncXHTML         : "Include declaraţii XHTML",\r
+DlgDocBgColor          : "Culoarea fundalului (Background Color)",\r
+DlgDocBgImage          : "URL-ul imaginii din fundal (Background Image URL)",\r
+DlgDocBgNoScroll       : "Fundal neflotant, fix (Nonscrolling Background)",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Link (Legătură web)",\r
+DlgDocCVisited         : "Link (Legătură web) vizitat",\r
+DlgDocCActive          : "Link (Legătură web) activ",\r
+DlgDocMargins          : "Marginile paginii",\r
+DlgDocMaTop                    : "Sus",\r
+DlgDocMaLeft           : "Stânga",\r
+DlgDocMaRight          : "Dreapta",\r
+DlgDocMaBottom         : "Jos",\r
+DlgDocMeIndex          : "Cuvinte cheie după care se va indexa documentul (separate prin virgulă)",\r
+DlgDocMeDescr          : "Descrierea documentului",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Drepturi de autor",\r
+DlgDocPreview          : "Previzualizare",\r
+\r
+// Templates Dialog\r
+Templates                      : "Template-uri (şabloane)",\r
+DlgTemplatesTitle      : "Template-uri (şabloane) de conţinut",\r
+DlgTemplatesSelMsg     : "Vă rugăm selectaţi template-ul (şablonul) ce se va deschide în editor<br>(conţinutul actual va fi pierdut):",\r
+DlgTemplatesLoading    : "Se încarcă lista cu template-uri (şabloane). Vă rugăm aşteptaţi...",\r
+DlgTemplatesNoTpl      : "(Niciun template (şablon) definit)",\r
+DlgTemplatesReplace    : "Înlocuieşte cuprinsul actual",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Despre",\r
+DlgAboutBrowserInfoTab : "Informaţii browser",\r
+DlgAboutLicenseTab     : "Licenţă",\r
+DlgAboutVersion                : "versiune",\r
+DlgAboutInfo           : "Pentru informaţii amănunţite, vizitaţi"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/ru.js b/httemplate/elements/fckeditor/editor/lang/ru.js
new file mode 100644 (file)
index 0000000..fdf151b
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Russian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Свернуть панель инструментов",\r
+ToolbarExpand          : "Развернуть панель инструментов",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Сохранить",\r
+NewPage                                : "Новая страница",\r
+Preview                                : "Предварительный просмотр",\r
+Cut                                    : "Вырезать",\r
+Copy                           : "Копировать",\r
+Paste                          : "Вставить",\r
+PasteText                      : "Вставить только текст",\r
+PasteWord                      : "Вставить из Word",\r
+Print                          : "Печать",\r
+SelectAll                      : "Выделить все",\r
+RemoveFormat           : "Убрать форматирование",\r
+InsertLinkLbl          : "Ссылка",\r
+InsertLink                     : "Вставить/Редактировать ссылку",\r
+RemoveLink                     : "Убрать ссылку",\r
+Anchor                         : "Вставить/Редактировать якорь",\r
+InsertImageLbl         : "Изображение",\r
+InsertImage                    : "Вставить/Редактировать изображение",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Вставить/Редактировать Flash",\r
+InsertTableLbl         : "Таблица",\r
+InsertTable                    : "Вставить/Редактировать таблицу",\r
+InsertLineLbl          : "Линия",\r
+InsertLine                     : "Вставить горизонтальную линию",\r
+InsertSpecialCharLbl: "Специальный символ",\r
+InsertSpecialChar      : "Вставить специальный символ",\r
+InsertSmileyLbl                : "Смайлик",\r
+InsertSmiley           : "Вставить смайлик",\r
+About                          : "О FCKeditor",\r
+Bold                           : "Жирный",\r
+Italic                         : "Курсив",\r
+Underline                      : "Подчеркнутый",\r
+StrikeThrough          : "Зачеркнутый",\r
+Subscript                      : "Подстрочный индекс",\r
+Superscript                    : "Надстрочный индекс",\r
+LeftJustify                    : "По левому краю",\r
+CenterJustify          : "По центру",\r
+RightJustify           : "По правому краю",\r
+BlockJustify           : "По ширине",\r
+DecreaseIndent         : "Уменьшить отступ",\r
+IncreaseIndent         : "Увеличить отступ",\r
+Undo                           : "Отменить",\r
+Redo                           : "Повторить",\r
+NumberedListLbl                : "Нумерованный список",\r
+NumberedList           : "Вставить/Удалить нумерованный список",\r
+BulletedListLbl                : "Маркированный список",\r
+BulletedList           : "Вставить/Удалить маркированный список",\r
+ShowTableBorders       : "Показать бордюры таблицы",\r
+ShowDetails                    : "Показать детали",\r
+Style                          : "Стиль",\r
+FontFormat                     : "Форматирование",\r
+Font                           : "Шрифт",\r
+FontSize                       : "Размер",\r
+TextColor                      : "Цвет текста",\r
+BGColor                                : "Цвет фона",\r
+Source                         : "Источник",\r
+Find                           : "Найти",\r
+Replace                                : "Заменить",\r
+SpellCheck                     : "Проверить орфографию",\r
+UniversalKeyboard      : "Универсальная клавиатура",\r
+PageBreakLbl           : "Разрыв страницы",\r
+PageBreak                      : "Вставить разрыв страницы",\r
+\r
+Form                   : "Форма",\r
+Checkbox               : "Флаговая кнопка",\r
+RadioButton            : "Кнопка выбора",\r
+TextField              : "Текстовое поле",\r
+Textarea               : "Текстовая область",\r
+HiddenField            : "Скрытое поле",\r
+Button                 : "Кнопка",\r
+SelectionField : "Список",\r
+ImageButton            : "Кнопка с изображением",\r
+\r
+FitWindow              : "Развернуть окно редактора",\r
+\r
+// Context Menu\r
+EditLink                       : "Вставить ссылку",\r
+CellCM                         : "Ячейка",\r
+RowCM                          : "Строка",\r
+ColumnCM                       : "Колонка",\r
+InsertRow                      : "Вставить строку",\r
+DeleteRows                     : "Удалить строки",\r
+InsertColumn           : "Вставить колонку",\r
+DeleteColumns          : "Удалить колонки",\r
+InsertCell                     : "Вставить ячейку",\r
+DeleteCells                    : "Удалить ячейки",\r
+MergeCells                     : "Соединить ячейки",\r
+SplitCell                      : "Разбить ячейку",\r
+TableDelete                    : "Удалить таблицу",\r
+CellProperties         : "Свойства ячейки",\r
+TableProperties                : "Свойства таблицы",\r
+ImageProperties                : "Свойства изображения",\r
+FlashProperties                : "Свойства Flash",\r
+\r
+AnchorProp                     : "Свойства якоря",\r
+ButtonProp                     : "Свойства кнопки",\r
+CheckboxProp           : "Свойства флаговой кнопки",\r
+HiddenFieldProp                : "Свойства скрытого поля",\r
+RadioButtonProp                : "Свойства кнопки выбора",\r
+ImageButtonProp                : "Свойства кнопки с изображением",\r
+TextFieldProp          : "Свойства текстового поля",\r
+SelectionFieldProp     : "Свойства списка",\r
+TextareaProp           : "Свойства текстовой области",\r
+FormProp                       : "Свойства формы",\r
+\r
+FontFormats                    : "Нормальный;Форматированный;Адрес;Заголовок 1;Заголовок 2;Заголовок 3;Заголовок 4;Заголовок 5;Заголовок 6;Нормальный (DIV)",            //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Обработка XHTML. Пожалуйста подождите...",\r
+Done                           : "Сделано",\r
+PasteWordConfirm       : "Текст, который вы хотите вставить, похож на копируемый из Word. Вы хотите очистить его перед вставкой?",\r
+NotCompatiblePaste     : "Эта команда доступна для Internet Explorer версии 5.5 или выше. Вы хотите вставить без очистки?",\r
+UnknownToolbarItem     : "Не известный элемент панели инструментов \"%1\"",\r
+UnknownCommand         : "Не известное имя команды \"%1\"",\r
+NotImplemented         : "Команда не реализована",\r
+UnknownToolbarSet      : "Панель инструментов \"%1\" не существует",\r
+NoActiveX                      : "Настройки безопасности вашего браузера могут ограничивать некоторые свойства редактора. Вы должны включить опцию \"Запускать элементы управления ActiveX и плугины\". Вы можете видеть ошибки и замечать отсутствие возможностей.",\r
+BrowseServerBlocked : "Ресурсы браузера не могут быть открыты. Проверьте что блокировки всплывающих окон выключены.",\r
+DialogBlocked          : "Не возможно открыть окно диалога. Проверьте что блокировки всплывающих окон выключены.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "ОК",\r
+DlgBtnCancel           : "Отмена",\r
+DlgBtnClose                    : "Закрыть",\r
+DlgBtnBrowseServer     : "Просмотреть на сервере",\r
+DlgAdvancedTag         : "Расширенный",\r
+DlgOpOther                     : "<Другое>",\r
+DlgInfoTab                     : "Информация",\r
+DlgAlertUrl                    : "Пожалуйста вставьте URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<не определено>",\r
+DlgGenId                       : "Идентификатор",\r
+DlgGenLangDir          : "Направление языка",\r
+DlgGenLangDirLtr       : "Слева на право (LTR)",\r
+DlgGenLangDirRtl       : "Справа на лево (RTL)",\r
+DlgGenLangCode         : "Язык",\r
+DlgGenAccessKey                : "Горячая клавиша",\r
+DlgGenName                     : "Имя",\r
+DlgGenTabIndex         : "Последовательность перехода",\r
+DlgGenLongDescr                : "Длинное описание URL",\r
+DlgGenClass                    : "Класс CSS",\r
+DlgGenTitle                    : "Заголовок",\r
+DlgGenContType         : "Тип содержимого",\r
+DlgGenLinkCharset      : "Кодировка",\r
+DlgGenStyle                    : "Стиль CSS",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Свойства изображения",\r
+DlgImgInfoTab          : "Информация о изображении",\r
+DlgImgBtnUpload                : "Послать на сервер",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Закачать",\r
+DlgImgAlt                      : "Альтернативный текст",\r
+DlgImgWidth                    : "Ширина",\r
+DlgImgHeight           : "Высота",\r
+DlgImgLockRatio                : "Сохранять пропорции",\r
+DlgBtnResetSize                : "Сбросить размер",\r
+DlgImgBorder           : "Бордюр",\r
+DlgImgHSpace           : "Горизонтальный отступ",\r
+DlgImgVSpace           : "Вертикальный отступ",\r
+DlgImgAlign                    : "Выравнивание",\r
+DlgImgAlignLeft                : "По левому краю",\r
+DlgImgAlignAbsBottom: "Абс понизу",\r
+DlgImgAlignAbsMiddle: "Абс посередине",\r
+DlgImgAlignBaseline    : "По базовой линии",\r
+DlgImgAlignBottom      : "Понизу",\r
+DlgImgAlignMiddle      : "Посередине",\r
+DlgImgAlignRight       : "По правому краю",\r
+DlgImgAlignTextTop     : "Текст наверху",\r
+DlgImgAlignTop         : "По верху",\r
+DlgImgPreview          : "Предварительный просмотр",\r
+DlgImgAlertUrl         : "Пожалуйста введите URL изображения",\r
+DlgImgLinkTab          : "Ссылка",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Свойства Flash",\r
+DlgFlashChkPlay                : "Авто проигрывание",\r
+DlgFlashChkLoop                : "Повтор",\r
+DlgFlashChkMenu                : "Включить меню Flash",\r
+DlgFlashScale          : "Масштабировать",\r
+DlgFlashScaleAll       : "Показывать все",\r
+DlgFlashScaleNoBorder  : "Без бордюра",\r
+DlgFlashScaleFit       : "Точное совпадение",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Ссылка",\r
+DlgLnkInfoTab          : "Информация ссылки",\r
+DlgLnkTargetTab                : "Цель",\r
+\r
+DlgLnkType                     : "Тип ссылки",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Якорь на эту страницу",\r
+DlgLnkTypeEMail                : "Эл. почта",\r
+DlgLnkProto                    : "Протокол",\r
+DlgLnkProtoOther       : "<другое>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Выберите якорь",\r
+DlgLnkAnchorByName     : "По имени якоря",\r
+DlgLnkAnchorById       : "По идентификатору элемента",\r
+DlgLnkNoAnchors                : "<Нет якорей доступных в этом документе>",            //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Адрес эл. почты",\r
+DlgLnkEMailSubject     : "Заголовок сообщения",\r
+DlgLnkEMailBody                : "Тело сообщения",\r
+DlgLnkUpload           : "Закачать",\r
+DlgLnkBtnUpload                : "Послать на сервер",\r
+\r
+DlgLnkTarget           : "Цель",\r
+DlgLnkTargetFrame      : "<фрейм>",\r
+DlgLnkTargetPopup      : "<всплывающее окно>",\r
+DlgLnkTargetBlank      : "Новое окно (_blank)",\r
+DlgLnkTargetParent     : "Родительское окно (_parent)",\r
+DlgLnkTargetSelf       : "Тоже окно (_self)",\r
+DlgLnkTargetTop                : "Самое верхнее окно (_top)",\r
+DlgLnkTargetFrameName  : "Имя целевого фрейма",\r
+DlgLnkPopWinName       : "Имя всплывающего окна",\r
+DlgLnkPopWinFeat       : "Свойства всплывающего окна",\r
+DlgLnkPopResize                : "Изменяющееся в размерах",\r
+DlgLnkPopLocation      : "Панель локации",\r
+DlgLnkPopMenu          : "Панель меню",\r
+DlgLnkPopScroll                : "Полосы прокрутки",\r
+DlgLnkPopStatus                : "Строка состояния",\r
+DlgLnkPopToolbar       : "Панель инструментов",\r
+DlgLnkPopFullScrn      : "Полный экран (IE)",\r
+DlgLnkPopDependent     : "Зависимый (Netscape)",\r
+DlgLnkPopWidth         : "Ширина",\r
+DlgLnkPopHeight                : "Высота",\r
+DlgLnkPopLeft          : "Позиция слева",\r
+DlgLnkPopTop           : "Позиция сверху",\r
+\r
+DlnLnkMsgNoUrl         : "Пожалуйста введите URL ссылки",\r
+DlnLnkMsgNoEMail       : "Пожалуйста введите адрес эл. почты",\r
+DlnLnkMsgNoAnchor      : "Пожалуйста выберете якорь",\r
+DlnLnkMsgInvPopName    : "Название вспывающего окна должно начинаться буквы и не может содержать пробелов",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Выберите цвет",\r
+DlgColorBtnClear       : "Очистить",\r
+DlgColorHighlight      : "Подсвеченный",\r
+DlgColorSelected       : "Выбранный",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Вставить смайлик",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Выберите специальный символ",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Свойства таблицы",\r
+DlgTableRows           : "Строки",\r
+DlgTableColumns                : "Колонки",\r
+DlgTableBorder         : "Размер бордюра",\r
+DlgTableAlign          : "Выравнивание",\r
+DlgTableAlignNotSet    : "<Не уст.>",\r
+DlgTableAlignLeft      : "Слева",\r
+DlgTableAlignCenter    : "По центру",\r
+DlgTableAlignRight     : "Справа",\r
+DlgTableWidth          : "Ширина",\r
+DlgTableWidthPx                : "пикселей",\r
+DlgTableWidthPc                : "процентов",\r
+DlgTableHeight         : "Высота",\r
+DlgTableCellSpace      : "Промежуток (spacing)",\r
+DlgTableCellPad                : "Отступ (padding)",\r
+DlgTableCaption                : "Заголовок",\r
+DlgTableSummary                : "Резюме",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Свойства ячейки",\r
+DlgCellWidth           : "Ширина",\r
+DlgCellWidthPx         : "пикселей",\r
+DlgCellWidthPc         : "процентов",\r
+DlgCellHeight          : "Высота",\r
+DlgCellWordWrap                : "Заворачивание текста",\r
+DlgCellWordWrapNotSet  : "<Не уст.>",\r
+DlgCellWordWrapYes     : "Да",\r
+DlgCellWordWrapNo      : "Нет",\r
+DlgCellHorAlign                : "Гор. выравнивание",\r
+DlgCellHorAlignNotSet  : "<Не уст.>",\r
+DlgCellHorAlignLeft    : "Слева",\r
+DlgCellHorAlignCenter  : "По центру",\r
+DlgCellHorAlignRight: "Справа",\r
+DlgCellVerAlign                : "Верт. выравнивание",\r
+DlgCellVerAlignNotSet  : "<Не уст.>",\r
+DlgCellVerAlignTop     : "Сверху",\r
+DlgCellVerAlignMiddle  : "Посередине",\r
+DlgCellVerAlignBottom  : "Снизу",\r
+DlgCellVerAlignBaseline        : "По базовой линии",\r
+DlgCellRowSpan         : "Диапазон строк (span)",\r
+DlgCellCollSpan                : "Диапазон колонок (span)",\r
+DlgCellBackColor       : "Цвет фона",\r
+DlgCellBorderColor     : "Цвет бордюра",\r
+DlgCellBtnSelect       : "Выберите...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Найти",\r
+DlgFindFindBtn         : "Найти",\r
+DlgFindNotFoundMsg     : "Указанный текст не найден.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Заменить",\r
+DlgReplaceFindLbl              : "Найти:",\r
+DlgReplaceReplaceLbl   : "Заменить на:",\r
+DlgReplaceCaseChk              : "Учитывать регистр",\r
+DlgReplaceReplaceBtn   : "Заменить",\r
+DlgReplaceReplAllBtn   : "Заменить все",\r
+DlgReplaceWordChk              : "Совпадение целых слов",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Настройки безопасности вашего браузера не позволяют редактору автоматически выполнять операции вырезания. Пожалуйста используйте клавиатуру для этого (Ctrl+X).",\r
+PasteErrorCopy : "Настройки безопасности вашего браузера не позволяют редактору автоматически выполнять операции копирования. Пожалуйста используйте клавиатуру для этого (Ctrl+C).",\r
+\r
+PasteAsText            : "Вставить только текст",\r
+PasteFromWord  : "Вставить из Word",\r
+\r
+DlgPasteMsg2   : "Пожалуйста вставьте текст в прямоугольник используя сочетание клавиш (<STRONG>Ctrl+V</STRONG>) и нажмите <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Игнорировать определения гарнитуры",\r
+DlgPasteRemoveStyles   : "Убрать определения стилей",\r
+DlgPasteCleanBox               : "Очистить",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Автоматический",\r
+ColorMoreColors        : "Цвета...",\r
+\r
+// Document Properties\r
+DocProps               : "Свойства документа",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Свойства якоря",\r
+DlgAnchorName          : "Имя якоря",\r
+DlgAnchorErrorName     : "Пожалуйста введите имя якоря",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Нет в словаре",\r
+DlgSpellChangeTo               : "Заменить на",\r
+DlgSpellBtnIgnore              : "Игнорировать",\r
+DlgSpellBtnIgnoreAll   : "Игнорировать все",\r
+DlgSpellBtnReplace             : "Заменить",\r
+DlgSpellBtnReplaceAll  : "Заменить все",\r
+DlgSpellBtnUndo                        : "Отменить",\r
+DlgSpellNoSuggestions  : "- Нет предположений -",\r
+DlgSpellProgress               : "Идет проверка орфографии...",\r
+DlgSpellNoMispell              : "Проверка орфографии закончена: ошибок не найдено",\r
+DlgSpellNoChanges              : "Проверка орфографии закончена: ни одного слова не изменено",\r
+DlgSpellOneChange              : "Проверка орфографии закончена: одно слово изменено",\r
+DlgSpellManyChanges            : "Проверка орфографии закончена: 1% слов изменен",\r
+\r
+IeSpellDownload                        : "Модуль проверки орфографии не установлен. Хотите скачать его сейчас?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Текст (Значение)",\r
+DlgButtonType          : "Тип",\r
+DlgButtonTypeBtn       : "Кнопка",\r
+DlgButtonTypeSbm       : "Отправить",\r
+DlgButtonTypeRst       : "Сбросить",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Имя",\r
+DlgCheckboxValue       : "Значение",\r
+DlgCheckboxSelected    : "Выбранная",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Имя",\r
+DlgFormAction  : "Действие",\r
+DlgFormMethod  : "Метод",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Имя",\r
+DlgSelectValue         : "Значение",\r
+DlgSelectSize          : "Размер",\r
+DlgSelectLines         : "линии",\r
+DlgSelectChkMulti      : "Разрешить множественный выбор",\r
+DlgSelectOpAvail       : "Доступные варианты",\r
+DlgSelectOpText                : "Текст",\r
+DlgSelectOpValue       : "Значение",\r
+DlgSelectBtnAdd                : "Добавить",\r
+DlgSelectBtnModify     : "Модифицировать",\r
+DlgSelectBtnUp         : "Вверх",\r
+DlgSelectBtnDown       : "Вниз",\r
+DlgSelectBtnSetValue : "Установить как выбранное значение",\r
+DlgSelectBtnDelete     : "Удалить",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Имя",\r
+DlgTextareaCols        : "Колонки",\r
+DlgTextareaRows        : "Строки",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Имя",\r
+DlgTextValue           : "Значение",\r
+DlgTextCharWidth       : "Ширина",\r
+DlgTextMaxChars                : "Макс. кол-во символов",\r
+DlgTextType                    : "Тип",\r
+DlgTextTypeText                : "Текст",\r
+DlgTextTypePass                : "Пароль",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Имя",\r
+DlgHiddenValue : "Значение",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Свойства маркированного списка",\r
+NumberedListProp       : "Свойства нумерованного списка",\r
+DlgLstStart                    : "Начало",\r
+DlgLstType                     : "Тип",\r
+DlgLstTypeCircle       : "Круг",\r
+DlgLstTypeDisc         : "Диск",\r
+DlgLstTypeSquare       : "Квадрат",\r
+DlgLstTypeNumbers      : "Номера (1, 2, 3)",\r
+DlgLstTypeLCase                : "Буквы нижнего регистра (a, b, c)",\r
+DlgLstTypeUCase                : "Буквы верхнего регистра (A, B, C)",\r
+DlgLstTypeSRoman       : "Малые римские буквы (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Большие римские буквы (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Общие",\r
+DlgDocBackTab          : "Задний фон",\r
+DlgDocColorsTab                : "Цвета и отступы",\r
+DlgDocMetaTab          : "Мета данные",\r
+\r
+DlgDocPageTitle                : "Заголовок страницы",\r
+DlgDocLangDir          : "Направление текста",\r
+DlgDocLangDirLTR       : "Слева на право (LTR)",\r
+DlgDocLangDirRTL       : "Справа на лево (RTL)",\r
+DlgDocLangCode         : "Код языка",\r
+DlgDocCharSet          : "Кодировка набора символов",\r
+DlgDocCharSetCE                : "Центрально-европейская",\r
+DlgDocCharSetCT                : "Китайская традиционная (Big5)",\r
+DlgDocCharSetCR                : "Кириллица",\r
+DlgDocCharSetGR                : "Греческая",\r
+DlgDocCharSetJP                : "Японская",\r
+DlgDocCharSetKR                : "Корейская",\r
+DlgDocCharSetTR                : "Турецкая",\r
+DlgDocCharSetUN                : "Юникод (UTF-8)",\r
+DlgDocCharSetWE                : "Западно-европейская",\r
+DlgDocCharSetOther     : "Другая кодировка набора символов",\r
+\r
+DlgDocDocType          : "Заголовок типа документа",\r
+DlgDocDocTypeOther     : "Другой заголовок типа документа",\r
+DlgDocIncXHTML         : "Включить XHTML объявления",\r
+DlgDocBgColor          : "Цвет фона",\r
+DlgDocBgImage          : "URL изображения фона",\r
+DlgDocBgNoScroll       : "Нескроллируемый фон",\r
+DlgDocCText                    : "Текст",\r
+DlgDocCLink                    : "Ссылка",\r
+DlgDocCVisited         : "Посещенная ссылка",\r
+DlgDocCActive          : "Активная ссылка",\r
+DlgDocMargins          : "Отступы страницы",\r
+DlgDocMaTop                    : "Верхний",\r
+DlgDocMaLeft           : "Левый",\r
+DlgDocMaRight          : "Правый",\r
+DlgDocMaBottom         : "Нижний",\r
+DlgDocMeIndex          : "Ключевые слова документа (разделенные запятой)",\r
+DlgDocMeDescr          : "Описание документа",\r
+DlgDocMeAuthor         : "Автор",\r
+DlgDocMeCopy           : "Авторские права",\r
+DlgDocPreview          : "Предварительный просмотр",\r
+\r
+// Templates Dialog\r
+Templates                      : "Шаблоны",\r
+DlgTemplatesTitle      : "Шаблоны содержимого",\r
+DlgTemplatesSelMsg     : "Пожалуйста выберете шаблон для открытия в редакторе<br>(текущее содержимое будет потеряно):",\r
+DlgTemplatesLoading    : "Загрузка списка шаблонов. Пожалуйста подождите...",\r
+DlgTemplatesNoTpl      : "(Ни одного шаблона не определено)",\r
+DlgTemplatesReplace    : "Заменить текущее содержание",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "О программе",\r
+DlgAboutBrowserInfoTab : "Информация браузера",\r
+DlgAboutLicenseTab     : "Лицензия",\r
+DlgAboutVersion                : "Версия",\r
+DlgAboutInfo           : "Для большей информации, посетите"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/sk.js b/httemplate/elements/fckeditor/editor/lang/sk.js
new file mode 100644 (file)
index 0000000..83d00ba
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Slovak language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Skryť panel nástrojov",\r
+ToolbarExpand          : "Zobraziť panel nástrojov",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Uložit",\r
+NewPage                                : "Nová stránka",\r
+Preview                                : "Náhľad",\r
+Cut                                    : "Vystrihnúť",\r
+Copy                           : "Kopírovať",\r
+Paste                          : "Vložiť",\r
+PasteText                      : "Vložiť ako čistý text",\r
+PasteWord                      : "Vložiť z Wordu",\r
+Print                          : "Tlač",\r
+SelectAll                      : "Vybrať všetko",\r
+RemoveFormat           : "Odstrániť formátovanie",\r
+InsertLinkLbl          : "Odkaz",\r
+InsertLink                     : "Vložiť/zmeniť odkaz",\r
+RemoveLink                     : "Odstrániť odkaz",\r
+Anchor                         : "Vložiť/zmeniť kotvu",\r
+InsertImageLbl         : "Obrázok",\r
+InsertImage                    : "Vložiť/zmeniť obrázok",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Vložiť/zmeniť Flash",\r
+InsertTableLbl         : "Tabuľka",\r
+InsertTable                    : "Vložiť/zmeniť tabuľku",\r
+InsertLineLbl          : "Čiara",\r
+InsertLine                     : "Vložiť vodorovnú čiaru",\r
+InsertSpecialCharLbl: "Špeciálne znaky",\r
+InsertSpecialChar      : "Vložiť špeciálne znaky",\r
+InsertSmileyLbl                : "Smajlíky",\r
+InsertSmiley           : "Vložiť smajlíka",\r
+About                          : "O aplikáci FCKeditor",\r
+Bold                           : "Tučné",\r
+Italic                         : "Kurzíva",\r
+Underline                      : "Podčiarknuté",\r
+StrikeThrough          : "Prečiarknuté",\r
+Subscript                      : "Dolný index",\r
+Superscript                    : "Horný index",\r
+LeftJustify                    : "Zarovnať vľavo",\r
+CenterJustify          : "Zarovnať na stred",\r
+RightJustify           : "Zarovnať vpravo",\r
+BlockJustify           : "Zarovnať do bloku",\r
+DecreaseIndent         : "Zmenšiť odsadenie",\r
+IncreaseIndent         : "Zväčšiť odsadenie",\r
+Undo                           : "Späť",\r
+Redo                           : "Znovu",\r
+NumberedListLbl                : "Číslovanie",\r
+NumberedList           : "Vložiť/odstrániť číslovaný zoznam",\r
+BulletedListLbl                : "Odrážky",\r
+BulletedList           : "Vložiť/odstraniť odrážky",\r
+ShowTableBorders       : "Zobraziť okraje tabuliek",\r
+ShowDetails                    : "Zobraziť podrobnosti",\r
+Style                          : "Štýl",\r
+FontFormat                     : "Formát",\r
+Font                           : "Písmo",\r
+FontSize                       : "Veľkosť",\r
+TextColor                      : "Farba textu",\r
+BGColor                                : "Farba pozadia",\r
+Source                         : "Zdroj",\r
+Find                           : "Hľadať",\r
+Replace                                : "Nahradiť",\r
+SpellCheck                     : "Kontrola pravopisu",\r
+UniversalKeyboard      : "Univerzálna klávesnica",\r
+PageBreakLbl           : "Oddeľovač stránky",\r
+PageBreak                      : "Vložiť oddeľovač stránky",\r
+\r
+Form                   : "Formulár",\r
+Checkbox               : "Zaškrtávacie políčko",\r
+RadioButton            : "Prepínač",\r
+TextField              : "Textové pole",\r
+Textarea               : "Textová oblasť",\r
+HiddenField            : "Skryté pole",\r
+Button                 : "Tlačíidlo",\r
+SelectionField : "Rozbaľovací zoznam",\r
+ImageButton            : "Obrázkové tlačidlo",\r
+\r
+FitWindow              : "Maximalizovať veľkosť okna editora",\r
+\r
+// Context Menu\r
+EditLink                       : "Zmeniť odkaz",\r
+CellCM                         : "Bunka",\r
+RowCM                          : "Riadok",\r
+ColumnCM                       : "Stĺpec",\r
+InsertRow                      : "Vložiť riadok",\r
+DeleteRows                     : "Vymazať riadok",\r
+InsertColumn           : "Vložiť stĺpec",\r
+DeleteColumns          : "Zmazať stĺpec",\r
+InsertCell                     : "Vložiť bunku",\r
+DeleteCells                    : "Vymazať bunky",\r
+MergeCells                     : "Zlúčiť bunky",\r
+SplitCell                      : "Rozdeliť bunku",\r
+TableDelete                    : "Vymazať tabuľku",\r
+CellProperties         : "Vlastnosti bunky",\r
+TableProperties                : "Vlastnosti tabuľky",\r
+ImageProperties                : "Vlastnosti obrázku",\r
+FlashProperties                : "Vlastnosti Flashu",\r
+\r
+AnchorProp                     : "Vlastnosti kotvy",\r
+ButtonProp                     : "Vlastnosti tlačidla",\r
+CheckboxProp           : "Vlastnosti zaškrtávacieho políčka",\r
+HiddenFieldProp                : "Vlastnosti skrytého poľa",\r
+RadioButtonProp                : "Vlastnosti prepínača",\r
+ImageButtonProp                : "Vlastnosti obrázkového tlačidla",\r
+TextFieldProp          : "Vlastnosti textového poľa",\r
+SelectionFieldProp     : "Vlastnosti rozbaľovacieho zoznamu",\r
+TextareaProp           : "Vlastnosti textovej oblasti",\r
+FormProp                       : "Vlastnosti formulára",\r
+\r
+FontFormats                    : "Normálny;Formátovaný;Adresa;Nadpis 1;Nadpis 2;Nadpis 3;Nadpis 4;Nadpis 5;Nadpis 6;Odsek (DIV)",           //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Prebieha spracovanie XHTML. Čakajte prosím...",\r
+Done                           : "Dokončené.",\r
+PasteWordConfirm       : "Vyzerá to tak, že vkladaný text je kopírovaný z Wordu. Chcete ho pred vložením vyčistiť?",\r
+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?",\r
+UnknownToolbarItem     : "Neznáma položka panela nástrojov \"%1\"",\r
+UnknownCommand         : "Neznámy príkaz \"%1\"",\r
+NotImplemented         : "Príkaz nie je implementovaný",\r
+UnknownToolbarSet      : "Panel nástrojov \"%1\" neexistuje",\r
+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í.",\r
+BrowseServerBlocked : "Prehliadač zdrojových prvkov nebolo možné otvoriť. Uistite sa, že máte vypnuté všetky blokovače vyskakujúcich okien.",\r
+DialogBlocked          : "Dialógové okno nebolo možné otvoriť. Uistite sa, že máte vypnuté všetky blokovače vyskakujúcich okien.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Zrušiť",\r
+DlgBtnClose                    : "Zavrieť",\r
+DlgBtnBrowseServer     : "Prechádzať server",\r
+DlgAdvancedTag         : "Rozšírené",\r
+DlgOpOther                     : "<Ďalšie>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Prosím vložte URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nenastavené>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Orientácia jazyka",\r
+DlgGenLangDirLtr       : "Zľava doprava (LTR)",\r
+DlgGenLangDirRtl       : "Sprava doľava (RTL)",\r
+DlgGenLangCode         : "Kód jazyka",\r
+DlgGenAccessKey                : "Prístupový kľúč",\r
+DlgGenName                     : "Meno",\r
+DlgGenTabIndex         : "Poradie prvku",\r
+DlgGenLongDescr                : "Dlhý popis URL",\r
+DlgGenClass                    : "Trieda štýlu",\r
+DlgGenTitle                    : "Pomocný titulok",\r
+DlgGenContType         : "Pomocný typ obsahu",\r
+DlgGenLinkCharset      : "Priradená znaková sada",\r
+DlgGenStyle                    : "Štýl",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Vlastnosti obrázku",\r
+DlgImgInfoTab          : "Informácie o obrázku",\r
+DlgImgBtnUpload                : "Odoslať na server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Odoslať",\r
+DlgImgAlt                      : "Alternatívny text",\r
+DlgImgWidth                    : "Šírka",\r
+DlgImgHeight           : "Výška",\r
+DlgImgLockRatio                : "Zámok",\r
+DlgBtnResetSize                : "Pôvodná veľkosť",\r
+DlgImgBorder           : "Okraje",\r
+DlgImgHSpace           : "H-medzera",\r
+DlgImgVSpace           : "V-medzera",\r
+DlgImgAlign                    : "Zarovnanie",\r
+DlgImgAlignLeft                : "Vľavo",\r
+DlgImgAlignAbsBottom: "Úplne dole",\r
+DlgImgAlignAbsMiddle: "Do stredu",\r
+DlgImgAlignBaseline    : "Na základňu",\r
+DlgImgAlignBottom      : "Dole",\r
+DlgImgAlignMiddle      : "Na stred",\r
+DlgImgAlignRight       : "Vpravo",\r
+DlgImgAlignTextTop     : "Na horný okraj textu",\r
+DlgImgAlignTop         : "Nahor",\r
+DlgImgPreview          : "Náhľad",\r
+DlgImgAlertUrl         : "Zadajte prosím URL obrázku",\r
+DlgImgLinkTab          : "Odkaz",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Vlastnosti Flashu",\r
+DlgFlashChkPlay                : "Automatické prehrávanie",\r
+DlgFlashChkLoop                : "Opakovanie",\r
+DlgFlashChkMenu                : "Povoliť Flash Menu",\r
+DlgFlashScale          : "Mierka",\r
+DlgFlashScaleAll       : "Zobraziť mierku",\r
+DlgFlashScaleNoBorder  : "Bez okrajov",\r
+DlgFlashScaleFit       : "Roztiahnuť na celé",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Odkaz",\r
+DlgLnkInfoTab          : "Informácie o odkaze",\r
+DlgLnkTargetTab                : "Cieľ",\r
+\r
+DlgLnkType                     : "Typ odkazu",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Kotva v tejto stránke",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<iný>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Vybrať kotvu",\r
+DlgLnkAnchorByName     : "Podľa mena kotvy",\r
+DlgLnkAnchorById       : "Podľa Id objektu",\r
+DlgLnkNoAnchors                : "<V stránke nie je definovaná žiadna kotva>",              //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mailová adresa",\r
+DlgLnkEMailSubject     : "Predmet správy",\r
+DlgLnkEMailBody                : "Telo správy",\r
+DlgLnkUpload           : "Odoslať",\r
+DlgLnkBtnUpload                : "Odoslať na server",\r
+\r
+DlgLnkTarget           : "Cieľ",\r
+DlgLnkTargetFrame      : "<rámec>",\r
+DlgLnkTargetPopup      : "<vyskakovacie okno>",\r
+DlgLnkTargetBlank      : "Nové okno (_blank)",\r
+DlgLnkTargetParent     : "Rodičovské okno (_parent)",\r
+DlgLnkTargetSelf       : "Rovnaké okno (_self)",\r
+DlgLnkTargetTop                : "Hlavné okno (_top)",\r
+DlgLnkTargetFrameName  : "Meno rámu cieľa",\r
+DlgLnkPopWinName       : "Názov vyskakovacieho okna",\r
+DlgLnkPopWinFeat       : "Vlastnosti vyskakovacieho okna",\r
+DlgLnkPopResize                : "Meniteľná veľkosť",\r
+DlgLnkPopLocation      : "Panel umiestnenia",\r
+DlgLnkPopMenu          : "Panel ponuky",\r
+DlgLnkPopScroll                : "Posuvníky",\r
+DlgLnkPopStatus                : "Stavový riadok",\r
+DlgLnkPopToolbar       : "Panel nástrojov",\r
+DlgLnkPopFullScrn      : "Celá obrazovka (IE)",\r
+DlgLnkPopDependent     : "Závislosť (Netscape)",\r
+DlgLnkPopWidth         : "Šírka",\r
+DlgLnkPopHeight                : "Výška",\r
+DlgLnkPopLeft          : "Ľavý okraj",\r
+DlgLnkPopTop           : "Horný okraj",\r
+\r
+DlnLnkMsgNoUrl         : "Zadajte prosím URL odkazu",\r
+DlnLnkMsgNoEMail       : "Zadajte prosím e-mailovú adresu",\r
+DlnLnkMsgNoAnchor      : "Vyberte prosím kotvu",\r
+DlnLnkMsgInvPopName    : "Názov vyskakovacieho okna sa musá začínať písmenom a nemôže obsahovať medzery",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Výber farby",\r
+DlgColorBtnClear       : "Vymazať",\r
+DlgColorHighlight      : "Zvýraznená",\r
+DlgColorSelected       : "Vybraná",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Vkladanie smajlíkov",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Výber špeciálneho znaku",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Vlastnosti tabuľky",\r
+DlgTableRows           : "Riadky",\r
+DlgTableColumns                : "Stĺpce",\r
+DlgTableBorder         : "Ohraničenie",\r
+DlgTableAlign          : "Zarovnanie",\r
+DlgTableAlignNotSet    : "<nenastavené>",\r
+DlgTableAlignLeft      : "Vľavo",\r
+DlgTableAlignCenter    : "Na stred",\r
+DlgTableAlignRight     : "Vpravo",\r
+DlgTableWidth          : "Šírka",\r
+DlgTableWidthPx                : "pixelov",\r
+DlgTableWidthPc                : "percent",\r
+DlgTableHeight         : "Výška",\r
+DlgTableCellSpace      : "Vzdialenosť buniek",\r
+DlgTableCellPad                : "Odsadenie obsahu",\r
+DlgTableCaption                : "Popis",\r
+DlgTableSummary                : "Prehľad",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Vlastnosti bunky",\r
+DlgCellWidth           : "Šírka",\r
+DlgCellWidthPx         : "bodov",\r
+DlgCellWidthPc         : "percent",\r
+DlgCellHeight          : "Výška",\r
+DlgCellWordWrap                : "Zalamovannie",\r
+DlgCellWordWrapNotSet  : "<nenastavené>",\r
+DlgCellWordWrapYes     : "Áno",\r
+DlgCellWordWrapNo      : "Nie",\r
+DlgCellHorAlign                : "Vodorovné zarovnanie",\r
+DlgCellHorAlignNotSet  : "<nenastavené>",\r
+DlgCellHorAlignLeft    : "Vľavo",\r
+DlgCellHorAlignCenter  : "Na stred",\r
+DlgCellHorAlignRight: "Vpravo",\r
+DlgCellVerAlign                : "Zvislé zarovnanie",\r
+DlgCellVerAlignNotSet  : "<nenastavené>",\r
+DlgCellVerAlignTop     : "Nahor",\r
+DlgCellVerAlignMiddle  : "Doprostred",\r
+DlgCellVerAlignBottom  : "Dole",\r
+DlgCellVerAlignBaseline        : "Na základňu",\r
+DlgCellRowSpan         : "Zlúčené riadky",\r
+DlgCellCollSpan                : "Zlúčené stĺpce",\r
+DlgCellBackColor       : "Farba pozadia",\r
+DlgCellBorderColor     : "Farba ohraničenia",\r
+DlgCellBtnSelect       : "Výber...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Hľadať",\r
+DlgFindFindBtn         : "Hľadať",\r
+DlgFindNotFoundMsg     : "Hľadaný text nebol nájdený.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Nahradiť",\r
+DlgReplaceFindLbl              : "Čo hľadať:",\r
+DlgReplaceReplaceLbl   : "Čím nahradiť:",\r
+DlgReplaceCaseChk              : "Rozlišovať malé/veľké písmená",\r
+DlgReplaceReplaceBtn   : "Nahradiť",\r
+DlgReplaceReplAllBtn   : "Nahradiť všetko",\r
+DlgReplaceWordChk              : "Len celé slová",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Bezpečnostné nastavenie 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).",\r
+PasteErrorCopy : "Bezpečnostné nastavenie 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).",\r
+\r
+PasteAsText            : "Vložiť ako čistý text",\r
+PasteFromWord  : "Vložiť text z Wordu",\r
+\r
+DlgPasteMsg2   : "Prosím vložte nasledovný rámček použitím klávesnice (<STRONG>Ctrl+V</STRONG>) a stlačte <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignorovať nastavenia typu písma",\r
+DlgPasteRemoveStyles   : "Odstrániť formátovanie",\r
+DlgPasteCleanBox               : "Vyčistiť schránku",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automaticky",\r
+ColorMoreColors        : "Viac farieb...",\r
+\r
+// Document Properties\r
+DocProps               : "Vlastnosti dokumentu",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Vlastnosti kotvy",\r
+DlgAnchorName          : "Meno kotvy",\r
+DlgAnchorErrorName     : "Zadajte prosím meno kotvy",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Nie je v slovníku",\r
+DlgSpellChangeTo               : "Zmeniť na",\r
+DlgSpellBtnIgnore              : "Ignorovať",\r
+DlgSpellBtnIgnoreAll   : "Ignorovať všetko",\r
+DlgSpellBtnReplace             : "Prepísat",\r
+DlgSpellBtnReplaceAll  : "Prepísat všetko",\r
+DlgSpellBtnUndo                        : "Späť",\r
+DlgSpellNoSuggestions  : "- Žiadny návrh -",\r
+DlgSpellProgress               : "Prebieha kontrola pravopisu...",\r
+DlgSpellNoMispell              : "Kontrola pravopisu dokončená: bez chýb",\r
+DlgSpellNoChanges              : "Kontrola pravopisu dokončená: žiadne slová nezmenené",\r
+DlgSpellOneChange              : "Kontrola pravopisu dokončená: zmenené jedno slovo",\r
+DlgSpellManyChanges            : "Kontrola pravopisu dokončená: zmenených %1 slov",\r
+\r
+IeSpellDownload                        : "Kontrola pravopisu nie je naištalovaná. Chcete ju hneď stiahnuť?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text",\r
+DlgButtonType          : "Typ",\r
+DlgButtonTypeBtn       : "Tlačidlo",\r
+DlgButtonTypeSbm       : "Odoslať",\r
+DlgButtonTypeRst       : "Vymazať",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Názov",\r
+DlgCheckboxValue       : "Hodnota",\r
+DlgCheckboxSelected    : "Vybrané",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Názov",\r
+DlgFormAction  : "Akcie",\r
+DlgFormMethod  : "Metóda",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Názov",\r
+DlgSelectValue         : "Hodnota",\r
+DlgSelectSize          : "Veľkosť",\r
+DlgSelectLines         : "riadkov",\r
+DlgSelectChkMulti      : "Povoliť viacnásobný výber",\r
+DlgSelectOpAvail       : "Dostupné možnosti",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Hodnota",\r
+DlgSelectBtnAdd                : "Pridať",\r
+DlgSelectBtnModify     : "Zmeniť",\r
+DlgSelectBtnUp         : "Hore",\r
+DlgSelectBtnDown       : "Dole",\r
+DlgSelectBtnSetValue : "Nastaviť ako vybranú hodnotu",\r
+DlgSelectBtnDelete     : "Zmazať",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Názov",\r
+DlgTextareaCols        : "Stĺpce",\r
+DlgTextareaRows        : "Riadky",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Názov",\r
+DlgTextValue           : "Hodnota",\r
+DlgTextCharWidth       : "Šírka pola (znakov)",\r
+DlgTextMaxChars                : "Maximálny počet znakov",\r
+DlgTextType                    : "Typ",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Heslo",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Názov",\r
+DlgHiddenValue : "Hodnota",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Vlastnosti odrážok",\r
+NumberedListProp       : "Vlastnosti číslovania",\r
+DlgLstStart                    : "Štart",\r
+DlgLstType                     : "Typ",\r
+DlgLstTypeCircle       : "Krúžok",\r
+DlgLstTypeDisc         : "Disk",\r
+DlgLstTypeSquare       : "Štvorec",\r
+DlgLstTypeNumbers      : "Číslovanie (1, 2, 3)",\r
+DlgLstTypeLCase                : "Malé písmená (a, b, c)",\r
+DlgLstTypeUCase                : "Veľké písmená (A, B, C)",\r
+DlgLstTypeSRoman       : "Malé rímske číslice (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Veľké rímske číslice (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Všeobecné",\r
+DlgDocBackTab          : "Pozadie",\r
+DlgDocColorsTab                : "Farby a okraje",\r
+DlgDocMetaTab          : "Meta Data",\r
+\r
+DlgDocPageTitle                : "Titulok",\r
+DlgDocLangDir          : "Orientácie jazyka",\r
+DlgDocLangDirLTR       : "Zľava doprava (LTR)",\r
+DlgDocLangDirRTL       : "Sprava doľava (RTL)",\r
+DlgDocLangCode         : "Kód jazyka",\r
+DlgDocCharSet          : "Kódová stránka",\r
+DlgDocCharSetCE                : "Stredoeurópske",\r
+DlgDocCharSetCT                : "Čínština tradičná (Big5)",\r
+DlgDocCharSetCR                : "Cyrillika",\r
+DlgDocCharSetGR                : "Gréčtina",\r
+DlgDocCharSetJP                : "Japončina",\r
+DlgDocCharSetKR                : "Korejčina",\r
+DlgDocCharSetTR                : "Turečtina",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Západná európa",\r
+DlgDocCharSetOther     : "Iná kódová stránka",\r
+\r
+DlgDocDocType          : "Typ záhlavia dokumentu",\r
+DlgDocDocTypeOther     : "Iný typ záhlavia dokumentu",\r
+DlgDocIncXHTML         : "Obsahuje deklarácie XHTML",\r
+DlgDocBgColor          : "Farba pozadia",\r
+DlgDocBgImage          : "URL adresa obrázku na pozadí",\r
+DlgDocBgNoScroll       : "Fixné pozadie",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Odkaz",\r
+DlgDocCVisited         : "Navštívený odkaz",\r
+DlgDocCActive          : "Aktívny odkaz",\r
+DlgDocMargins          : "Okraje stránky",\r
+DlgDocMaTop                    : "Horný",\r
+DlgDocMaLeft           : "Ľavý",\r
+DlgDocMaRight          : "Pravý",\r
+DlgDocMaBottom         : "Dolný",\r
+DlgDocMeIndex          : "Kľúčové slová pre indexovanie (oddelené čiarkou)",\r
+DlgDocMeDescr          : "Popis stránky",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Autorské práva",\r
+DlgDocPreview          : "Náhľad",\r
+\r
+// Templates Dialog\r
+Templates                      : "Šablóny",\r
+DlgTemplatesTitle      : "Šablóny obsahu",\r
+DlgTemplatesSelMsg     : "Prosím vyberte šablóny na otvorenie v editore<br>(súšasný obsah bude stratený):",\r
+DlgTemplatesLoading    : "Nahrávam zoznam šablón. Čakajte prosím...",\r
+DlgTemplatesNoTpl      : "(žiadne šablóny nenájdené)",\r
+DlgTemplatesReplace    : "Nahradiť aktuálny obsah",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "O aplikáci",\r
+DlgAboutBrowserInfoTab : "Informácie o prehliadači",\r
+DlgAboutLicenseTab     : "Licencia",\r
+DlgAboutVersion                : "verzia",\r
+DlgAboutInfo           : "Viac informácií získate na"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/sl.js b/httemplate/elements/fckeditor/editor/lang/sl.js
new file mode 100644 (file)
index 0000000..95cde15
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Slovenian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Zloži orodno vrstico",\r
+ToolbarExpand          : "Razširi orodno vrstico",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Shrani",\r
+NewPage                                : "Nova stran",\r
+Preview                                : "Predogled",\r
+Cut                                    : "Izreži",\r
+Copy                           : "Kopiraj",\r
+Paste                          : "Prilepi",\r
+PasteText                      : "Prilepi kot golo besedilo",\r
+PasteWord                      : "Prilepi iz Worda",\r
+Print                          : "Natisni",\r
+SelectAll                      : "Izberi vse",\r
+RemoveFormat           : "Odstrani oblikovanje",\r
+InsertLinkLbl          : "Povezava",\r
+InsertLink                     : "Vstavi/uredi povezavo",\r
+RemoveLink                     : "Odstrani povezavo",\r
+Anchor                         : "Vstavi/uredi zaznamek",\r
+InsertImageLbl         : "Slika",\r
+InsertImage                    : "Vstavi/uredi sliko",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Vstavi/Uredi Flash",\r
+InsertTableLbl         : "Tabela",\r
+InsertTable                    : "Vstavi/uredi tabelo",\r
+InsertLineLbl          : "Črta",\r
+InsertLine                     : "Vstavi vodoravno črto",\r
+InsertSpecialCharLbl: "Posebni znak",\r
+InsertSpecialChar      : "Vstavi posebni znak",\r
+InsertSmileyLbl                : "Smeško",\r
+InsertSmiley           : "Vstavi smeška",\r
+About                          : "O FCKeditorju",\r
+Bold                           : "Krepko",\r
+Italic                         : "Ležeče",\r
+Underline                      : "Podčrtano",\r
+StrikeThrough          : "Prečrtano",\r
+Subscript                      : "Podpisano",\r
+Superscript                    : "Nadpisano",\r
+LeftJustify                    : "Leva poravnava",\r
+CenterJustify          : "Sredinska poravnava",\r
+RightJustify           : "Desna poravnava",\r
+BlockJustify           : "Obojestranska poravnava",\r
+DecreaseIndent         : "Zmanjšaj zamik",\r
+IncreaseIndent         : "Povečaj zamik",\r
+Undo                           : "Razveljavi",\r
+Redo                           : "Ponovi",\r
+NumberedListLbl                : "Oštevilčen seznam",\r
+NumberedList           : "Vstavi/odstrani oštevilčevanje",\r
+BulletedListLbl                : "Označen seznam",\r
+BulletedList           : "Vstavi/odstrani označevanje",\r
+ShowTableBorders       : "Pokaži meje tabele",\r
+ShowDetails                    : "Pokaži podrobnosti",\r
+Style                          : "Slog",\r
+FontFormat                     : "Oblika",\r
+Font                           : "Pisava",\r
+FontSize                       : "Velikost",\r
+TextColor                      : "Barva besedila",\r
+BGColor                                : "Barva ozadja",\r
+Source                         : "Izvorna koda",\r
+Find                           : "Najdi",\r
+Replace                                : "Zamenjaj",\r
+SpellCheck                     : "Preveri črkovanje",\r
+UniversalKeyboard      : "Večjezična tipkovnica",\r
+PageBreakLbl           : "Prelom strani",\r
+PageBreak                      : "Vstavi prelom strani",\r
+\r
+Form                   : "Obrazec",\r
+Checkbox               : "Potrditveno polje",\r
+RadioButton            : "Izbirno polje",\r
+TextField              : "Vnosno polje",\r
+Textarea               : "Vnosno območje",\r
+HiddenField            : "Skrito polje",\r
+Button                 : "Gumb",\r
+SelectionField : "Spustni seznam",\r
+ImageButton            : "Gumb s sliko",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Uredi povezavo",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Vstavi vrstico",\r
+DeleteRows                     : "Izbriši vrstice",\r
+InsertColumn           : "Vstavi stolpec",\r
+DeleteColumns          : "Izbriši stolpce",\r
+InsertCell                     : "Vstavi celico",\r
+DeleteCells                    : "Izbriši celice",\r
+MergeCells                     : "Združi celice",\r
+SplitCell                      : "Razdeli celico",\r
+TableDelete                    : "Izbriši tabelo",\r
+CellProperties         : "Lastnosti celice",\r
+TableProperties                : "Lastnosti tabele",\r
+ImageProperties                : "Lastnosti slike",\r
+FlashProperties                : "Lastnosti Flash",\r
+\r
+AnchorProp                     : "Lastnosti zaznamka",\r
+ButtonProp                     : "Lastnosti gumba",\r
+CheckboxProp           : "Lastnosti potrditvenega polja",\r
+HiddenFieldProp                : "Lastnosti skritega polja",\r
+RadioButtonProp                : "Lastnosti izbirnega polja",\r
+ImageButtonProp                : "Lastnosti gumba s sliko",\r
+TextFieldProp          : "Lastnosti vnosnega polja",\r
+SelectionFieldProp     : "Lastnosti spustnega seznama",\r
+TextareaProp           : "Lastnosti vnosnega območja",\r
+FormProp                       : "Lastnosti obrazca",\r
+\r
+FontFormats                    : "Navaden;Oblikovan;Napis;Naslov 1;Naslov 2;Naslov 3;Naslov 4;Naslov 5;Naslov 6",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Obdelujem XHTML. Prosim počakajte...",\r
+Done                           : "Narejeno",\r
+PasteWordConfirm       : "Izgleda, da želite prilepiti besedilo iz Worda. Ali ga želite očistiti, preden ga prilepite?",\r
+NotCompatiblePaste     : "Ta ukaz deluje le v Internet Explorerje različice 5.5 ali višje. Ali želite prilepiti brez čiščenja?",\r
+UnknownToolbarItem     : "Neznan element orodne vrstice \"%1\"",\r
+UnknownCommand         : "Neznano ime ukaza \"%1\"",\r
+NotImplemented         : "Ukaz ni izdelan",\r
+UnknownToolbarSet      : "Skupina orodnih vrstic \"%1\" ne obstoja",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "V redu",\r
+DlgBtnCancel           : "Prekliči",\r
+DlgBtnClose                    : "Zapri",\r
+DlgBtnBrowseServer     : "Prebrskaj na strežniku",\r
+DlgAdvancedTag         : "Napredno",\r
+DlgOpOther                     : "<Ostalo>",\r
+DlgInfoTab                     : "Podatki",\r
+DlgAlertUrl                    : "Prosim vpiši spletni naslov",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<ni postavljen>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Smer jezika",\r
+DlgGenLangDirLtr       : "Od leve proti desni (LTR)",\r
+DlgGenLangDirRtl       : "Od desne proti levi (RTL)",\r
+DlgGenLangCode         : "Oznaka jezika",\r
+DlgGenAccessKey                : "Vstopno geslo",\r
+DlgGenName                     : "Ime",\r
+DlgGenTabIndex         : "Številka tabulatorja",\r
+DlgGenLongDescr                : "Dolg opis URL-ja",\r
+DlgGenClass                    : "Razred stilne predloge",\r
+DlgGenTitle                    : "Predlagani naslov",\r
+DlgGenContType         : "Predlagani tip vsebine (content-type)",\r
+DlgGenLinkCharset      : "Kodna tabela povezanega vira",\r
+DlgGenStyle                    : "Slog",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Lastnosti slike",\r
+DlgImgInfoTab          : "Podatki o sliki",\r
+DlgImgBtnUpload                : "Pošlji na strežnik",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Pošlji",\r
+DlgImgAlt                      : "Nadomestno besedilo",\r
+DlgImgWidth                    : "Širina",\r
+DlgImgHeight           : "Višina",\r
+DlgImgLockRatio                : "Zakleni razmerje",\r
+DlgBtnResetSize                : "Ponastavi velikost",\r
+DlgImgBorder           : "Obroba",\r
+DlgImgHSpace           : "Vodoravni razmik",\r
+DlgImgVSpace           : "Navpični razmik",\r
+DlgImgAlign                    : "Poravnava",\r
+DlgImgAlignLeft                : "Levo",\r
+DlgImgAlignAbsBottom: "Popolnoma na dno",\r
+DlgImgAlignAbsMiddle: "Popolnoma v sredino",\r
+DlgImgAlignBaseline    : "Na osnovno črto",\r
+DlgImgAlignBottom      : "Na dno",\r
+DlgImgAlignMiddle      : "V sredino",\r
+DlgImgAlignRight       : "Desno",\r
+DlgImgAlignTextTop     : "Besedilo na vrh",\r
+DlgImgAlignTop         : "Na vrh",\r
+DlgImgPreview          : "Predogled",\r
+DlgImgAlertUrl         : "Vnesite URL slike",\r
+DlgImgLinkTab          : "Povezava",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Lastnosti Flash",\r
+DlgFlashChkPlay                : "Samodejno predvajaj",\r
+DlgFlashChkLoop                : "Ponavljanje",\r
+DlgFlashChkMenu                : "Omogoči Flash Meni",\r
+DlgFlashScale          : "Povečava",\r
+DlgFlashScaleAll       : "Pokaži vse",\r
+DlgFlashScaleNoBorder  : "Brez obrobe",\r
+DlgFlashScaleFit       : "Natančno prileganje",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Povezava",\r
+DlgLnkInfoTab          : "Podatki o povezavi",\r
+DlgLnkTargetTab                : "Cilj",\r
+\r
+DlgLnkType                     : "Vrsta povezave",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Zaznamek na tej strani",\r
+DlgLnkTypeEMail                : "Elektronski naslov",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<drugo>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Izberi zaznamek",\r
+DlgLnkAnchorByName     : "Po imenu zaznamka",\r
+DlgLnkAnchorById       : "Po ID-ju elementa",\r
+DlgLnkNoAnchors                : "<V tem dokumentu ni zaznamkov>",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Elektronski naslov",\r
+DlgLnkEMailSubject     : "Predmet sporočila",\r
+DlgLnkEMailBody                : "Vsebina sporočila",\r
+DlgLnkUpload           : "Prenesi",\r
+DlgLnkBtnUpload                : "Pošlji na strežnik",\r
+\r
+DlgLnkTarget           : "Cilj",\r
+DlgLnkTargetFrame      : "<okvir>",\r
+DlgLnkTargetPopup      : "<pojavno okno>",\r
+DlgLnkTargetBlank      : "Novo okno (_blank)",\r
+DlgLnkTargetParent     : "Starševsko okno (_parent)",\r
+DlgLnkTargetSelf       : "Isto okno (_self)",\r
+DlgLnkTargetTop                : "Najvišje okno (_top)",\r
+DlgLnkTargetFrameName  : "Ime ciljnega okvirja",\r
+DlgLnkPopWinName       : "Ime pojavnega okna",\r
+DlgLnkPopWinFeat       : "Značilnosti pojavnega okna",\r
+DlgLnkPopResize                : "Spremenljive velikosti",\r
+DlgLnkPopLocation      : "Naslovna vrstica",\r
+DlgLnkPopMenu          : "Menijska vrstica",\r
+DlgLnkPopScroll                : "Drsniki",\r
+DlgLnkPopStatus                : "Vrstica stanja",\r
+DlgLnkPopToolbar       : "Orodna vrstica",\r
+DlgLnkPopFullScrn      : "Celozaslonska slika (IE)",\r
+DlgLnkPopDependent     : "Podokno (Netscape)",\r
+DlgLnkPopWidth         : "Širina",\r
+DlgLnkPopHeight                : "Višina",\r
+DlgLnkPopLeft          : "Lega levo",\r
+DlgLnkPopTop           : "Lega na vrhu",\r
+\r
+DlnLnkMsgNoUrl         : "Vnesite URL povezave",\r
+DlnLnkMsgNoEMail       : "Vnesite elektronski naslov",\r
+DlnLnkMsgNoAnchor      : "Izberite zaznamek",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Izberite barvo",\r
+DlgColorBtnClear       : "Počisti",\r
+DlgColorHighlight      : "Označi",\r
+DlgColorSelected       : "Izbrano",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Vstavi smeška",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Izberi posebni znak",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Lastnosti tabele",\r
+DlgTableRows           : "Vrstice",\r
+DlgTableColumns                : "Stolpci",\r
+DlgTableBorder         : "Velikost obrobe",\r
+DlgTableAlign          : "Poravnava",\r
+DlgTableAlignNotSet    : "<Ni nastavljeno>",\r
+DlgTableAlignLeft      : "Levo",\r
+DlgTableAlignCenter    : "Sredinsko",\r
+DlgTableAlignRight     : "Desno",\r
+DlgTableWidth          : "Širina",\r
+DlgTableWidthPx                : "pik",\r
+DlgTableWidthPc                : "procentov",\r
+DlgTableHeight         : "Višina",\r
+DlgTableCellSpace      : "Razmik med celicami",\r
+DlgTableCellPad                : "Polnilo med celicami",\r
+DlgTableCaption                : "Naslov",\r
+DlgTableSummary                : "Povzetek",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Lastnosti celice",\r
+DlgCellWidth           : "Širina",\r
+DlgCellWidthPx         : "pik",\r
+DlgCellWidthPc         : "procentov",\r
+DlgCellHeight          : "Višina",\r
+DlgCellWordWrap                : "Pomikanje besedila",\r
+DlgCellWordWrapNotSet  : "<Ni nastavljeno>",\r
+DlgCellWordWrapYes     : "Da",\r
+DlgCellWordWrapNo      : "Ne",\r
+DlgCellHorAlign                : "Vodoravna poravnava",\r
+DlgCellHorAlignNotSet  : "<Ni nastavljeno>",\r
+DlgCellHorAlignLeft    : "Levo",\r
+DlgCellHorAlignCenter  : "Sredinsko",\r
+DlgCellHorAlignRight: "Desno",\r
+DlgCellVerAlign                : "Navpična poravnava",\r
+DlgCellVerAlignNotSet  : "<Ni nastavljeno>",\r
+DlgCellVerAlignTop     : "Na vrh",\r
+DlgCellVerAlignMiddle  : "V sredino",\r
+DlgCellVerAlignBottom  : "Na dno",\r
+DlgCellVerAlignBaseline        : "Na osnovno črto",\r
+DlgCellRowSpan         : "Spojenih vrstic (row-span)",\r
+DlgCellCollSpan                : "Spojenih stolpcev (col-span)",\r
+DlgCellBackColor       : "Barva ozadja",\r
+DlgCellBorderColor     : "Barva obrobe",\r
+DlgCellBtnSelect       : "Izberi...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Najdi",\r
+DlgFindFindBtn         : "Najdi",\r
+DlgFindNotFoundMsg     : "Navedeno besedilo ni bilo najdeno.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Zamenjaj",\r
+DlgReplaceFindLbl              : "Najdi:",\r
+DlgReplaceReplaceLbl   : "Zamenjaj z:",\r
+DlgReplaceCaseChk              : "Razlikuj velike in male črke",\r
+DlgReplaceReplaceBtn   : "Zamenjaj",\r
+DlgReplaceReplAllBtn   : "Zamenjaj vse",\r
+DlgReplaceWordChk              : "Samo cele besede",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Varnostne nastavitve brskalnika ne dopuščajo samodejnega izrezovanja. Uporabite kombinacijo tipk na tipkovnici (Ctrl+X).",\r
+PasteErrorCopy : "Varnostne nastavitve brskalnika ne dopuščajo samodejnega kopiranja. Uporabite kombinacijo tipk na tipkovnici (Ctrl+C).",\r
+\r
+PasteAsText            : "Prilepi kot golo besedilo",\r
+PasteFromWord  : "Prilepi iz Worda",\r
+\r
+DlgPasteMsg2   : "Prosim prilepite v sleči okvir s pomočjo tipkovnice (<STRONG>Ctrl+V</STRONG>) in pritisnite <STRONG>V redu</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Prezri obliko pisave",\r
+DlgPasteRemoveStyles   : "Odstrani nastavitve stila",\r
+DlgPasteCleanBox               : "Počisti okvir",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Samodejno",\r
+ColorMoreColors        : "Več barv...",\r
+\r
+// Document Properties\r
+DocProps               : "Lastnosti dokumenta",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Lastnosti zaznamka",\r
+DlgAnchorName          : "Ime zaznamka",\r
+DlgAnchorErrorName     : "Prosim vnesite ime zaznamka",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Ni v slovarju",\r
+DlgSpellChangeTo               : "Spremeni v",\r
+DlgSpellBtnIgnore              : "Prezri",\r
+DlgSpellBtnIgnoreAll   : "Prezri vse",\r
+DlgSpellBtnReplace             : "Zamenjaj",\r
+DlgSpellBtnReplaceAll  : "Zamenjaj vse",\r
+DlgSpellBtnUndo                        : "Razveljavi",\r
+DlgSpellNoSuggestions  : "- Ni predlogov -",\r
+DlgSpellProgress               : "Preverjanje črkovanja se izvaja...",\r
+DlgSpellNoMispell              : "Črkovanje je končano: Brez napak",\r
+DlgSpellNoChanges              : "Črkovanje je končano: Nobena beseda ni bila spremenjena",\r
+DlgSpellOneChange              : "Črkovanje je končano: Spremenjena je bila ena beseda",\r
+DlgSpellManyChanges            : "Črkovanje je končano: Spremenjenih je bilo %1 besed",\r
+\r
+IeSpellDownload                        : "Črkovalnik ni nameščen. Ali ga želite prenesti sedaj?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Besedilo (Vrednost)",\r
+DlgButtonType          : "Tip",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Ime",\r
+DlgCheckboxValue       : "Vrednost",\r
+DlgCheckboxSelected    : "Izbrano",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Ime",\r
+DlgFormAction  : "Akcija",\r
+DlgFormMethod  : "Metoda",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Ime",\r
+DlgSelectValue         : "Vrednost",\r
+DlgSelectSize          : "Velikost",\r
+DlgSelectLines         : "vrstic",\r
+DlgSelectChkMulti      : "Dovoli izbor večih vrstic",\r
+DlgSelectOpAvail       : "Razpoložljive izbire",\r
+DlgSelectOpText                : "Besedilo",\r
+DlgSelectOpValue       : "Vrednost",\r
+DlgSelectBtnAdd                : "Dodaj",\r
+DlgSelectBtnModify     : "Spremeni",\r
+DlgSelectBtnUp         : "Gor",\r
+DlgSelectBtnDown       : "Dol",\r
+DlgSelectBtnSetValue : "Postavi kot privzeto izbiro",\r
+DlgSelectBtnDelete     : "Izbriši",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Ime",\r
+DlgTextareaCols        : "Stolpcev",\r
+DlgTextareaRows        : "Vrstic",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Ime",\r
+DlgTextValue           : "Vrednost",\r
+DlgTextCharWidth       : "Dolžina",\r
+DlgTextMaxChars                : "Največje število znakov",\r
+DlgTextType                    : "Tip",\r
+DlgTextTypeText                : "Besedilo",\r
+DlgTextTypePass                : "Geslo",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Ime",\r
+DlgHiddenValue : "Vrednost",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Lastnosti označenega seznama",\r
+NumberedListProp       : "Lastnosti oštevilčenega seznama",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Tip",\r
+DlgLstTypeCircle       : "Pikica",\r
+DlgLstTypeDisc         : "Kroglica",\r
+DlgLstTypeSquare       : "Kvadratek",\r
+DlgLstTypeNumbers      : "Številke (1, 2, 3)",\r
+DlgLstTypeLCase                : "Male črke (a, b, c)",\r
+DlgLstTypeUCase                : "Velike črke (A, B, C)",\r
+DlgLstTypeSRoman       : "Male rimske številke (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Velike rimske številke (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Splošno",\r
+DlgDocBackTab          : "Ozadje",\r
+DlgDocColorsTab                : "Barve in zamiki",\r
+DlgDocMetaTab          : "Meta podatki",\r
+\r
+DlgDocPageTitle                : "Naslov strani",\r
+DlgDocLangDir          : "Smer jezika",\r
+DlgDocLangDirLTR       : "Od leve proti desni (LTR)",\r
+DlgDocLangDirRTL       : "Od desne proti levi (RTL)",\r
+DlgDocLangCode         : "Oznaka jezika",\r
+DlgDocCharSet          : "Kodna tabela",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Druga kodna tabela",\r
+\r
+DlgDocDocType          : "Glava tipa dokumenta",\r
+DlgDocDocTypeOther     : "Druga glava tipa dokumenta",\r
+DlgDocIncXHTML         : "Vstavi XHTML deklaracije",\r
+DlgDocBgColor          : "Barva ozadja",\r
+DlgDocBgImage          : "URL slike za ozadje",\r
+DlgDocBgNoScroll       : "Nepremično ozadje",\r
+DlgDocCText                    : "Besedilo",\r
+DlgDocCLink                    : "Povezava",\r
+DlgDocCVisited         : "Obiskana povezava",\r
+DlgDocCActive          : "Aktivna povezava",\r
+DlgDocMargins          : "Zamiki strani",\r
+DlgDocMaTop                    : "Na vrhu",\r
+DlgDocMaLeft           : "Levo",\r
+DlgDocMaRight          : "Desno",\r
+DlgDocMaBottom         : "Spodaj",\r
+DlgDocMeIndex          : "Ključne besede (ločene z vejicami)",\r
+DlgDocMeDescr          : "Opis strani",\r
+DlgDocMeAuthor         : "Avtor",\r
+DlgDocMeCopy           : "Avtorske pravice",\r
+DlgDocPreview          : "Predogled",\r
+\r
+// Templates Dialog\r
+Templates                      : "Predloge",\r
+DlgTemplatesTitle      : "Vsebinske predloge",\r
+DlgTemplatesSelMsg     : "Izberite predlogo, ki jo želite odpreti v urejevalniku<br>(trenutna vsebina bo izgubljena):",\r
+DlgTemplatesLoading    : "Nalagam seznam predlog. Prosim počakajte...",\r
+DlgTemplatesNoTpl      : "(Ni pripravljenih predlog)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Vizitka",\r
+DlgAboutBrowserInfoTab : "Informacije o brskalniku",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "različica",\r
+DlgAboutInfo           : "Za več informacij obiščite"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/sr-latn.js b/httemplate/elements/fckeditor/editor/lang/sr-latn.js
new file mode 100644 (file)
index 0000000..5fa8154
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Serbian (Latin) language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Smanji liniju sa alatkama",\r
+ToolbarExpand          : "Proiri liniju sa alatkama",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Sačuvaj",\r
+NewPage                                : "Nova stranica",\r
+Preview                                : "Izgled stranice",\r
+Cut                                    : "Iseci",\r
+Copy                           : "Kopiraj",\r
+Paste                          : "Zalepi",\r
+PasteText                      : "Zalepi kao neformatiran tekst",\r
+PasteWord                      : "Zalepi iz Worda",\r
+Print                          : "Štampa",\r
+SelectAll                      : "Označi sve",\r
+RemoveFormat           : "Ukloni formatiranje",\r
+InsertLinkLbl          : "Link",\r
+InsertLink                     : "Unesi/izmeni link",\r
+RemoveLink                     : "Ukloni link",\r
+Anchor                         : "Unesi/izmeni sidro",\r
+InsertImageLbl         : "Slika",\r
+InsertImage                    : "Unesi/izmeni sliku",\r
+InsertFlashLbl         : "Fleš",\r
+InsertFlash                    : "Unesi/izmeni fleš",\r
+InsertTableLbl         : "Tabela",\r
+InsertTable                    : "Unesi/izmeni tabelu",\r
+InsertLineLbl          : "Linija",\r
+InsertLine                     : "Unesi horizontalnu liniju",\r
+InsertSpecialCharLbl: "Specijalni karakteri",\r
+InsertSpecialChar      : "Unesi specijalni karakter",\r
+InsertSmileyLbl                : "Smajli",\r
+InsertSmiley           : "Unesi smajlija",\r
+About                          : "O FCKeditoru",\r
+Bold                           : "Podebljano",\r
+Italic                         : "Kurziv",\r
+Underline                      : "Podvučeno",\r
+StrikeThrough          : "Precrtano",\r
+Subscript                      : "Indeks",\r
+Superscript                    : "Stepen",\r
+LeftJustify                    : "Levo ravnanje",\r
+CenterJustify          : "Centriran tekst",\r
+RightJustify           : "Desno ravnanje",\r
+BlockJustify           : "Obostrano ravnanje",\r
+DecreaseIndent         : "Smanji levu marginu",\r
+IncreaseIndent         : "Uvećaj levu marginu",\r
+Undo                           : "Poni�ti akciju",\r
+Redo                           : "Ponovi akciju",\r
+NumberedListLbl                : "Nabrojiva lista",\r
+NumberedList           : "Unesi/ukloni nabrojivu listu",\r
+BulletedListLbl                : "Nenabrojiva lista",\r
+BulletedList           : "Unesi/ukloni nenabrojivu listu",\r
+ShowTableBorders       : "Prikaži okvir tabele",\r
+ShowDetails                    : "Prikaži detalje",\r
+Style                          : "Stil",\r
+FontFormat                     : "Format",\r
+Font                           : "Font",\r
+FontSize                       : "Veličina fonta",\r
+TextColor                      : "Boja teksta",\r
+BGColor                                : "Boja pozadine",\r
+Source                         : "Kôd",\r
+Find                           : "Pretraga",\r
+Replace                                : "Zamena",\r
+SpellCheck                     : "Proveri spelovanje",\r
+UniversalKeyboard      : "Univerzalna tastatura",\r
+PageBreakLbl           : "Page Break", //MISSING\r
+PageBreak                      : "Insert Page Break",  //MISSING\r
+\r
+Form                   : "Forma",\r
+Checkbox               : "Polje za potvrdu",\r
+RadioButton            : "Radio-dugme",\r
+TextField              : "Tekstualno polje",\r
+Textarea               : "Zona teksta",\r
+HiddenField            : "Skriveno polje",\r
+Button                 : "Dugme",\r
+SelectionField : "Izborno polje",\r
+ImageButton            : "Dugme sa slikom",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Izmeni link",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Unesi red",\r
+DeleteRows                     : "Obriši redove",\r
+InsertColumn           : "Unesi kolonu",\r
+DeleteColumns          : "Obriši kolone",\r
+InsertCell                     : "Unesi ćelije",\r
+DeleteCells                    : "Obriši ćelije",\r
+MergeCells                     : "Spoj celije",\r
+SplitCell                      : "Razdvoji celije",\r
+TableDelete                    : "Delete Table",       //MISSING\r
+CellProperties         : "Osobine celije",\r
+TableProperties                : "Osobine tabele",\r
+ImageProperties                : "Osobine slike",\r
+FlashProperties                : "Osobine fleša",\r
+\r
+AnchorProp                     : "Osobine sidra",\r
+ButtonProp                     : "Osobine dugmeta",\r
+CheckboxProp           : "Osobine polja za potvrdu",\r
+HiddenFieldProp                : "Osobine skrivenog polja",\r
+RadioButtonProp                : "Osobine radio-dugmeta",\r
+ImageButtonProp                : "Osobine dugmeta sa slikom",\r
+TextFieldProp          : "Osobine tekstualnog polja",\r
+SelectionFieldProp     : "Osobine izbornog polja",\r
+TextareaProp           : "Osobine zone teksta",\r
+FormProp                       : "Osobine forme",\r
+\r
+FontFormats                    : "Normal;Formatirano;Adresa;Naslov 1;Naslov 2;Naslov 3;Naslov 4;Naslov 5;Naslov 6",            //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Obradujem XHTML. Malo strpljenja...",\r
+Done                           : "Završio",\r
+PasteWordConfirm       : "Tekst koji želite da nalepite kopiran je iz Worda. Da li želite da bude očišćen od formata pre lepljenja?",\r
+NotCompatiblePaste     : "Ova komanda je dostupna samo za Internet Explorer od verzije 5.5. Da li želite da nalepim tekst bez čišćenja?",\r
+UnknownToolbarItem     : "Nepoznata stavka toolbara \"%1\"",\r
+UnknownCommand         : "Nepoznata naredba \"%1\"",\r
+NotImplemented         : "Naredba nije implementirana",\r
+UnknownToolbarSet      : "Toolbar \"%1\" ne postoji",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Otkaži",\r
+DlgBtnClose                    : "Zatvori",\r
+DlgBtnBrowseServer     : "Pretraži server",\r
+DlgAdvancedTag         : "Napredni tagovi",\r
+DlgOpOther                     : "<Ostali>",\r
+DlgInfoTab                     : "Info",\r
+DlgAlertUrl                    : "Molimo Vas, unesite URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<nije postavljeno>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Smer jezika",\r
+DlgGenLangDirLtr       : "S leva na desno (LTR)",\r
+DlgGenLangDirRtl       : "S desna na levo (RTL)",\r
+DlgGenLangCode         : "Kôd jezika",\r
+DlgGenAccessKey                : "Pristupni taster",\r
+DlgGenName                     : "Naziv",\r
+DlgGenTabIndex         : "Tab indeks",\r
+DlgGenLongDescr                : "Pun opis URL",\r
+DlgGenClass                    : "Stylesheet klase",\r
+DlgGenTitle                    : "Advisory naslov",\r
+DlgGenContType         : "Advisory vrsta sadržaja",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Stil",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Osobine slika",\r
+DlgImgInfoTab          : "Info slike",\r
+DlgImgBtnUpload                : "Pošalji na server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Pošalji",\r
+DlgImgAlt                      : "Alternativni tekst",\r
+DlgImgWidth                    : "Širina",\r
+DlgImgHeight           : "Visina",\r
+DlgImgLockRatio                : "Zaključaj odnos",\r
+DlgBtnResetSize                : "Resetuj veličinu",\r
+DlgImgBorder           : "Okvir",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Ravnanje",\r
+DlgImgAlignLeft                : "Levo",\r
+DlgImgAlignAbsBottom: "Abs dole",\r
+DlgImgAlignAbsMiddle: "Abs sredina",\r
+DlgImgAlignBaseline    : "Bazno",\r
+DlgImgAlignBottom      : "Dole",\r
+DlgImgAlignMiddle      : "Sredina",\r
+DlgImgAlignRight       : "Desno",\r
+DlgImgAlignTextTop     : "Vrh teksta",\r
+DlgImgAlignTop         : "Vrh",\r
+DlgImgPreview          : "Izgled",\r
+DlgImgAlertUrl         : "Unesite URL slike",\r
+DlgImgLinkTab          : "Link",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Osobine fleša",\r
+DlgFlashChkPlay                : "Automatski start",\r
+DlgFlashChkLoop                : "Ponavljaj",\r
+DlgFlashChkMenu                : "Uključi fleš meni",\r
+DlgFlashScale          : "Skaliraj",\r
+DlgFlashScaleAll       : "Prikaži sve",\r
+DlgFlashScaleNoBorder  : "Bez ivice",\r
+DlgFlashScaleFit       : "Popuni površinu",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Link",\r
+DlgLnkInfoTab          : "Link Info",\r
+DlgLnkTargetTab                : "Meta",\r
+\r
+DlgLnkType                     : "Vrsta linka",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Sidro na ovoj stranici",\r
+DlgLnkTypeEMail                : "E-Mail",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<drugo>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Odaberi sidro",\r
+DlgLnkAnchorByName     : "Po nazivu sidra",\r
+DlgLnkAnchorById       : "Po Id-ju elementa",\r
+DlgLnkNoAnchors                : "<Nema dostupnih sidra>",             //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Mail adresa",\r
+DlgLnkEMailSubject     : "Naslov",\r
+DlgLnkEMailBody                : "Sadržaj poruke",\r
+DlgLnkUpload           : "Pošalji",\r
+DlgLnkBtnUpload                : "Pošalji na server",\r
+\r
+DlgLnkTarget           : "Meta",\r
+DlgLnkTargetFrame      : "<okvir>",\r
+DlgLnkTargetPopup      : "<popup prozor>",\r
+DlgLnkTargetBlank      : "Novi prozor (_blank)",\r
+DlgLnkTargetParent     : "Roditeljski prozor (_parent)",\r
+DlgLnkTargetSelf       : "Isti prozor (_self)",\r
+DlgLnkTargetTop                : "Prozor na vrhu (_top)",\r
+DlgLnkTargetFrameName  : "Naziv odredišnog frejma",\r
+DlgLnkPopWinName       : "Naziv popup prozora",\r
+DlgLnkPopWinFeat       : "Mogućnosti popup prozora",\r
+DlgLnkPopResize                : "Promenljiva velicina",\r
+DlgLnkPopLocation      : "Lokacija",\r
+DlgLnkPopMenu          : "Kontekstni meni",\r
+DlgLnkPopScroll                : "Scroll bar",\r
+DlgLnkPopStatus                : "Statusna linija",\r
+DlgLnkPopToolbar       : "Toolbar",\r
+DlgLnkPopFullScrn      : "Prikaz preko celog ekrana (IE)",\r
+DlgLnkPopDependent     : "Zavisno (Netscape)",\r
+DlgLnkPopWidth         : "Širina",\r
+DlgLnkPopHeight                : "Visina",\r
+DlgLnkPopLeft          : "Od leve ivice ekrana (px)",\r
+DlgLnkPopTop           : "Od vrha ekrana (px)",\r
+\r
+DlnLnkMsgNoUrl         : "Unesite URL linka",\r
+DlnLnkMsgNoEMail       : "Otkucajte adresu elektronske pote",\r
+DlnLnkMsgNoAnchor      : "Odaberite sidro",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Odaberite boju",\r
+DlgColorBtnClear       : "Obriši",\r
+DlgColorHighlight      : "Posvetli",\r
+DlgColorSelected       : "Odaberi",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Unesi smajlija",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Odaberite specijalni karakter",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Osobine tabele",\r
+DlgTableRows           : "Redova",\r
+DlgTableColumns                : "Kolona",\r
+DlgTableBorder         : "Veličina okvira",\r
+DlgTableAlign          : "Ravnanje",\r
+DlgTableAlignNotSet    : "<nije postavljeno>",\r
+DlgTableAlignLeft      : "Levo",\r
+DlgTableAlignCenter    : "Sredina",\r
+DlgTableAlignRight     : "Desno",\r
+DlgTableWidth          : "Širina",\r
+DlgTableWidthPx                : "piksela",\r
+DlgTableWidthPc                : "procenata",\r
+DlgTableHeight         : "Visina",\r
+DlgTableCellSpace      : "Ćelijski prostor",\r
+DlgTableCellPad                : "Razmak ćelija",\r
+DlgTableCaption                : "Naslov tabele",\r
+DlgTableSummary                : "Summary",    //MISSING\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Osobine ćelije",\r
+DlgCellWidth           : "Širina",\r
+DlgCellWidthPx         : "piksela",\r
+DlgCellWidthPc         : "procenata",\r
+DlgCellHeight          : "Visina",\r
+DlgCellWordWrap                : "Deljenje reči",\r
+DlgCellWordWrapNotSet  : "<nije postavljeno>",\r
+DlgCellWordWrapYes     : "Da",\r
+DlgCellWordWrapNo      : "Ne",\r
+DlgCellHorAlign                : "Vodoravno ravnanje",\r
+DlgCellHorAlignNotSet  : "<nije postavljeno>",\r
+DlgCellHorAlignLeft    : "Levo",\r
+DlgCellHorAlignCenter  : "Sredina",\r
+DlgCellHorAlignRight: "Desno",\r
+DlgCellVerAlign                : "Vertikalno ravnanje",\r
+DlgCellVerAlignNotSet  : "<nije postavljeno>",\r
+DlgCellVerAlignTop     : "Gornje",\r
+DlgCellVerAlignMiddle  : "Sredina",\r
+DlgCellVerAlignBottom  : "Donje",\r
+DlgCellVerAlignBaseline        : "Bazno",\r
+DlgCellRowSpan         : "Spajanje redova",\r
+DlgCellCollSpan                : "Spajanje kolona",\r
+DlgCellBackColor       : "Boja pozadine",\r
+DlgCellBorderColor     : "Boja okvira",\r
+DlgCellBtnSelect       : "Odaberi...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Pronađi",\r
+DlgFindFindBtn         : "Pronađi",\r
+DlgFindNotFoundMsg     : "Traženi tekst nije pronađen.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Zameni",\r
+DlgReplaceFindLbl              : "Pronadi:",\r
+DlgReplaceReplaceLbl   : "Zameni sa:",\r
+DlgReplaceCaseChk              : "Razlikuj mala i velika slova",\r
+DlgReplaceReplaceBtn   : "Zameni",\r
+DlgReplaceReplAllBtn   : "Zameni sve",\r
+DlgReplaceWordChk              : "Uporedi cele reci",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Zalepi kao čist tekst",\r
+PasteFromWord  : "Zalepi iz Worda",\r
+\r
+DlgPasteMsg2   : "Molimo Vas da zalepite unutar donje povrine koristeći tastaturnu prečicu (<STRONG>Ctrl+V</STRONG>) i da pritisnete <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Ignoriši definicije fontova",\r
+DlgPasteRemoveStyles   : "Ukloni definicije stilova",\r
+DlgPasteCleanBox               : "Obriši sve",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatski",\r
+ColorMoreColors        : "Više boja...",\r
+\r
+// Document Properties\r
+DocProps               : "Osobine dokumenta",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Osobine sidra",\r
+DlgAnchorName          : "Ime sidra",\r
+DlgAnchorErrorName     : "Unesite ime sidra",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Nije u rečniku",\r
+DlgSpellChangeTo               : "Izmeni",\r
+DlgSpellBtnIgnore              : "Ignoriši",\r
+DlgSpellBtnIgnoreAll   : "Ignoriši sve",\r
+DlgSpellBtnReplace             : "Zameni",\r
+DlgSpellBtnReplaceAll  : "Zameni sve",\r
+DlgSpellBtnUndo                        : "Vrati akciju",\r
+DlgSpellNoSuggestions  : "- Bez sugestija -",\r
+DlgSpellProgress               : "Provera spelovanja u toku...",\r
+DlgSpellNoMispell              : "Provera spelovanja završena: greške nisu pronadene",\r
+DlgSpellNoChanges              : "Provera spelovanja završena: Nije izmenjena nijedna rec",\r
+DlgSpellOneChange              : "Provera spelovanja završena: Izmenjena je jedna reč",\r
+DlgSpellManyChanges            : "Provera spelovanja završena: %1 reč(i) je izmenjeno",\r
+\r
+IeSpellDownload                        : "Provera spelovanja nije instalirana. Da li želite da je skinete sa Interneta?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Tekst (vrednost)",\r
+DlgButtonType          : "Tip",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Naziv",\r
+DlgCheckboxValue       : "Vrednost",\r
+DlgCheckboxSelected    : "Označeno",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Naziv",\r
+DlgFormAction  : "Akcija",\r
+DlgFormMethod  : "Metoda",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Naziv",\r
+DlgSelectValue         : "Vrednost",\r
+DlgSelectSize          : "Veličina",\r
+DlgSelectLines         : "linija",\r
+DlgSelectChkMulti      : "Dozvoli višestruku selekciju",\r
+DlgSelectOpAvail       : "Dostupne opcije",\r
+DlgSelectOpText                : "Tekst",\r
+DlgSelectOpValue       : "Vrednost",\r
+DlgSelectBtnAdd                : "Dodaj",\r
+DlgSelectBtnModify     : "Izmeni",\r
+DlgSelectBtnUp         : "Gore",\r
+DlgSelectBtnDown       : "Dole",\r
+DlgSelectBtnSetValue : "Podesi kao označenu vrednost",\r
+DlgSelectBtnDelete     : "Obriši",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Naziv",\r
+DlgTextareaCols        : "Broj kolona",\r
+DlgTextareaRows        : "Broj redova",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Naziv",\r
+DlgTextValue           : "Vrednost",\r
+DlgTextCharWidth       : "Širina (karaktera)",\r
+DlgTextMaxChars                : "Maksimalno karaktera",\r
+DlgTextType                    : "Tip",\r
+DlgTextTypeText                : "Tekst",\r
+DlgTextTypePass                : "Lozinka",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Naziv",\r
+DlgHiddenValue : "Vrednost",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Osobine nenabrojive liste",\r
+NumberedListProp       : "Osobine nabrojive liste",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Tip",\r
+DlgLstTypeCircle       : "Krug",\r
+DlgLstTypeDisc         : "Disc",       //MISSING\r
+DlgLstTypeSquare       : "Kvadrat",\r
+DlgLstTypeNumbers      : "Brojevi (1, 2, 3)",\r
+DlgLstTypeLCase                : "mala slova (a, b, c)",\r
+DlgLstTypeUCase                : "VELIKA slova (A, B, C)",\r
+DlgLstTypeSRoman       : "Male rimske cifre (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Velike rimske cifre (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Opšte osobine",\r
+DlgDocBackTab          : "Pozadina",\r
+DlgDocColorsTab                : "Boje i margine",\r
+DlgDocMetaTab          : "Metapodaci",\r
+\r
+DlgDocPageTitle                : "Naslov stranice",\r
+DlgDocLangDir          : "Smer jezika",\r
+DlgDocLangDirLTR       : "Sleva nadesno (LTR)",\r
+DlgDocLangDirRTL       : "Zdesna nalevo (RTL)",\r
+DlgDocLangCode         : "Šifra jezika",\r
+DlgDocCharSet          : "Kodiranje skupa karaktera",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Ostala kodiranja skupa karaktera",\r
+\r
+DlgDocDocType          : "Zaglavlje tipa dokumenta",\r
+DlgDocDocTypeOther     : "Ostala zaglavlja tipa dokumenta",\r
+DlgDocIncXHTML         : "Ukljuci XHTML deklaracije",\r
+DlgDocBgColor          : "Boja pozadine",\r
+DlgDocBgImage          : "URL pozadinske slike",\r
+DlgDocBgNoScroll       : "Fiksirana pozadina",\r
+DlgDocCText                    : "Tekst",\r
+DlgDocCLink                    : "Link",\r
+DlgDocCVisited         : "Posećeni link",\r
+DlgDocCActive          : "Aktivni link",\r
+DlgDocMargins          : "Margine stranice",\r
+DlgDocMaTop                    : "Gornja",\r
+DlgDocMaLeft           : "Leva",\r
+DlgDocMaRight          : "Desna",\r
+DlgDocMaBottom         : "Donja",\r
+DlgDocMeIndex          : "Ključne reci za indeksiranje dokumenta (razdvojene zarezima)",\r
+DlgDocMeDescr          : "Opis dokumenta",\r
+DlgDocMeAuthor         : "Autor",\r
+DlgDocMeCopy           : "Autorska prava",\r
+DlgDocPreview          : "Izgled stranice",\r
+\r
+// Templates Dialog\r
+Templates                      : "Obrasci",\r
+DlgTemplatesTitle      : "Obrasci za sadržaj",\r
+DlgTemplatesSelMsg     : "Molimo Vas da odaberete obrazac koji ce biti primenjen na stranicu (trenutni sadržaj ce biti obrisan):",\r
+DlgTemplatesLoading    : "Učitavam listu obrazaca. Malo strpljenja...",\r
+DlgTemplatesNoTpl      : "(Nema definisanih obrazaca)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "O editoru",\r
+DlgAboutBrowserInfoTab : "Informacije o pretraživacu",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "verzija",\r
+DlgAboutInfo           : "Za više informacija posetite"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/sr.js b/httemplate/elements/fckeditor/editor/lang/sr.js
new file mode 100644 (file)
index 0000000..e7aac23
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Serbian (Cyrillic) language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Смањи линију са алаткама",\r
+ToolbarExpand          : "Прошири линију са алаткама",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Сачувај",\r
+NewPage                                : "Нова страница",\r
+Preview                                : "Изглед странице",\r
+Cut                                    : "Исеци",\r
+Copy                           : "Копирај",\r
+Paste                          : "Залепи",\r
+PasteText                      : "Залепи као неформатиран текст",\r
+PasteWord                      : "Залепи из Worda",\r
+Print                          : "Штампа",\r
+SelectAll                      : "Означи све",\r
+RemoveFormat           : "Уклони форматирање",\r
+InsertLinkLbl          : "Линк",\r
+InsertLink                     : "Унеси/измени линк",\r
+RemoveLink                     : "Уклони линк",\r
+Anchor                         : "Унеси/измени сидро",\r
+InsertImageLbl         : "Слика",\r
+InsertImage                    : "Унеси/измени слику",\r
+InsertFlashLbl         : "Флеш елемент",\r
+InsertFlash                    : "Унеси/измени флеш",\r
+InsertTableLbl         : "Табела",\r
+InsertTable                    : "Унеси/измени табелу",\r
+InsertLineLbl          : "Линија",\r
+InsertLine                     : "Унеси хоризонталну линију",\r
+InsertSpecialCharLbl: "Специјални карактери",\r
+InsertSpecialChar      : "Унеси специјални карактер",\r
+InsertSmileyLbl                : "Смајли",\r
+InsertSmiley           : "Унеси смајлија",\r
+About                          : "О ФЦКедитору",\r
+Bold                           : "Подебљано",\r
+Italic                         : "Курзив",\r
+Underline                      : "Подвучено",\r
+StrikeThrough          : "Прецртано",\r
+Subscript                      : "Индекс",\r
+Superscript                    : "Степен",\r
+LeftJustify                    : "Лево равнање",\r
+CenterJustify          : "Центриран текст",\r
+RightJustify           : "Десно равнање",\r
+BlockJustify           : "Обострано равнање",\r
+DecreaseIndent         : "Смањи леву маргину",\r
+IncreaseIndent         : "Увећај леву маргину",\r
+Undo                           : "Поништи акцију",\r
+Redo                           : "Понови акцију",\r
+NumberedListLbl                : "Набројиву листу",\r
+NumberedList           : "Унеси/уклони набројиву листу",\r
+BulletedListLbl                : "Ненабројива листа",\r
+BulletedList           : "Унеси/уклони ненабројиву листу",\r
+ShowTableBorders       : "Прикажи оквир табеле",\r
+ShowDetails                    : "Прикажи детаље",\r
+Style                          : "Стил",\r
+FontFormat                     : "Формат",\r
+Font                           : "Фонт",\r
+FontSize                       : "Величина фонта",\r
+TextColor                      : "Боја текста",\r
+BGColor                                : "Боја позадине",\r
+Source                         : "Kôд",\r
+Find                           : "Претрага",\r
+Replace                                : "Замена",\r
+SpellCheck                     : "Провери спеловање",\r
+UniversalKeyboard      : "Универзална тастатура",\r
+PageBreakLbl           : "Page Break", //MISSING\r
+PageBreak                      : "Insert Page Break",  //MISSING\r
+\r
+Form                   : "Форма",\r
+Checkbox               : "Поље за потврду",\r
+RadioButton            : "Радио-дугме",\r
+TextField              : "Текстуално поље",\r
+Textarea               : "Зона текста",\r
+HiddenField            : "Скривено поље",\r
+Button                 : "Дугме",\r
+SelectionField : "Изборно поље",\r
+ImageButton            : "Дугме са сликом",\r
+\r
+FitWindow              : "Maximize the editor size",   //MISSING\r
+\r
+// Context Menu\r
+EditLink                       : "Промени линк",\r
+CellCM                         : "Cell",       //MISSING\r
+RowCM                          : "Row",        //MISSING\r
+ColumnCM                       : "Column",     //MISSING\r
+InsertRow                      : "Унеси ред",\r
+DeleteRows                     : "Обриши редове",\r
+InsertColumn           : "Унеси колону",\r
+DeleteColumns          : "Обриши колоне",\r
+InsertCell                     : "Унеси ћелије",\r
+DeleteCells                    : "Обриши ћелије",\r
+MergeCells                     : "Спој ћелије",\r
+SplitCell                      : "Раздвоји ћелије",\r
+TableDelete                    : "Delete Table",       //MISSING\r
+CellProperties         : "Особине ћелије",\r
+TableProperties                : "Особине табеле",\r
+ImageProperties                : "Особине слике",\r
+FlashProperties                : "Особине Флеша",\r
+\r
+AnchorProp                     : "Особине сидра",\r
+ButtonProp                     : "Особине дугмета",\r
+CheckboxProp           : "Особине поља за потврду",\r
+HiddenFieldProp                : "Особине скривеног поља",\r
+RadioButtonProp                : "Особине радио-дугмета",\r
+ImageButtonProp                : "Особине дугмета са сликом",\r
+TextFieldProp          : "Особине текстуалног поља",\r
+SelectionFieldProp     : "Особине изборног поља",\r
+TextareaProp           : "Особине зоне текста",\r
+FormProp                       : "Особине форме",\r
+\r
+FontFormats                    : "Normal;Formatirano;Adresa;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Обрађујем XHTML. Maлo стрпљења...",\r
+Done                           : "Завршио",\r
+PasteWordConfirm       : "Текст који желите да налепите копиран је из Worda. Да ли желите да буде очишћен од формата пре лепљења?",\r
+NotCompatiblePaste     : "Ова команда је доступна само за Интернет Екплорер од верзије 5.5. Да ли желите да налепим текст без чишћења?",\r
+UnknownToolbarItem     : "Непозната ставка toolbara \"%1\"",\r
+UnknownCommand         : "Непозната наредба \"%1\"",\r
+NotImplemented         : "Наредба није имплементирана",\r
+UnknownToolbarSet      : "Toolbar \"%1\" не постоји",\r
+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\r
+BrowseServerBlocked : "The resources browser could not be opened. Make sure that all popup blockers are disabled.",    //MISSING\r
+DialogBlocked          : "It was not possible to open the dialog window. Make sure all popup blockers are disabled.",  //MISSING\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Oткажи",\r
+DlgBtnClose                    : "Затвори",\r
+DlgBtnBrowseServer     : "Претражи сервер",\r
+DlgAdvancedTag         : "Напредни тагови",\r
+DlgOpOther                     : "<Остали>",\r
+DlgInfoTab                     : "Инфо",\r
+DlgAlertUrl                    : "Молимо Вас, унесите УРЛ",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<није постављено>",\r
+DlgGenId                       : "Ид",\r
+DlgGenLangDir          : "Смер језика",\r
+DlgGenLangDirLtr       : "С лева на десно (LTR)",\r
+DlgGenLangDirRtl       : "С десна на лево (RTL)",\r
+DlgGenLangCode         : "Kôд језика",\r
+DlgGenAccessKey                : "Приступни тастер",\r
+DlgGenName                     : "Назив",\r
+DlgGenTabIndex         : "Таб индекс",\r
+DlgGenLongDescr                : "Пун опис УРЛ",\r
+DlgGenClass                    : "Stylesheet класе",\r
+DlgGenTitle                    : "Advisory наслов",\r
+DlgGenContType         : "Advisory врста садржаја",\r
+DlgGenLinkCharset      : "Linked Resource Charset",\r
+DlgGenStyle                    : "Стил",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Особине слика",\r
+DlgImgInfoTab          : "Инфо слике",\r
+DlgImgBtnUpload                : "Пошаљи на сервер",\r
+DlgImgURL                      : "УРЛ",\r
+DlgImgUpload           : "Пошаљи",\r
+DlgImgAlt                      : "Алтернативни текст",\r
+DlgImgWidth                    : "Ширина",\r
+DlgImgHeight           : "Висина",\r
+DlgImgLockRatio                : "Закључај однос",\r
+DlgBtnResetSize                : "Ресетуј величину",\r
+DlgImgBorder           : "Оквир",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Равнање",\r
+DlgImgAlignLeft                : "Лево",\r
+DlgImgAlignAbsBottom: "Abs доле",\r
+DlgImgAlignAbsMiddle: "Abs средина",\r
+DlgImgAlignBaseline    : "Базно",\r
+DlgImgAlignBottom      : "Доле",\r
+DlgImgAlignMiddle      : "Средина",\r
+DlgImgAlignRight       : "Десно",\r
+DlgImgAlignTextTop     : "Врх текста",\r
+DlgImgAlignTop         : "Врх",\r
+DlgImgPreview          : "Изглед",\r
+DlgImgAlertUrl         : "Унесите УРЛ слике",\r
+DlgImgLinkTab          : "Линк",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Особине флеша",\r
+DlgFlashChkPlay                : "Аутоматски старт",\r
+DlgFlashChkLoop                : "Понављај",\r
+DlgFlashChkMenu                : "Укључи флеш мени",\r
+DlgFlashScale          : "Скалирај",\r
+DlgFlashScaleAll       : "Прикажи све",\r
+DlgFlashScaleNoBorder  : "Без ивице",\r
+DlgFlashScaleFit       : "Попуни површину",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Линк",\r
+DlgLnkInfoTab          : "Линк инфо",\r
+DlgLnkTargetTab                : "Мета",\r
+\r
+DlgLnkType                     : "Врста линка",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Сидро на овој страници",\r
+DlgLnkTypeEMail                : "Eлектронска пошта",\r
+DlgLnkProto                    : "Протокол",\r
+DlgLnkProtoOther       : "<друго>",\r
+DlgLnkURL                      : "УРЛ",\r
+DlgLnkAnchorSel                : "Одабери сидро",\r
+DlgLnkAnchorByName     : "По називу сидра",\r
+DlgLnkAnchorById       : "Пo Ид-jу елемента",\r
+DlgLnkNoAnchors                : "<Нема доступних сидра>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Адреса електронске поште",\r
+DlgLnkEMailSubject     : "Наслов",\r
+DlgLnkEMailBody                : "Садржај поруке",\r
+DlgLnkUpload           : "Пошаљи",\r
+DlgLnkBtnUpload                : "Пошаљи на сервер",\r
+\r
+DlgLnkTarget           : "Meтa",\r
+DlgLnkTargetFrame      : "<оквир>",\r
+DlgLnkTargetPopup      : "<искачући прозор>",\r
+DlgLnkTargetBlank      : "Нови прозор (_blank)",\r
+DlgLnkTargetParent     : "Родитељски прозор (_parent)",\r
+DlgLnkTargetSelf       : "Исти прозор (_self)",\r
+DlgLnkTargetTop                : "Прозор на врху (_top)",\r
+DlgLnkTargetFrameName  : "Назив одредишног фрејма",\r
+DlgLnkPopWinName       : "Назив искачућег прозора",\r
+DlgLnkPopWinFeat       : "Могућности искачућег прозора",\r
+DlgLnkPopResize                : "Променљива величина",\r
+DlgLnkPopLocation      : "Локација",\r
+DlgLnkPopMenu          : "Контекстни мени",\r
+DlgLnkPopScroll                : "Скрол бар",\r
+DlgLnkPopStatus                : "Статусна линија",\r
+DlgLnkPopToolbar       : "Toolbar",\r
+DlgLnkPopFullScrn      : "Приказ преко целог екрана (ИE)",\r
+DlgLnkPopDependent     : "Зависно (Netscape)",\r
+DlgLnkPopWidth         : "Ширина",\r
+DlgLnkPopHeight                : "Висина",\r
+DlgLnkPopLeft          : "Од леве ивице екрана (пиксела)",\r
+DlgLnkPopTop           : "Од врха екрана (пиксела)",\r
+\r
+DlnLnkMsgNoUrl         : "Унесите УРЛ линка",\r
+DlnLnkMsgNoEMail       : "Откуцајте адресу електронске поште",\r
+DlnLnkMsgNoAnchor      : "Одаберите сидро",\r
+DlnLnkMsgInvPopName    : "The popup name must begin with an alphabetic character and must not contain spaces", //MISSING\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Одаберите боју",\r
+DlgColorBtnClear       : "Обриши",\r
+DlgColorHighlight      : "Посветли",\r
+DlgColorSelected       : "Одабери",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Унеси смајлија",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Одаберите специјални карактер",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Особине табеле",\r
+DlgTableRows           : "Редова",\r
+DlgTableColumns                : "Kолона",\r
+DlgTableBorder         : "Величина оквира",\r
+DlgTableAlign          : "Равнање",\r
+DlgTableAlignNotSet    : "<није постављено>",\r
+DlgTableAlignLeft      : "Лево",\r
+DlgTableAlignCenter    : "Средина",\r
+DlgTableAlignRight     : "Десно",\r
+DlgTableWidth          : "Ширина",\r
+DlgTableWidthPx                : "пиксела",\r
+DlgTableWidthPc                : "процената",\r
+DlgTableHeight         : "Висина",\r
+DlgTableCellSpace      : "Ћелијски простор",\r
+DlgTableCellPad                : "Размак ћелија",\r
+DlgTableCaption                : "Наслов табеле",\r
+DlgTableSummary                : "Summary",    //MISSING\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Особине ћелије",\r
+DlgCellWidth           : "Ширина",\r
+DlgCellWidthPx         : "пиксела",\r
+DlgCellWidthPc         : "процената",\r
+DlgCellHeight          : "Висина",\r
+DlgCellWordWrap                : "Дељење речи",\r
+DlgCellWordWrapNotSet  : "<није постављено>",\r
+DlgCellWordWrapYes     : "Да",\r
+DlgCellWordWrapNo      : "Не",\r
+DlgCellHorAlign                : "Водоравно равнање",\r
+DlgCellHorAlignNotSet  : "<није постављено>",\r
+DlgCellHorAlignLeft    : "Лево",\r
+DlgCellHorAlignCenter  : "Средина",\r
+DlgCellHorAlignRight: "Десно",\r
+DlgCellVerAlign                : "Вертикално равнање",\r
+DlgCellVerAlignNotSet  : "<није постављено>",\r
+DlgCellVerAlignTop     : "Горње",\r
+DlgCellVerAlignMiddle  : "Средина",\r
+DlgCellVerAlignBottom  : "Доње",\r
+DlgCellVerAlignBaseline        : "Базно",\r
+DlgCellRowSpan         : "Спајање редова",\r
+DlgCellCollSpan                : "Спајање колона",\r
+DlgCellBackColor       : "Боја позадине",\r
+DlgCellBorderColor     : "Боја оквира",\r
+DlgCellBtnSelect       : "Oдабери...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Пронађи",\r
+DlgFindFindBtn         : "Пронађи",\r
+DlgFindNotFoundMsg     : "Тражени текст није пронађен.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Замени",\r
+DlgReplaceFindLbl              : "Пронађи:",\r
+DlgReplaceReplaceLbl   : "Замени са:",\r
+DlgReplaceCaseChk              : "Разликуј велика и мала слова",\r
+DlgReplaceReplaceBtn   : "Замени",\r
+DlgReplaceReplAllBtn   : "Замени све",\r
+DlgReplaceWordChk              : "Упореди целе речи",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Сигурносна подешавања Вашег претраживача не дозвољавају операције аутоматског исецања текста. Молимо Вас да користите пречицу са тастатуре (Ctrl+X).",\r
+PasteErrorCopy : "Сигурносна подешавања Вашег претраживача не дозвољавају операције аутоматског копирања текста. Молимо Вас да користите пречицу са тастатуре (Ctrl+C).",\r
+\r
+PasteAsText            : "Залепи као чист текст",\r
+PasteFromWord  : "Залепи из Worda",\r
+\r
+DlgPasteMsg2   : "Молимо Вас да залепите унутар доње површине користећи тастатурну пречицу (<STRONG>Ctrl+V</STRONG>) и да притиснете <STRONG>OK</STRONG>.",\r
+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\r
+DlgPasteIgnoreFont             : "Игнориши Font Face дефиниције",\r
+DlgPasteRemoveStyles   : "Уклони дефиниције стилова",\r
+DlgPasteCleanBox               : "Обриши све",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Аутоматски",\r
+ColorMoreColors        : "Више боја...",\r
+\r
+// Document Properties\r
+DocProps               : "Особине документа",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Особине сидра",\r
+DlgAnchorName          : "Име сидра",\r
+DlgAnchorErrorName     : "Молимо Вас да унесете име сидра",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Није у речнику",\r
+DlgSpellChangeTo               : "Измени",\r
+DlgSpellBtnIgnore              : "Игнориши",\r
+DlgSpellBtnIgnoreAll   : "Игнориши све",\r
+DlgSpellBtnReplace             : "Замени",\r
+DlgSpellBtnReplaceAll  : "Замени све",\r
+DlgSpellBtnUndo                        : "Врати акцију",\r
+DlgSpellNoSuggestions  : "- Без сугестија -",\r
+DlgSpellProgress               : "Провера спеловања у току...",\r
+DlgSpellNoMispell              : "Провера спеловања завршена: грешке нису пронађене",\r
+DlgSpellNoChanges              : "Провера спеловања завршена: Није измењена ниједна реч",\r
+DlgSpellOneChange              : "Провера спеловања завршена: Измењена је једна реч",\r
+DlgSpellManyChanges            : "Провера спеловања завршена:  %1 реч(и) је измењено",\r
+\r
+IeSpellDownload                        : "Провера спеловања није инсталирана. Да ли желите да је скинете са Интернета?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Текст (вредност)",\r
+DlgButtonType          : "Tип",\r
+DlgButtonTypeBtn       : "Button",     //MISSING\r
+DlgButtonTypeSbm       : "Submit",     //MISSING\r
+DlgButtonTypeRst       : "Reset",      //MISSING\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Назив",\r
+DlgCheckboxValue       : "Вредност",\r
+DlgCheckboxSelected    : "Означено",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Назив",\r
+DlgFormAction  : "Aкција",\r
+DlgFormMethod  : "Mетода",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Назив",\r
+DlgSelectValue         : "Вредност",\r
+DlgSelectSize          : "Величина",\r
+DlgSelectLines         : "линија",\r
+DlgSelectChkMulti      : "Дозволи вишеструку селекцију",\r
+DlgSelectOpAvail       : "Доступне опције",\r
+DlgSelectOpText                : "Текст",\r
+DlgSelectOpValue       : "Вредност",\r
+DlgSelectBtnAdd                : "Додај",\r
+DlgSelectBtnModify     : "Измени",\r
+DlgSelectBtnUp         : "Горе",\r
+DlgSelectBtnDown       : "Доле",\r
+DlgSelectBtnSetValue : "Подеси као означену вредност",\r
+DlgSelectBtnDelete     : "Обриши",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Назив",\r
+DlgTextareaCols        : "Број колона",\r
+DlgTextareaRows        : "Број редова",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Назив",\r
+DlgTextValue           : "Вредност",\r
+DlgTextCharWidth       : "Ширина (карактера)",\r
+DlgTextMaxChars                : "Максимално карактера",\r
+DlgTextType                    : "Тип",\r
+DlgTextTypeText                : "Текст",\r
+DlgTextTypePass                : "Лозинка",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Назив",\r
+DlgHiddenValue : "Вредност",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Особине Bulleted листе",\r
+NumberedListProp       : "Особине набројиве листе",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Тип",\r
+DlgLstTypeCircle       : "Круг",\r
+DlgLstTypeDisc         : "Disc",       //MISSING\r
+DlgLstTypeSquare       : "Квадрат",\r
+DlgLstTypeNumbers      : "Бројеви (1, 2, 3)",\r
+DlgLstTypeLCase                : "мала слова (a, b, c)",\r
+DlgLstTypeUCase                : "ВЕЛИКА СЛОВА (A, B, C)",\r
+DlgLstTypeSRoman       : "Мале римске цифре (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Велике римске цифре (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Опште особине",\r
+DlgDocBackTab          : "Позадина",\r
+DlgDocColorsTab                : "Боје и маргине",\r
+DlgDocMetaTab          : "Метаподаци",\r
+\r
+DlgDocPageTitle                : "Наслов странице",\r
+DlgDocLangDir          : "Смер језика",\r
+DlgDocLangDirLTR       : "Слева надесно (LTR)",\r
+DlgDocLangDirRTL       : "Здесна налево (RTL)",\r
+DlgDocLangCode         : "Шифра језика",\r
+DlgDocCharSet          : "Кодирање скупа карактера",\r
+DlgDocCharSetCE                : "Central European",   //MISSING\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)", //MISSING\r
+DlgDocCharSetCR                : "Cyrillic",   //MISSING\r
+DlgDocCharSetGR                : "Greek",      //MISSING\r
+DlgDocCharSetJP                : "Japanese",   //MISSING\r
+DlgDocCharSetKR                : "Korean",     //MISSING\r
+DlgDocCharSetTR                : "Turkish",    //MISSING\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",    //MISSING\r
+DlgDocCharSetWE                : "Western European",   //MISSING\r
+DlgDocCharSetOther     : "Остала кодирања скупа карактера",\r
+\r
+DlgDocDocType          : "Заглавље типа документа",\r
+DlgDocDocTypeOther     : "Остала заглавља типа документа",\r
+DlgDocIncXHTML         : "Улључи XHTML декларације",\r
+DlgDocBgColor          : "Боја позадине",\r
+DlgDocBgImage          : "УРЛ позадинске слике",\r
+DlgDocBgNoScroll       : "Фиксирана позадина",\r
+DlgDocCText                    : "Текст",\r
+DlgDocCLink                    : "Линк",\r
+DlgDocCVisited         : "Посећени линк",\r
+DlgDocCActive          : "Активни линк",\r
+DlgDocMargins          : "Маргине странице",\r
+DlgDocMaTop                    : "Горња",\r
+DlgDocMaLeft           : "Лева",\r
+DlgDocMaRight          : "Десна",\r
+DlgDocMaBottom         : "Доња",\r
+DlgDocMeIndex          : "Кључне речи за индексирање документа (раздвојене зарезом)",\r
+DlgDocMeDescr          : "Опис документа",\r
+DlgDocMeAuthor         : "Аутор",\r
+DlgDocMeCopy           : "Ауторска права",\r
+DlgDocPreview          : "Изглед странице",\r
+\r
+// Templates Dialog\r
+Templates                      : "Обрасци",\r
+DlgTemplatesTitle      : "Обрасци за садржај",\r
+DlgTemplatesSelMsg     : "Молимо Вас да одаберете образац који ће бити примењен на страницу (тренутни садржај ће бити обрисан):",\r
+DlgTemplatesLoading    : "Учитавам листу образаца. Мало стрпљења...",\r
+DlgTemplatesNoTpl      : "(Нема дефинисаних образаца)",\r
+DlgTemplatesReplace    : "Replace actual contents",    //MISSING\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "О едитору",\r
+DlgAboutBrowserInfoTab : "Информације о претраживачу",\r
+DlgAboutLicenseTab     : "License",    //MISSING\r
+DlgAboutVersion                : "верзија",\r
+DlgAboutInfo           : "За више информација посетите"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/sv.js b/httemplate/elements/fckeditor/editor/lang/sv.js
new file mode 100644 (file)
index 0000000..a301a03
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Swedish language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Dölj verktygsfält",\r
+ToolbarExpand          : "Visa verktygsfält",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Spara",\r
+NewPage                                : "Ny sida",\r
+Preview                                : "Förhandsgranska",\r
+Cut                                    : "Klipp ut",\r
+Copy                           : "Kopiera",\r
+Paste                          : "Klistra in",\r
+PasteText                      : "Klistra in som text",\r
+PasteWord                      : "Klistra in från Word",\r
+Print                          : "Skriv ut",\r
+SelectAll                      : "Markera allt",\r
+RemoveFormat           : "Radera formatering",\r
+InsertLinkLbl          : "Länk",\r
+InsertLink                     : "Infoga/Redigera länk",\r
+RemoveLink                     : "Radera länk",\r
+Anchor                         : "Infoga/Redigera ankarlänk",\r
+InsertImageLbl         : "Bild",\r
+InsertImage                    : "Infoga/Redigera bild",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Infoga/Redigera Flash",\r
+InsertTableLbl         : "Tabell",\r
+InsertTable                    : "Infoga/Redigera tabell",\r
+InsertLineLbl          : "Linje",\r
+InsertLine                     : "Infoga horisontal linje",\r
+InsertSpecialCharLbl: "Utökade tecken",\r
+InsertSpecialChar      : "Klistra in utökat tecken",\r
+InsertSmileyLbl                : "Smiley",\r
+InsertSmiley           : "Infoga Smiley",\r
+About                          : "Om FCKeditor",\r
+Bold                           : "Fet",\r
+Italic                         : "Kursiv",\r
+Underline                      : "Understruken",\r
+StrikeThrough          : "Genomstruken",\r
+Subscript                      : "Nedsänkta tecken",\r
+Superscript                    : "Upphöjda tecken",\r
+LeftJustify                    : "Vänsterjustera",\r
+CenterJustify          : "Centrera",\r
+RightJustify           : "Högerjustera",\r
+BlockJustify           : "Justera till marginaler",\r
+DecreaseIndent         : "Minska indrag",\r
+IncreaseIndent         : "Öka indrag",\r
+Undo                           : "Ångra",\r
+Redo                           : "Gör om",\r
+NumberedListLbl                : "Numrerad lista",\r
+NumberedList           : "Infoga/Radera numrerad lista",\r
+BulletedListLbl                : "Punktlista",\r
+BulletedList           : "Infoga/Radera punktlista",\r
+ShowTableBorders       : "Visa tabellkant",\r
+ShowDetails                    : "Visa radbrytningar",\r
+Style                          : "Anpassad stil",\r
+FontFormat                     : "Teckenformat",\r
+Font                           : "Typsnitt",\r
+FontSize                       : "Storlek",\r
+TextColor                      : "Textfärg",\r
+BGColor                                : "Bakgrundsfärg",\r
+Source                         : "Källa",\r
+Find                           : "Sök",\r
+Replace                                : "Ersätt",\r
+SpellCheck                     : "Stavningskontroll",\r
+UniversalKeyboard      : "Universellt tangentbord",\r
+PageBreakLbl           : "Sidbrytning",\r
+PageBreak                      : "Infoga sidbrytning",\r
+\r
+Form                   : "Formulär",\r
+Checkbox               : "Kryssruta",\r
+RadioButton            : "Alternativknapp",\r
+TextField              : "Textfält",\r
+Textarea               : "Textruta",\r
+HiddenField            : "Dolt fält",\r
+Button                 : "Knapp",\r
+SelectionField : "Flervalslista",\r
+ImageButton            : "Bildknapp",\r
+\r
+FitWindow              : "Anpassa till fönstrets storlek",\r
+\r
+// Context Menu\r
+EditLink                       : "Redigera länk",\r
+CellCM                         : "Cell",\r
+RowCM                          : "Rad",\r
+ColumnCM                       : "Kolumn",\r
+InsertRow                      : "Infoga rad",\r
+DeleteRows                     : "Radera rad",\r
+InsertColumn           : "Infoga kolumn",\r
+DeleteColumns          : "Radera kolumn",\r
+InsertCell                     : "Infoga cell",\r
+DeleteCells                    : "Radera celler",\r
+MergeCells                     : "Sammanfoga celler",\r
+SplitCell                      : "Separera celler",\r
+TableDelete                    : "Radera tabell",\r
+CellProperties         : "Cellegenskaper",\r
+TableProperties                : "Tabellegenskaper",\r
+ImageProperties                : "Bildegenskaper",\r
+FlashProperties                : "Flashegenskaper",\r
+\r
+AnchorProp                     : "Egenskaper för ankarlänk",\r
+ButtonProp                     : "Egenskaper för knapp",\r
+CheckboxProp           : "Egenskaper för kryssruta",\r
+HiddenFieldProp                : "Egenskaper för dolt fält",\r
+RadioButtonProp                : "Egenskaper för alternativknapp",\r
+ImageButtonProp                : "Egenskaper för bildknapp",\r
+TextFieldProp          : "Egenskaper för textfält",\r
+SelectionFieldProp     : "Egenskaper för flervalslista",\r
+TextareaProp           : "Egenskaper för textruta",\r
+FormProp                       : "Egenskaper för formulär",\r
+\r
+FontFormats                    : "Normal;Formaterad;Adress;Rubrik 1;Rubrik 2;Rubrik 3;Rubrik 4;Rubrik 5;Rubrik 6;Normal (DIV)",                //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Bearbetar XHTML. Var god vänta...",\r
+Done                           : "Klar",\r
+PasteWordConfirm       : "Texten du vill klistra in verkar vara kopierad från Word. Vill du rensa innan du klistar in?",\r
+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?",\r
+UnknownToolbarItem     : "Okänt verktygsfält \"%1\"",\r
+UnknownCommand         : "Okänt kommando \"%1\"",\r
+NotImplemented         : "Kommandot finns ej",\r
+UnknownToolbarSet      : "Verktygsfält \"%1\" finns ej",\r
+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å.",\r
+BrowseServerBlocked : "Kunde Ej öppna resursfönstret. Var god och avaktivera alla popup-blockerare.",\r
+DialogBlocked          : "Kunde Ej öppna dialogfönstret. Var god och avaktivera alla popup-blockerare.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "OK",\r
+DlgBtnCancel           : "Avbryt",\r
+DlgBtnClose                    : "Stäng",\r
+DlgBtnBrowseServer     : "Bläddra på server",\r
+DlgAdvancedTag         : "Avancerad",\r
+DlgOpOther                     : "Övrigt",\r
+DlgInfoTab                     : "Information",\r
+DlgAlertUrl                    : "Var god och ange en URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<ej angivet>",\r
+DlgGenId                       : "Id",\r
+DlgGenLangDir          : "Språkriktning",\r
+DlgGenLangDirLtr       : "Vänster till Höger (VTH)",\r
+DlgGenLangDirRtl       : "Höger till Vänster (HTV)",\r
+DlgGenLangCode         : "Språkkod",\r
+DlgGenAccessKey                : "Behörighetsnyckel",\r
+DlgGenName                     : "Namn",\r
+DlgGenTabIndex         : "Tabindex",\r
+DlgGenLongDescr                : "URL-beskrivning",\r
+DlgGenClass                    : "Stylesheet class",\r
+DlgGenTitle                    : "Titel",\r
+DlgGenContType         : "Innehållstyp",\r
+DlgGenLinkCharset      : "Teckenuppställning",\r
+DlgGenStyle                    : "Style",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Bildegenskaper",\r
+DlgImgInfoTab          : "Bildinformation",\r
+DlgImgBtnUpload                : "Skicka till server",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Ladda upp",\r
+DlgImgAlt                      : "Alternativ text",\r
+DlgImgWidth                    : "Bredd",\r
+DlgImgHeight           : "Höjd",\r
+DlgImgLockRatio                : "Lås höjd/bredd förhållanden",\r
+DlgBtnResetSize                : "Återställ storlek",\r
+DlgImgBorder           : "Kant",\r
+DlgImgHSpace           : "Horis. marginal",\r
+DlgImgVSpace           : "Vert. marginal",\r
+DlgImgAlign                    : "Justering",\r
+DlgImgAlignLeft                : "Vänster",\r
+DlgImgAlignAbsBottom: "Absolut nederkant",\r
+DlgImgAlignAbsMiddle: "Absolut centrering",\r
+DlgImgAlignBaseline    : "Baslinje",\r
+DlgImgAlignBottom      : "Nederkant",\r
+DlgImgAlignMiddle      : "Mitten",\r
+DlgImgAlignRight       : "Höger",\r
+DlgImgAlignTextTop     : "Text överkant",\r
+DlgImgAlignTop         : "Överkant",\r
+DlgImgPreview          : "Förhandsgranska",\r
+DlgImgAlertUrl         : "Var god och ange bildens URL",\r
+DlgImgLinkTab          : "Länk",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flashegenskaper",\r
+DlgFlashChkPlay                : "Automatisk uppspelning",\r
+DlgFlashChkLoop                : "Upprepa/Loopa",\r
+DlgFlashChkMenu                : "Aktivera Flashmeny",\r
+DlgFlashScale          : "Skala",\r
+DlgFlashScaleAll       : "Visa allt",\r
+DlgFlashScaleNoBorder  : "Ingen ram",\r
+DlgFlashScaleFit       : "Exakt passning",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Länk",\r
+DlgLnkInfoTab          : "Länkinformation",\r
+DlgLnkTargetTab                : "Mål",\r
+\r
+DlgLnkType                     : "Länktyp",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Ankare i sidan",\r
+DlgLnkTypeEMail                : "E-post",\r
+DlgLnkProto                    : "Protokoll",\r
+DlgLnkProtoOther       : "<övrigt>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Välj ett ankare",\r
+DlgLnkAnchorByName     : "efter ankarnamn",\r
+DlgLnkAnchorById       : "efter objektid",\r
+DlgLnkNoAnchors                : "(Inga ankare kunde hittas)",         //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-postadress",\r
+DlgLnkEMailSubject     : "Ämne",\r
+DlgLnkEMailBody                : "Innehåll",\r
+DlgLnkUpload           : "Ladda upp",\r
+DlgLnkBtnUpload                : "Skicka till servern",\r
+\r
+DlgLnkTarget           : "Mål",\r
+DlgLnkTargetFrame      : "<ram>",\r
+DlgLnkTargetPopup      : "<popup-fönster>",\r
+DlgLnkTargetBlank      : "Nytt fönster (_blank)",\r
+DlgLnkTargetParent     : "Föregående Window (_parent)",\r
+DlgLnkTargetSelf       : "Detta fönstret (_self)",\r
+DlgLnkTargetTop                : "Översta fönstret (_top)",\r
+DlgLnkTargetFrameName  : "Målets ramnamn",\r
+DlgLnkPopWinName       : "Popup-fönstrets namn",\r
+DlgLnkPopWinFeat       : "Popup-fönstrets egenskaper",\r
+DlgLnkPopResize                : "Kan ändra storlek",\r
+DlgLnkPopLocation      : "Adressfält",\r
+DlgLnkPopMenu          : "Menyfält",\r
+DlgLnkPopScroll                : "Scrolllista",\r
+DlgLnkPopStatus                : "Statusfält",\r
+DlgLnkPopToolbar       : "Verktygsfält",\r
+DlgLnkPopFullScrn      : "Helskärm (endast IE)",\r
+DlgLnkPopDependent     : "Beroende (endest Netscape)",\r
+DlgLnkPopWidth         : "Bredd",\r
+DlgLnkPopHeight                : "Höjd",\r
+DlgLnkPopLeft          : "Position från vänster",\r
+DlgLnkPopTop           : "Position från sidans topp",\r
+\r
+DlnLnkMsgNoUrl         : "Var god ange länkens URL",\r
+DlnLnkMsgNoEMail       : "Var god ange E-postadress",\r
+DlnLnkMsgNoAnchor      : "Var god ange ett ankare",\r
+DlnLnkMsgInvPopName    : "Popup-rutans namn måste börja med en alfabetisk bokstav och får inte innehålla mellanslag",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Välj färg",\r
+DlgColorBtnClear       : "Rensa",\r
+DlgColorHighlight      : "Markera",\r
+DlgColorSelected       : "Vald",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Infoga smiley",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Välj utökat tecken",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Tabellegenskaper",\r
+DlgTableRows           : "Rader",\r
+DlgTableColumns                : "Kolumner",\r
+DlgTableBorder         : "Kantstorlek",\r
+DlgTableAlign          : "Justering",\r
+DlgTableAlignNotSet    : "<ej angivet>",\r
+DlgTableAlignLeft      : "Vänster",\r
+DlgTableAlignCenter    : "Centrerad",\r
+DlgTableAlignRight     : "Höger",\r
+DlgTableWidth          : "Bredd",\r
+DlgTableWidthPx                : "pixlar",\r
+DlgTableWidthPc                : "procent",\r
+DlgTableHeight         : "Höjd",\r
+DlgTableCellSpace      : "Cellavstånd",\r
+DlgTableCellPad                : "Cellutfyllnad",\r
+DlgTableCaption                : "Rubrik",\r
+DlgTableSummary                : "Sammanfattning",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Cellegenskaper",\r
+DlgCellWidth           : "Bredd",\r
+DlgCellWidthPx         : "pixlar",\r
+DlgCellWidthPc         : "procent",\r
+DlgCellHeight          : "Höjd",\r
+DlgCellWordWrap                : "Automatisk radbrytning",\r
+DlgCellWordWrapNotSet  : "<Ej angivet>",\r
+DlgCellWordWrapYes     : "Ja",\r
+DlgCellWordWrapNo      : "Nej",\r
+DlgCellHorAlign                : "Horisontal justering",\r
+DlgCellHorAlignNotSet  : "<Ej angivet>",\r
+DlgCellHorAlignLeft    : "Vänster",\r
+DlgCellHorAlignCenter  : "Centrerad",\r
+DlgCellHorAlignRight: "Höger",\r
+DlgCellVerAlign                : "Vertikal justering",\r
+DlgCellVerAlignNotSet  : "<Ej angivet>",\r
+DlgCellVerAlignTop     : "Topp",\r
+DlgCellVerAlignMiddle  : "Mitten",\r
+DlgCellVerAlignBottom  : "Nederkant",\r
+DlgCellVerAlignBaseline        : "Underst",\r
+DlgCellRowSpan         : "Radomfång",\r
+DlgCellCollSpan                : "Kolumnomfång",\r
+DlgCellBackColor       : "Bakgrundsfärg",\r
+DlgCellBorderColor     : "Kantfärg",\r
+DlgCellBtnSelect       : "Välj...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Sök",\r
+DlgFindFindBtn         : "Sök",\r
+DlgFindNotFoundMsg     : "Angiven text kunde ej hittas.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Ersätt",\r
+DlgReplaceFindLbl              : "Sök efter:",\r
+DlgReplaceReplaceLbl   : "Ersätt med:",\r
+DlgReplaceCaseChk              : "Skiftläge",\r
+DlgReplaceReplaceBtn   : "Ersätt",\r
+DlgReplaceReplAllBtn   : "Ersätt alla",\r
+DlgReplaceWordChk              : "Inkludera hela ord",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Säkerhetsinställningar i Er webläsare tillåter inte åtgården Klipp ut. Använd (Ctrl+X) istället.",\r
+PasteErrorCopy : "Säkerhetsinställningar i Er webläsare tillåter inte åtgården Kopiera. Använd (Ctrl+C) istället",\r
+\r
+PasteAsText            : "Klistra in som vanlig text",\r
+PasteFromWord  : "Klistra in från Word",\r
+\r
+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>.",\r
+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.",\r
+DlgPasteIgnoreFont             : "Ignorera typsnittsdefinitioner",\r
+DlgPasteRemoveStyles   : "Radera Stildefinitioner",\r
+DlgPasteCleanBox               : "Töm rutans innehåll",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Automatisk",\r
+ColorMoreColors        : "Fler färger...",\r
+\r
+// Document Properties\r
+DocProps               : "Dokumentegenskaper",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Ankaregenskaper",\r
+DlgAnchorName          : "Ankarnamn",\r
+DlgAnchorErrorName     : "Var god ange ett ankarnamn",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Saknas i ordlistan",\r
+DlgSpellChangeTo               : "Ändra till",\r
+DlgSpellBtnIgnore              : "Ignorera",\r
+DlgSpellBtnIgnoreAll   : "Ignorera alla",\r
+DlgSpellBtnReplace             : "Ersätt",\r
+DlgSpellBtnReplaceAll  : "Ersätt alla",\r
+DlgSpellBtnUndo                        : "Ångra",\r
+DlgSpellNoSuggestions  : "- Förslag saknas -",\r
+DlgSpellProgress               : "Stavningskontroll pågår...",\r
+DlgSpellNoMispell              : "Stavningskontroll slutförd: Inga stavfel påträffades.",\r
+DlgSpellNoChanges              : "Stavningskontroll slutförd: Inga ord rättades.",\r
+DlgSpellOneChange              : "Stavningskontroll slutförd: Ett ord rättades.",\r
+DlgSpellManyChanges            : "Stavningskontroll slutförd: %1 ord rättades.",\r
+\r
+IeSpellDownload                        : "Stavningskontrollen är ej installerad. Vill du göra det nu?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Text (Värde)",\r
+DlgButtonType          : "Typ",\r
+DlgButtonTypeBtn       : "Knapp",\r
+DlgButtonTypeSbm       : "Skicka",\r
+DlgButtonTypeRst       : "Återställ",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Namn",\r
+DlgCheckboxValue       : "Värde",\r
+DlgCheckboxSelected    : "Vald",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Namn",\r
+DlgFormAction  : "Funktion",\r
+DlgFormMethod  : "Metod",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Namn",\r
+DlgSelectValue         : "Värde",\r
+DlgSelectSize          : "Storlek",\r
+DlgSelectLines         : "Linjer",\r
+DlgSelectChkMulti      : "Tillåt flerval",\r
+DlgSelectOpAvail       : "Befintliga val",\r
+DlgSelectOpText                : "Text",\r
+DlgSelectOpValue       : "Värde",\r
+DlgSelectBtnAdd                : "Lägg till",\r
+DlgSelectBtnModify     : "Redigera",\r
+DlgSelectBtnUp         : "Upp",\r
+DlgSelectBtnDown       : "Ner",\r
+DlgSelectBtnSetValue : "Markera som valt värde",\r
+DlgSelectBtnDelete     : "Radera",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Namn",\r
+DlgTextareaCols        : "Kolumner",\r
+DlgTextareaRows        : "Rader",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Namn",\r
+DlgTextValue           : "Värde",\r
+DlgTextCharWidth       : "Teckenbredd",\r
+DlgTextMaxChars                : "Max antal tecken",\r
+DlgTextType                    : "Typ",\r
+DlgTextTypeText                : "Text",\r
+DlgTextTypePass                : "Lösenord",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Namn",\r
+DlgHiddenValue : "Värde",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Egenskaper för punktlista",\r
+NumberedListProp       : "Egenskaper för numrerad lista",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "Typ",\r
+DlgLstTypeCircle       : "Cirkel",\r
+DlgLstTypeDisc         : "Punkt",\r
+DlgLstTypeSquare       : "Ruta",\r
+DlgLstTypeNumbers      : "Nummer (1, 2, 3)",\r
+DlgLstTypeLCase                : "Gemener (a, b, c)",\r
+DlgLstTypeUCase                : "Versaler (A, B, C)",\r
+DlgLstTypeSRoman       : "Små romerska siffror (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Stora romerska siffror (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Allmän",\r
+DlgDocBackTab          : "Bakgrund",\r
+DlgDocColorsTab                : "Färg och marginal",\r
+DlgDocMetaTab          : "Metadata",\r
+\r
+DlgDocPageTitle                : "Sidtitel",\r
+DlgDocLangDir          : "Språkriktning",\r
+DlgDocLangDirLTR       : "Vänster till Höger",\r
+DlgDocLangDirRTL       : "Höger till Vänster",\r
+DlgDocLangCode         : "Språkkod",\r
+DlgDocCharSet          : "Teckenuppsättningar",\r
+DlgDocCharSetCE                : "Central Europa",\r
+DlgDocCharSetCT                : "Traditionell Kinesisk (Big5)",\r
+DlgDocCharSetCR                : "Kyrillisk",\r
+DlgDocCharSetGR                : "Grekiska",\r
+DlgDocCharSetJP                : "Japanska",\r
+DlgDocCharSetKR                : "Koreanska",\r
+DlgDocCharSetTR                : "Turkiska",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Väst Europa",\r
+DlgDocCharSetOther     : "Övriga teckenuppsättningar",\r
+\r
+DlgDocDocType          : "Sidhuvud",\r
+DlgDocDocTypeOther     : "Övriga sidhuvuden",\r
+DlgDocIncXHTML         : "Inkludera XHTML deklaration",\r
+DlgDocBgColor          : "Bakgrundsfärg",\r
+DlgDocBgImage          : "Bakgrundsbildens URL",\r
+DlgDocBgNoScroll       : "Fast bakgrund",\r
+DlgDocCText                    : "Text",\r
+DlgDocCLink                    : "Länk",\r
+DlgDocCVisited         : "Besökt länk",\r
+DlgDocCActive          : "Aktiv länk",\r
+DlgDocMargins          : "Sidmarginal",\r
+DlgDocMaTop                    : "Topp",\r
+DlgDocMaLeft           : "Vänster",\r
+DlgDocMaRight          : "Höger",\r
+DlgDocMaBottom         : "Botten",\r
+DlgDocMeIndex          : "Sidans nyckelord",\r
+DlgDocMeDescr          : "Sidans beskrivning",\r
+DlgDocMeAuthor         : "Författare",\r
+DlgDocMeCopy           : "Upphovsrätt",\r
+DlgDocPreview          : "Förhandsgranska",\r
+\r
+// Templates Dialog\r
+Templates                      : "Sidmallar",\r
+DlgTemplatesTitle      : "Sidmallar",\r
+DlgTemplatesSelMsg     : "Var god välj en mall att använda med editorn<br>(allt nuvarande innehåll raderas):",\r
+DlgTemplatesLoading    : "Laddar mallar. Var god vänta...",\r
+DlgTemplatesNoTpl      : "(Ingen mall är vald)",\r
+DlgTemplatesReplace    : "Ersätt aktuellt innehåll",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Om",\r
+DlgAboutBrowserInfoTab : "Webläsare",\r
+DlgAboutLicenseTab     : "Licens",\r
+DlgAboutVersion                : "version",\r
+DlgAboutInfo           : "För mer information se"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/th.js b/httemplate/elements/fckeditor/editor/lang/th.js
new file mode 100644 (file)
index 0000000..8c4319a
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Thai language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "ซ่อนแถบเครื่องมือ",\r
+ToolbarExpand          : "แสดงแถบเครื่องมือ",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "บันทึก",\r
+NewPage                                : "สร้างหน้าเอกสารใหม่",\r
+Preview                                : "ดูหน้าเอกสารตัวอย่าง",\r
+Cut                                    : "ตัด",\r
+Copy                           : "สำเนา",\r
+Paste                          : "วาง",\r
+PasteText                      : "วางสำเนาจากตัวอักษรธรรมดา",\r
+PasteWord                      : "วางสำเนาจากตัวอักษรเวิร์ด",\r
+Print                          : "สั่งพิมพ์",\r
+SelectAll                      : "เลือกทั้งหมด",\r
+RemoveFormat           : "ล้างรูปแบบ",\r
+InsertLinkLbl          : "ลิงค์เชื่อมโยงเว็บ อีเมล์ รูปภาพ หรือไฟล์อื่นๆ",\r
+InsertLink                     : "แทรก/แก้ไข ลิงค์",\r
+RemoveLink                     : "ลบ ลิงค์",\r
+Anchor                         : "แทรก/แก้ไข Anchor",\r
+InsertImageLbl         : "รูปภาพ",\r
+InsertImage                    : "แทรก/แก้ไข รูปภาพ",\r
+InsertFlashLbl         : "ไฟล์ Flash",\r
+InsertFlash                    : "แทรก/แก้ไข ไฟล์ Flash",\r
+InsertTableLbl         : "ตาราง",\r
+InsertTable                    : "แทรก/แก้ไข ตาราง",\r
+InsertLineLbl          : "เส้นคั่นบรรทัด",\r
+InsertLine                     : "แทรกเส้นคั่นบรรทัด",\r
+InsertSpecialCharLbl: "ตัวอักษรพิเศษ",\r
+InsertSpecialChar      : "แทรกตัวอักษรพิเศษ",\r
+InsertSmileyLbl                : "รูปสื่ออารมณ์",\r
+InsertSmiley           : "แทรกรูปสื่ออารมณ์",\r
+About                          : "เกี่ยวกับโปรแกรม FCKeditor",\r
+Bold                           : "ตัวหนา",\r
+Italic                         : "ตัวเอียง",\r
+Underline                      : "ตัวขีดเส้นใต้",\r
+StrikeThrough          : "ตัวขีดเส้นทับ",\r
+Subscript                      : "ตัวห้อย",\r
+Superscript                    : "ตัวยก",\r
+LeftJustify                    : "จัดชิดซ้าย",\r
+CenterJustify          : "จัดกึ่งกลาง",\r
+RightJustify           : "จัดชิดขวา",\r
+BlockJustify           : "จัดพอดีหน้ากระดาษ",\r
+DecreaseIndent         : "ลดระยะย่อหน้า",\r
+IncreaseIndent         : "เพิ่มระยะย่อหน้า",\r
+Undo                           : "ยกเลิกคำสั่ง",\r
+Redo                           : "ทำซ้ำคำสั่ง",\r
+NumberedListLbl                : "ลำดับรายการแบบตัวเลข",\r
+NumberedList           : "แทรก/แก้ไข ลำดับรายการแบบตัวเลข",\r
+BulletedListLbl                : "ลำดับรายการแบบสัญลักษณ์",\r
+BulletedList           : "แทรก/แก้ไข ลำดับรายการแบบสัญลักษณ์",\r
+ShowTableBorders       : "แสดงขอบของตาราง",\r
+ShowDetails                    : "แสดงรายละเอียด",\r
+Style                          : "ลักษณะ",\r
+FontFormat                     : "รูปแบบ",\r
+Font                           : "แบบอักษร",\r
+FontSize                       : "ขนาด",\r
+TextColor                      : "สีตัวอักษร",\r
+BGColor                                : "สีพื้นหลัง",\r
+Source                         : "ดูรหัส HTML",\r
+Find                           : "ค้นหา",\r
+Replace                                : "ค้นหาและแทนที่",\r
+SpellCheck                     : "ตรวจการสะกดคำ",\r
+UniversalKeyboard      : "คีย์บอร์ดหลากภาษา",\r
+PageBreakLbl           : "ใส่ตัวแบ่งหน้า Page Break",\r
+PageBreak                      : "แทรกตัวแบ่งหน้า Page Break",\r
+\r
+Form                   : "แบบฟอร์ม",\r
+Checkbox               : "เช็คบ๊อก",\r
+RadioButton            : "เรดิโอบัตตอน",\r
+TextField              : "เท็กซ์ฟิลด์",\r
+Textarea               : "เท็กซ์แอเรีย",\r
+HiddenField            : "ฮิดเดนฟิลด์",\r
+Button                 : "ปุ่ม",\r
+SelectionField : "แถบตัวเลือก",\r
+ImageButton            : "ปุ่มแบบรูปภาพ",\r
+\r
+FitWindow              : "ขยายขนาดตัวอีดิตเตอร์",\r
+\r
+// Context Menu\r
+EditLink                       : "แก้ไข ลิงค์",\r
+CellCM                         : "ช่องตาราง",\r
+RowCM                          : "แถว",\r
+ColumnCM                       : "คอลัมน์",\r
+InsertRow                      : "แทรกแถว",\r
+DeleteRows                     : "ลบแถว",\r
+InsertColumn           : "แทรกสดมน์",\r
+DeleteColumns          : "ลบสดมน์",\r
+InsertCell                     : "แทรกช่อง",\r
+DeleteCells                    : "ลบช่อง",\r
+MergeCells                     : "ผสานช่อง",\r
+SplitCell                      : "แยกช่อง",\r
+TableDelete                    : "ลบตาราง",\r
+CellProperties         : "คุณสมบัติของช่อง",\r
+TableProperties                : "คุณสมบัติของตาราง",\r
+ImageProperties                : "คุณสมบัติของรูปภาพ",\r
+FlashProperties                : "คุณสมบัติของไฟล์ Flash",\r
+\r
+AnchorProp                     : "รายละเอียด Anchor",\r
+ButtonProp                     : "รายละเอียดของ ปุ่ม",\r
+CheckboxProp           : "คุณสมบัติของ เช็คบ๊อก",\r
+HiddenFieldProp                : "คุณสมบัติของ ฮิดเดนฟิลด์",\r
+RadioButtonProp                : "คุณสมบัติของ เรดิโอบัตตอน",\r
+ImageButtonProp                : "คุณสมบัติของ ปุ่มแบบรูปภาพ",\r
+TextFieldProp          : "คุณสมบัติของ เท็กซ์ฟิลด์",\r
+SelectionFieldProp     : "คุณสมบัติของ แถบตัวเลือก",\r
+TextareaProp           : "คุณสมบัติของ เท็กแอเรีย",\r
+FormProp                       : "คุณสมบัติของ แบบฟอร์ม",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Paragraph (DIV)",               //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "โปรแกรมกำลังทำงานด้วยเทคโนโลยี XHTML กรุณารอสักครู่...",\r
+Done                           : "โปรแกรมทำงานเสร็จสมบูรณ์",\r
+PasteWordConfirm       : "ข้อมูลที่ท่านต้องการวางลงในแผ่นงาน ถูกจัดรูปแบบจากโปรแกรมเวิร์ด. ท่านต้องการล้างรูปแบบที่มาจากโปรแกรมเวิร์ดหรือไม่?",\r
+NotCompatiblePaste     : "คำสั่งนี้ทำงานในโปรแกรมท่องเว็บ Internet Explorer version รุ่น 5.5 หรือใหม่กว่าเท่านั้น. ท่านต้องการวางตัวอักษรโดยไม่ล้างรูปแบบที่มาจากโปรแกรมเวิร์ดหรือไม่?",\r
+UnknownToolbarItem     : "ไม่สามารถระบุปุ่มเครื่องมือได้ \"%1\"",\r
+UnknownCommand         : "ไม่สามารถระบุชื่อคำสั่งได้ \"%1\"",\r
+NotImplemented         : "ไม่สามารถใช้งานคำสั่งได้",\r
+UnknownToolbarSet      : "ไม่มีการติดตั้งชุดคำสั่งในแถบเครื่องมือ \"%1\" กรุณาติดต่อผู้ดูแลระบบ",\r
+NoActiveX                      : "โปรแกรมท่องอินเตอร์เน็ตของท่านไม่อนุญาติให้อีดิตเตอร์ทำงาน \"Run ActiveX controls and plug-ins\". หากไม่อนุญาติให้ใช้งาน ActiveX controls ท่านจะไม่สามารถใช้งานได้อย่างเต็มประสิทธิภาพ.",\r
+BrowseServerBlocked : "เปิดหน้าต่างป๊อบอัพเพื่อทำงานต่อไม่ได้ กรุณาปิดเครื่องมือป้องกันป๊อบอัพในโปรแกรมท่องอินเตอร์เน็ตของท่านด้วย",\r
+DialogBlocked          : "เปิดหน้าต่างป๊อบอัพเพื่อทำงานต่อไม่ได้ กรุณาปิดเครื่องมือป้องกันป๊อบอัพในโปรแกรมท่องอินเตอร์เน็ตของท่านด้วย",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "ตกลง",\r
+DlgBtnCancel           : "ยกเลิก",\r
+DlgBtnClose                    : "ปิด",\r
+DlgBtnBrowseServer     : "เปิดหน้าต่างจัดการไฟล์อัพโหลด",\r
+DlgAdvancedTag         : "ขั้นสูง",\r
+DlgOpOther                     : "<อื่นๆ>",\r
+DlgInfoTab                     : "อินโฟ",\r
+DlgAlertUrl                    : "กรุณาระบุ URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<ไม่ระบุ>",\r
+DlgGenId                       : "ไอดี",\r
+DlgGenLangDir          : "การเขียน-อ่านภาษา",\r
+DlgGenLangDirLtr       : "จากซ้ายไปขวา (LTR)",\r
+DlgGenLangDirRtl       : "จากขวามาซ้าย (RTL)",\r
+DlgGenLangCode         : "รหัสภาษา",\r
+DlgGenAccessKey                : "แอคเซส คีย์",\r
+DlgGenName                     : "ชื่อ",\r
+DlgGenTabIndex         : "ลำดับของ แท็บ",\r
+DlgGenLongDescr                : "คำอธิบายประกอบ URL",\r
+DlgGenClass                    : "คลาสของไฟล์กำหนดลักษณะการแสดงผล",\r
+DlgGenTitle                    : "คำเกริ่นนำ",\r
+DlgGenContType         : "ชนิดของคำเกริ่นนำ",\r
+DlgGenLinkCharset      : "ลิงค์เชื่อมโยงไปยังชุดตัวอักษร",\r
+DlgGenStyle                    : "ลักษณะการแสดงผล",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "คุณสมบัติของ รูปภาพ",\r
+DlgImgInfoTab          : "ข้อมูลของรูปภาพ",\r
+DlgImgBtnUpload                : "อัพโหลดไฟล์ไปเก็บไว้ที่เครื่องแม่ข่าย (เซิร์ฟเวอร์)",\r
+DlgImgURL                      : "ที่อยู่อ้างอิง URL",\r
+DlgImgUpload           : "อัพโหลดไฟล์",\r
+DlgImgAlt                      : "คำประกอบรูปภาพ",\r
+DlgImgWidth                    : "ความกว้าง",\r
+DlgImgHeight           : "ความสูง",\r
+DlgImgLockRatio                : "กำหนดอัตราส่วน กว้าง-สูง แบบคงที่",\r
+DlgBtnResetSize                : "กำหนดรูปเท่าขนาดจริง",\r
+DlgImgBorder           : "ขนาดขอบรูป",\r
+DlgImgHSpace           : "ระยะแนวนอน",\r
+DlgImgVSpace           : "ระยะแนวตั้ง",\r
+DlgImgAlign                    : "การจัดวาง",\r
+DlgImgAlignLeft                : "ชิดซ้าย",\r
+DlgImgAlignAbsBottom: "ชิดด้านล่างสุด",\r
+DlgImgAlignAbsMiddle: "กึ่งกลาง",\r
+DlgImgAlignBaseline    : "ชิดบรรทัด",\r
+DlgImgAlignBottom      : "ชิดด้านล่าง",\r
+DlgImgAlignMiddle      : "กึ่งกลางแนวตั้ง",\r
+DlgImgAlignRight       : "ชิดขวา",\r
+DlgImgAlignTextTop     : "ใต้ตัวอักษร",\r
+DlgImgAlignTop         : "บนสุด",\r
+DlgImgPreview          : "หน้าเอกสารตัวอย่าง",\r
+DlgImgAlertUrl         : "กรุณาระบุที่อยู่อ้างอิงออนไลน์ของไฟล์รูปภาพ (URL)",\r
+DlgImgLinkTab          : "ลิ้งค์",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "คุณสมบัติของไฟล์ Flash",\r
+DlgFlashChkPlay                : "เล่นอัตโนมัติ Auto Play",\r
+DlgFlashChkLoop                : "เล่นวนรอบ Loop",\r
+DlgFlashChkMenu                : "ให้ใช้งานเมนูของ Flash",\r
+DlgFlashScale          : "อัตราส่วน Scale",\r
+DlgFlashScaleAll       : "แสดงให้เห็นทั้งหมด Show all",\r
+DlgFlashScaleNoBorder  : "ไม่แสดงเส้นขอบ No Border",\r
+DlgFlashScaleFit       : "แสดงให้พอดีกับพื้นที่ Exact Fit",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "ลิงค์เชื่อมโยงเว็บ อีเมล์ รูปภาพ หรือไฟล์อื่นๆ",\r
+DlgLnkInfoTab          : "รายละเอียด",\r
+DlgLnkTargetTab                : "การเปิดหน้าจอ",\r
+\r
+DlgLnkType                     : "ประเภทของลิงค์",\r
+DlgLnkTypeURL          : "ที่อยู่อ้างอิงออนไลน์ (URL)",\r
+DlgLnkTypeAnchor       : "จุดเชื่อมโยง (Anchor)",\r
+DlgLnkTypeEMail                : "ส่งอีเมล์ (E-Mail)",\r
+DlgLnkProto                    : "โปรโตคอล",\r
+DlgLnkProtoOther       : "<อื่นๆ>",\r
+DlgLnkURL                      : "ที่อยู่อ้างอิงออนไลน์ (URL)",\r
+DlgLnkAnchorSel                : "ระบุข้อมูลของจุดเชื่อมโยง (Anchor)",\r
+DlgLnkAnchorByName     : "ชื่อ",\r
+DlgLnkAnchorById       : "ไอดี",\r
+DlgLnkNoAnchors                : "(ยังไม่มีจุดเชื่อมโยงภายในหน้าเอกสารนี้)",               //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "อีเมล์ (E-Mail)",\r
+DlgLnkEMailSubject     : "หัวเรื่อง",\r
+DlgLnkEMailBody                : "ข้อความ",\r
+DlgLnkUpload           : "อัพโหลดไฟล์",\r
+DlgLnkBtnUpload                : "บันทึกไฟล์ไว้บนเซิร์ฟเวอร์",\r
+\r
+DlgLnkTarget           : "การเปิดหน้าลิงค์",\r
+DlgLnkTargetFrame      : "<เปิดในเฟรม>",\r
+DlgLnkTargetPopup      : "<เปิดหน้าจอเล็ก (Pop-up)>",\r
+DlgLnkTargetBlank      : "เปิดหน้าจอใหม่ (_blank)",\r
+DlgLnkTargetParent     : "เปิดในหน้าหลัก (_parent)",\r
+DlgLnkTargetSelf       : "เปิดในหน้าปัจจุบัน (_self)",\r
+DlgLnkTargetTop                : "เปิดในหน้าบนสุด (_top)",\r
+DlgLnkTargetFrameName  : "ชื่อทาร์เก็ตเฟรม",\r
+DlgLnkPopWinName       : "ระบุชื่อหน้าจอเล็ก (Pop-up)",\r
+DlgLnkPopWinFeat       : "คุณสมบัติของหน้าจอเล็ก (Pop-up)",\r
+DlgLnkPopResize                : "ปรับขนาดหน้าจอ",\r
+DlgLnkPopLocation      : "แสดงที่อยู่ของไฟล์",\r
+DlgLnkPopMenu          : "แสดงแถบเมนู",\r
+DlgLnkPopScroll                : "แสดงแถบเลื่อน",\r
+DlgLnkPopStatus                : "แสดงแถบสถานะ",\r
+DlgLnkPopToolbar       : "แสดงแถบเครื่องมือ",\r
+DlgLnkPopFullScrn      : "แสดงเต็มหน้าจอ (IE5.5++ เท่านั้น)",\r
+DlgLnkPopDependent     : "แสดงเต็มหน้าจอ (Netscape)",\r
+DlgLnkPopWidth         : "กว้าง",\r
+DlgLnkPopHeight                : "สูง",\r
+DlgLnkPopLeft          : "พิกัดซ้าย (Left Position)",\r
+DlgLnkPopTop           : "พิกัดบน (Top Position)",\r
+\r
+DlnLnkMsgNoUrl         : "กรุณาระบุที่อยู่อ้างอิงออนไลน์ (URL)",\r
+DlnLnkMsgNoEMail       : "กรุณาระบุอีเมล์ (E-mail)",\r
+DlnLnkMsgNoAnchor      : "กรุณาระบุจุดเชื่อมโยง (Anchor)",\r
+DlnLnkMsgInvPopName    : "ชื่อของหน้าต่างป๊อบอัพ จะต้องขึ้นต้นด้วยตัวอักษรเท่านั้น และต้องไม่มีช่องว่างในชื่อ",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "เลือกสี",\r
+DlgColorBtnClear       : "ล้างค่ารหัสสี",\r
+DlgColorHighlight      : "ตัวอย่างสี",\r
+DlgColorSelected       : "สีที่เลือก",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "แทรกสัญลักษณ์สื่ออารมณ์",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "แทรกตัวอักษรพิเศษ",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "คุณสมบัติของ ตาราง",\r
+DlgTableRows           : "แถว",\r
+DlgTableColumns                : "สดมน์",\r
+DlgTableBorder         : "ขนาดเส้นขอบ",\r
+DlgTableAlign          : "การจัดตำแหน่ง",\r
+DlgTableAlignNotSet    : "<ไม่ระบุ>",\r
+DlgTableAlignLeft      : "ชิดซ้าย",\r
+DlgTableAlignCenter    : "กึ่งกลาง",\r
+DlgTableAlignRight     : "ชิดขวา",\r
+DlgTableWidth          : "กว้าง",\r
+DlgTableWidthPx                : "จุดสี",\r
+DlgTableWidthPc                : "เปอร์เซ็น",\r
+DlgTableHeight         : "สูง",\r
+DlgTableCellSpace      : "ระยะแนวนอนน",\r
+DlgTableCellPad                : "ระยะแนวตั้ง",\r
+DlgTableCaption                : "หัวเรื่องของตาราง",\r
+DlgTableSummary                : "สรุปความ",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "คุณสมบัติของ ช่อง",\r
+DlgCellWidth           : "กว้าง",\r
+DlgCellWidthPx         : "จุดสี",\r
+DlgCellWidthPc         : "เปอร์เซ็น",\r
+DlgCellHeight          : "สูง",\r
+DlgCellWordWrap                : "ตัดบรรทัดอัตโนมัติ",\r
+DlgCellWordWrapNotSet  : "<ไม่ระบุ>",\r
+DlgCellWordWrapYes     : "ใ่ช่",\r
+DlgCellWordWrapNo      : "ไม่",\r
+DlgCellHorAlign                : "การจัดวางแนวนอน",\r
+DlgCellHorAlignNotSet  : "<ไม่ระบุ>",\r
+DlgCellHorAlignLeft    : "ชิดซ้าย",\r
+DlgCellHorAlignCenter  : "กึ่งกลาง",\r
+DlgCellHorAlignRight: "ชิดขวา",\r
+DlgCellVerAlign                : "การจัดวางแนวตั้ง",\r
+DlgCellVerAlignNotSet  : "<ไม่ระบุ>",\r
+DlgCellVerAlignTop     : "บนสุด",\r
+DlgCellVerAlignMiddle  : "กึ่งกลาง",\r
+DlgCellVerAlignBottom  : "ล่างสุด",\r
+DlgCellVerAlignBaseline        : "อิงบรรทัด",\r
+DlgCellRowSpan         : "จำนวนแถวที่คร่อมกัน",\r
+DlgCellCollSpan                : "จำนวนสดมน์ที่คร่อมกัน",\r
+DlgCellBackColor       : "สีพื้นหลัง",\r
+DlgCellBorderColor     : "สีเส้นขอบ",\r
+DlgCellBtnSelect       : "เลือก..",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "ค้นหา",\r
+DlgFindFindBtn         : "ค้นหา",\r
+DlgFindNotFoundMsg     : "ไม่พบคำที่ค้นหา.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "ค้นหาและแทนที่",\r
+DlgReplaceFindLbl              : "ค้นหาคำว่า:",\r
+DlgReplaceReplaceLbl   : "แทนที่ด้วย:",\r
+DlgReplaceCaseChk              : "ตัวโหญ่-เล็ก ต้องตรงกัน",\r
+DlgReplaceReplaceBtn   : "แทนที่",\r
+DlgReplaceReplAllBtn   : "แทนที่ทั้งหมดที่พบ",\r
+DlgReplaceWordChk              : "ต้องตรงกันทุกคำ",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "ไม่สามารถตัดข้อความที่เลือกไว้ได้เนื่องจากการกำหนดค่าระดับความปลอดภัย. กรุณาใช้ปุ่มลัดเพื่อวางข้อความแทน (กดปุ่ม Ctrl และตัว X พร้อมกัน).",\r
+PasteErrorCopy : "ไม่สามารถสำเนาข้อความที่เลือกไว้ได้เนื่องจากการกำหนดค่าระดับความปลอดภัย. กรุณาใช้ปุ่มลัดเพื่อวางข้อความแทน (กดปุ่ม Ctrl และตัว C พร้อมกัน).",\r
+\r
+PasteAsText            : "วางแบบตัวอักษรธรรมดา",\r
+PasteFromWord  : "วางแบบตัวอักษรจากโปรแกรมเวิร์ด",\r
+\r
+DlgPasteMsg2   : "กรุณาใช้คีย์บอร์ดเท่านั้น โดยกดปุ๋ม (<strong>Ctrl และ V</strong>)พร้อมๆกัน และกด <strong>OK</strong>.",\r
+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\r
+DlgPasteIgnoreFont             : "ไม่สนใจ Font Face definitions",\r
+DlgPasteRemoveStyles   : "ลบ Styles definitions",\r
+DlgPasteCleanBox               : "ล้างข้อมูลใน Box",\r
+\r
+// Color Picker\r
+ColorAutomatic : "สีอัตโนมัติ",\r
+ColorMoreColors        : "เลือกสีอื่นๆ...",\r
+\r
+// Document Properties\r
+DocProps               : "คุณสมบัติของเอกสาร",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "คุณสมบัติของ Anchor",\r
+DlgAnchorName          : "ชื่อ Anchor",\r
+DlgAnchorErrorName     : "กรุณาระบุชื่อของ Anchor",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "ไม่พบในดิกชันนารี",\r
+DlgSpellChangeTo               : "แก้ไขเป็น",\r
+DlgSpellBtnIgnore              : "ยกเว้น",\r
+DlgSpellBtnIgnoreAll   : "ยกเว้นทั้งหมด",\r
+DlgSpellBtnReplace             : "แทนที่",\r
+DlgSpellBtnReplaceAll  : "แทนที่ทั้งหมด",\r
+DlgSpellBtnUndo                        : "ยกเลิก",\r
+DlgSpellNoSuggestions  : "- ไม่มีคำแนะนำใดๆ -",\r
+DlgSpellProgress               : "กำลังตรวจสอบคำสะกด...",\r
+DlgSpellNoMispell              : "ตรวจสอบคำสะกดเสร็จสิ้น: ไม่พบคำสะกดผิด",\r
+DlgSpellNoChanges              : "ตรวจสอบคำสะกดเสร็จสิ้น: ไม่มีการแก้คำใดๆ",\r
+DlgSpellOneChange              : "ตรวจสอบคำสะกดเสร็จสิ้น: แก้ไข1คำ",\r
+DlgSpellManyChanges            : "ตรวจสอบคำสะกดเสร็จสิ้น:: แก้ไข %1 คำ",\r
+\r
+IeSpellDownload                        : "ไม่ได้ติดตั้งระบบตรวจสอบคำสะกด. ต้องการติดตั้งไหมครับ?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "ข้อความ (ค่าตัวแปร)",\r
+DlgButtonType          : "ข้อความ",\r
+DlgButtonTypeBtn       : "Button",\r
+DlgButtonTypeSbm       : "Submit",\r
+DlgButtonTypeRst       : "Reset",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "ชื่อ",\r
+DlgCheckboxValue       : "ค่าตัวแปร",\r
+DlgCheckboxSelected    : "เลือกเป็นค่าเริ่มต้น",\r
+\r
+// Form Dialog\r
+DlgFormName            : "ชื่อ",\r
+DlgFormAction  : "แอคชั่น",\r
+DlgFormMethod  : "เมธอด",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "ชื่อ",\r
+DlgSelectValue         : "ค่าตัวแปร",\r
+DlgSelectSize          : "ขนาด",\r
+DlgSelectLines         : "บรรทัด",\r
+DlgSelectChkMulti      : "เลือกหลายค่าได้",\r
+DlgSelectOpAvail       : "รายการตัวเลือก",\r
+DlgSelectOpText                : "ข้อความ",\r
+DlgSelectOpValue       : "ค่าตัวแปร",\r
+DlgSelectBtnAdd                : "เพิ่ม",\r
+DlgSelectBtnModify     : "แก้ไข",\r
+DlgSelectBtnUp         : "บน",\r
+DlgSelectBtnDown       : "ล่าง",\r
+DlgSelectBtnSetValue : "เลือกเป็นค่าเริ่มต้น",\r
+DlgSelectBtnDelete     : "ลบ",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "ชื่อ",\r
+DlgTextareaCols        : "สดมภ์",\r
+DlgTextareaRows        : "แถว",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "ชื่อ",\r
+DlgTextValue           : "ค่าตัวแปร",\r
+DlgTextCharWidth       : "ความกว้าง",\r
+DlgTextMaxChars                : "จำนวนตัวอักษรสูงสุด",\r
+DlgTextType                    : "ชนิด",\r
+DlgTextTypeText                : "ข้อความ",\r
+DlgTextTypePass                : "รหัสผ่าน",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "ชื่อ",\r
+DlgHiddenValue : "ค่าตัวแปร",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "คุณสมบัติของ บูลเล็ตลิสต์",\r
+NumberedListProp       : "คุณสมบัติของ นัมเบอร์ลิสต์",\r
+DlgLstStart                    : "Start",      //MISSING\r
+DlgLstType                     : "ชนิด",\r
+DlgLstTypeCircle       : "รูปวงกลม",\r
+DlgLstTypeDisc         : "Disc",       //MISSING\r
+DlgLstTypeSquare       : "รูปสี่เหลี่ยม",\r
+DlgLstTypeNumbers      : "หมายเลข (1, 2, 3)",\r
+DlgLstTypeLCase                : "ตัวพิมพ์เล็ก (a, b, c)",\r
+DlgLstTypeUCase                : "ตัวพิมพ์ใหญ่ (A, B, C)",\r
+DlgLstTypeSRoman       : "เลขโรมันพิมพ์เล็ก (i, ii, iii)",\r
+DlgLstTypeLRoman       : "เลขโรมันพิมพ์ใหญ่ (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "ลักษณะทั่วไปของเอกสาร",\r
+DlgDocBackTab          : "พื้นหลัง",\r
+DlgDocColorsTab                : "สีและระยะขอบ",\r
+DlgDocMetaTab          : "ข้อมูลสำหรับเสิร์ชเอนจิ้น",\r
+\r
+DlgDocPageTitle                : "ชื่อไตเติ้ล",\r
+DlgDocLangDir          : "การอ่านภาษา",\r
+DlgDocLangDirLTR       : "จากซ้ายไปขวา (LTR)",\r
+DlgDocLangDirRTL       : "จากขวาไปซ้าย (RTL)",\r
+DlgDocLangCode         : "รหัสภาษา",\r
+DlgDocCharSet          : "ชุดตัวอักษร",\r
+DlgDocCharSetCE                : "Central European",\r
+DlgDocCharSetCT                : "Chinese Traditional (Big5)",\r
+DlgDocCharSetCR                : "Cyrillic",\r
+DlgDocCharSetGR                : "Greek",\r
+DlgDocCharSetJP                : "Japanese",\r
+DlgDocCharSetKR                : "Korean",\r
+DlgDocCharSetTR                : "Turkish",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Western European",\r
+DlgDocCharSetOther     : "ชุดตัวอักษรอื่นๆ",\r
+\r
+DlgDocDocType          : "ประเภทของเอกสาร",\r
+DlgDocDocTypeOther     : "ประเภทเอกสารอื่นๆ",\r
+DlgDocIncXHTML         : "รวมเอา  XHTML Declarations ไว้ด้วย",\r
+DlgDocBgColor          : "สีพื้นหลัง",\r
+DlgDocBgImage          : "ที่อยู่อ้างอิงออนไลน์ของรูปพื้นหลัง (Image URL)",\r
+DlgDocBgNoScroll       : "พื้นหลังแบบไม่มีแถบเลื่อน",\r
+DlgDocCText                    : "ข้อความ",\r
+DlgDocCLink                    : "ลิงค์",\r
+DlgDocCVisited         : "ลิงค์ที่เคยคลิ้กแล้ว Visited Link",\r
+DlgDocCActive          : "ลิงค์ที่กำลังคลิ้ก Active Link",\r
+DlgDocMargins          : "ระยะขอบของหน้าเอกสาร",\r
+DlgDocMaTop                    : "ด้านบน",\r
+DlgDocMaLeft           : "ด้านซ้าย",\r
+DlgDocMaRight          : "ด้านขวา",\r
+DlgDocMaBottom         : "ด้านล่าง",\r
+DlgDocMeIndex          : "คำสำคัญอธิบายเอกสาร (คั่นคำด้วย คอมม่า)",\r
+DlgDocMeDescr          : "ประโยคอธิบายเกี่ยวกับเอกสาร",\r
+DlgDocMeAuthor         : "ผู้สร้างเอกสาร",\r
+DlgDocMeCopy           : "สงวนลิขสิทธิ์",\r
+DlgDocPreview          : "ตัวอย่างหน้าเอกสาร",\r
+\r
+// Templates Dialog\r
+Templates                      : "เทมเพลต",\r
+DlgTemplatesTitle      : "เทมเพลตของส่วนเนื้อหาเว็บไซต์",\r
+DlgTemplatesSelMsg     : "กรุณาเลือก เทมเพลต เพื่อนำไปแก้ไขในอีดิตเตอร์<br />(เนื้อหาส่วนนี้จะหายไป):",\r
+DlgTemplatesLoading    : "กำลังโหลดรายการเทมเพลตทั้งหมด...",\r
+DlgTemplatesNoTpl      : "(ยังไม่มีการกำหนดเทมเพลต)",\r
+DlgTemplatesReplace    : "แทนที่เนื้อหาเว็บไซต์ที่เลือก",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "เกี่ยวกับโปรแกรม",\r
+DlgAboutBrowserInfoTab : "โปรแกรมท่องเว็บที่ท่านใช้",\r
+DlgAboutLicenseTab     : "ลิขสิทธิ์",\r
+DlgAboutVersion                : "รุ่น",\r
+DlgAboutInfo           : "For further information go to"       //MISSING\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/tr.js b/httemplate/elements/fckeditor/editor/lang/tr.js
new file mode 100644 (file)
index 0000000..53b371e
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Turkish language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Araç Çubuğunu Kapat",\r
+ToolbarExpand          : "Araç Çubuğunu Aç",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Kaydet",\r
+NewPage                                : "Yeni Sayfa",\r
+Preview                                : "Ön İzleme",\r
+Cut                                    : "Kes",\r
+Copy                           : "Kopyala",\r
+Paste                          : "Yapıştır",\r
+PasteText                      : "Düzyazı Olarak Yapıştır",\r
+PasteWord                      : "Word'den Yapıştır",\r
+Print                          : "Yazdır",\r
+SelectAll                      : "Tümünü Seç",\r
+RemoveFormat           : "Biçimi Kaldır",\r
+InsertLinkLbl          : "Köprü",\r
+InsertLink                     : "Köprü Ekle/Düzenle",\r
+RemoveLink                     : "Köprü Kaldır",\r
+Anchor                         : "Çapa Ekle/Düzenle",\r
+InsertImageLbl         : "Resim",\r
+InsertImage                    : "Resim Ekle/Düzenle",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Flash Ekle/Düzenle",\r
+InsertTableLbl         : "Tablo",\r
+InsertTable                    : "Tablo Ekle/Düzenle",\r
+InsertLineLbl          : "Satır",\r
+InsertLine                     : "Yatay Satır Ekle",\r
+InsertSpecialCharLbl: "Özel Karakter",\r
+InsertSpecialChar      : "Özel Karakter Ekle",\r
+InsertSmileyLbl                : "İfade",\r
+InsertSmiley           : "İfade Ekle",\r
+About                          : "FCKeditor Hakkında",\r
+Bold                           : "Kalın",\r
+Italic                         : "İtalik",\r
+Underline                      : "Altı Çizgili",\r
+StrikeThrough          : "Üstü Çizgili",\r
+Subscript                      : "Alt Simge",\r
+Superscript                    : "Üst Simge",\r
+LeftJustify                    : "Sola Dayalı",\r
+CenterJustify          : "Ortalanmış",\r
+RightJustify           : "Sağa Dayalı",\r
+BlockJustify           : "İki Kenara Yaslanmış",\r
+DecreaseIndent         : "Sekme Azalt",\r
+IncreaseIndent         : "Sekme Arttır",\r
+Undo                           : "Geri Al",\r
+Redo                           : "Tekrarla",\r
+NumberedListLbl                : "Numaralı Liste",\r
+NumberedList           : "Numaralı Liste Ekle/Kaldır",\r
+BulletedListLbl                : "Simgeli Liste",\r
+BulletedList           : "Simgeli Liste Ekle/Kaldır",\r
+ShowTableBorders       : "Tablo Kenarlarını Göster",\r
+ShowDetails                    : "Detayları Göster",\r
+Style                          : "Biçem",\r
+FontFormat                     : "Biçim",\r
+Font                           : "Yazı Türü",\r
+FontSize                       : "Boyut",\r
+TextColor                      : "Yazı Rengi",\r
+BGColor                                : "Arka Renk",\r
+Source                         : "Kaynak",\r
+Find                           : "Bul",\r
+Replace                                : "Değiştir",\r
+SpellCheck                     : "Yazım Denetimi",\r
+UniversalKeyboard      : "Evrensel Klavye",\r
+PageBreakLbl           : "Sayfa sonu",\r
+PageBreak                      : "Sayfa Sonu Ekle",\r
+\r
+Form                   : "Form",\r
+Checkbox               : "Onay Kutusu",\r
+RadioButton            : "Seçenek Düğmesi",\r
+TextField              : "Metin Girişi",\r
+Textarea               : "Çok Satırlı Metin",\r
+HiddenField            : "Gizli Veri",\r
+Button                 : "Düğme",\r
+SelectionField : "Seçim Menüsü",\r
+ImageButton            : "Resimli Düğme",\r
+\r
+FitWindow              : "Düzenleyici boyutunu büyüt",\r
+\r
+// Context Menu\r
+EditLink                       : "Köprü Düzenle",\r
+CellCM                         : "Hücre",\r
+RowCM                          : "Satır",\r
+ColumnCM                       : "Sütun",\r
+InsertRow                      : "Satır Ekle",\r
+DeleteRows                     : "Satır Sil",\r
+InsertColumn           : "Sütun Ekle",\r
+DeleteColumns          : "Sütun Sil",\r
+InsertCell                     : "Hücre Ekle",\r
+DeleteCells                    : "Hücre Sil",\r
+MergeCells                     : "Hücreleri Birleştir",\r
+SplitCell                      : "Hücre Böl",\r
+TableDelete                    : "Tabloyu Sil",\r
+CellProperties         : "Hücre Özellikleri",\r
+TableProperties                : "Tablo Özellikleri",\r
+ImageProperties                : "Resim Özellikleri",\r
+FlashProperties                : "Flash Özellikleri",\r
+\r
+AnchorProp                     : "Çapa Özellikleri",\r
+ButtonProp                     : "Düğme Özellikleri",\r
+CheckboxProp           : "Onay Kutusu Özellikleri",\r
+HiddenFieldProp                : "Gizli Veri Özellikleri",\r
+RadioButtonProp                : "Seçenek Düğmesi Özellikleri",\r
+ImageButtonProp                : "Resimli Düğme Özellikleri",\r
+TextFieldProp          : "Metin Girişi Özellikleri",\r
+SelectionFieldProp     : "Seçim Menüsü Özellikleri",\r
+TextareaProp           : "Çok Satırlı Metin Özellikleri",\r
+FormProp                       : "Form Özellikleri",\r
+\r
+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)",             //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "XHTML işleniyor. Lütfen bekleyin...",\r
+Done                           : "Bitti",\r
+PasteWordConfirm       : "Yapıştırdığınız yazı Word'den gelmişe benziyor. Yapıştırmadan önce gereksiz eklentileri silmek ister misiniz?",\r
+NotCompatiblePaste     : "Bu komut Internet Explorer 5.5 ve ileriki sürümleri için mevcuttur. Temizlenmeden yapıştırılmasını ister misiniz ?",\r
+UnknownToolbarItem     : "Bilinmeyen araç çubugu öğesi \"%1\"",\r
+UnknownCommand         : "Bilinmeyen komut \"%1\"",\r
+NotImplemented         : "Komut uyarlanamadı",\r
+UnknownToolbarSet      : "\"%1\" araç çubuğu öğesi mevcut değil",\r
+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.",\r
+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)",\r
+DialogBlocked          : "Diyalog açmak mümkün olmadı. Tüm \"Popup Blocker\" programlarının devre dışı olduğundan emin olun.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "Tamam",\r
+DlgBtnCancel           : "İptal",\r
+DlgBtnClose                    : "Kapat",\r
+DlgBtnBrowseServer     : "Sunucuyu Gez",\r
+DlgAdvancedTag         : "Gelişmiş",\r
+DlgOpOther                     : "<Diğer>",\r
+DlgInfoTab                     : "Bilgi",\r
+DlgAlertUrl                    : "Lütfen URL girin",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<tanımlanmamış>",\r
+DlgGenId                       : "Kimlik",\r
+DlgGenLangDir          : "Dil Yönü",\r
+DlgGenLangDirLtr       : "Soldan Sağa (LTR)",\r
+DlgGenLangDirRtl       : "Sağdan Sola (RTL)",\r
+DlgGenLangCode         : "Dil Kodlaması",\r
+DlgGenAccessKey                : "Erişim Tuşu",\r
+DlgGenName                     : "Ad",\r
+DlgGenTabIndex         : "Sekme İndeksi",\r
+DlgGenLongDescr                : "Uzun Tanımlı URL",\r
+DlgGenClass                    : "Biçem Sayfası Sınıfları",\r
+DlgGenTitle                    : "Danışma Başlığı",\r
+DlgGenContType         : "Danışma İçerik Türü",\r
+DlgGenLinkCharset      : "Bağlı Kaynak Karakter Gurubu",\r
+DlgGenStyle                    : "Biçem",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Resim Özellikleri",\r
+DlgImgInfoTab          : "Resim Bilgisi",\r
+DlgImgBtnUpload                : "Sunucuya Yolla",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Karşıya Yükle",\r
+DlgImgAlt                      : "Alternatif Yazı",\r
+DlgImgWidth                    : "Genişlik",\r
+DlgImgHeight           : "Yükseklik",\r
+DlgImgLockRatio                : "Oranı Kilitle",\r
+DlgBtnResetSize                : "Boyutu Başa Döndür",\r
+DlgImgBorder           : "Kenar",\r
+DlgImgHSpace           : "Yatay Boşluk",\r
+DlgImgVSpace           : "Dikey Boşluk",\r
+DlgImgAlign                    : "Hizalama",\r
+DlgImgAlignLeft                : "Sol",\r
+DlgImgAlignAbsBottom: "Tam Altı",\r
+DlgImgAlignAbsMiddle: "Tam Ortası",\r
+DlgImgAlignBaseline    : "Taban Çizgisi",\r
+DlgImgAlignBottom      : "Alt",\r
+DlgImgAlignMiddle      : "Orta",\r
+DlgImgAlignRight       : "Sağ",\r
+DlgImgAlignTextTop     : "Yazı Tepeye",\r
+DlgImgAlignTop         : "Tepe",\r
+DlgImgPreview          : "Ön İzleme",\r
+DlgImgAlertUrl         : "Lütfen resmin URL'sini yazınız",\r
+DlgImgLinkTab          : "Köprü",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash Özellikleri",\r
+DlgFlashChkPlay                : "Otomatik Oynat",\r
+DlgFlashChkLoop                : "Döngü",\r
+DlgFlashChkMenu                : "Flash Menüsünü Kullan",\r
+DlgFlashScale          : "Boyutlandır",\r
+DlgFlashScaleAll       : "Hepsini Göster",\r
+DlgFlashScaleNoBorder  : "Kenar Yok",\r
+DlgFlashScaleFit       : "Tam Sığdır",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Köprü",\r
+DlgLnkInfoTab          : "Köprü Bilgisi",\r
+DlgLnkTargetTab                : "Hedef",\r
+\r
+DlgLnkType                     : "Köprü Türü",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Bu sayfada çapa",\r
+DlgLnkTypeEMail                : "E-Posta",\r
+DlgLnkProto                    : "Protokol",\r
+DlgLnkProtoOther       : "<diğer>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Çapa Seç",\r
+DlgLnkAnchorByName     : "Çapa Adı ile",\r
+DlgLnkAnchorById       : "Eleman Kimlik Numarası ile",\r
+DlgLnkNoAnchors                : "<Bu belgede hiç çapa yok>",                //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "E-Posta Adresi",\r
+DlgLnkEMailSubject     : "İleti Konusu",\r
+DlgLnkEMailBody                : "İleti Gövdesi",\r
+DlgLnkUpload           : "Karşıya Yükle",\r
+DlgLnkBtnUpload                : "Sunucuya Gönder",\r
+\r
+DlgLnkTarget           : "Hedef",\r
+DlgLnkTargetFrame      : "<çerçeve>",\r
+DlgLnkTargetPopup      : "<yeni açılan pencere>",\r
+DlgLnkTargetBlank      : "Yeni Pencere(_blank)",\r
+DlgLnkTargetParent     : "Anne Pencere (_parent)",\r
+DlgLnkTargetSelf       : "Kendi Penceresi (_self)",\r
+DlgLnkTargetTop                : "En Üst Pencere (_top)",\r
+DlgLnkTargetFrameName  : "Hedef Çerçeve Adı",\r
+DlgLnkPopWinName       : "Yeni Açılan Pencere Adı",\r
+DlgLnkPopWinFeat       : "Yeni Açılan Pencere Özellikleri",\r
+DlgLnkPopResize                : "Boyutlandırılabilir",\r
+DlgLnkPopLocation      : "Yer Çubuğu",\r
+DlgLnkPopMenu          : "Menü Çubuğu",\r
+DlgLnkPopScroll                : "Kaydırma Çubukları",\r
+DlgLnkPopStatus                : "Durum Çubuğu",\r
+DlgLnkPopToolbar       : "Araç Çubuğu",\r
+DlgLnkPopFullScrn      : "Tam Ekran (IE)",\r
+DlgLnkPopDependent     : "Bağımlı (Netscape)",\r
+DlgLnkPopWidth         : "Genişlik",\r
+DlgLnkPopHeight                : "Yükseklik",\r
+DlgLnkPopLeft          : "Sola Göre Konum",\r
+DlgLnkPopTop           : "Yukarıya Göre Konum",\r
+\r
+DlnLnkMsgNoUrl         : "Lütfen köprü URL'sini yazın",\r
+DlnLnkMsgNoEMail       : "Lütfen E-posta adresini yazın",\r
+DlnLnkMsgNoAnchor      : "Lütfen bir çapa seçin",\r
+DlnLnkMsgInvPopName    : "Açılır pencere adı abecesel bir karakterle başlamalı ve boşluk içermemelidir",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Renk Seç",\r
+DlgColorBtnClear       : "Temizle",\r
+DlgColorHighlight      : "Vurgula",\r
+DlgColorSelected       : "Seçilmiş",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "İfade Ekle",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Özel Karakter Seç",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Tablo Özellikleri",\r
+DlgTableRows           : "Satırlar",\r
+DlgTableColumns                : "Sütunlar",\r
+DlgTableBorder         : "Kenar Kalınlığı",\r
+DlgTableAlign          : "Hizalama",\r
+DlgTableAlignNotSet    : "<Tanımlanmamış>",\r
+DlgTableAlignLeft      : "Sol",\r
+DlgTableAlignCenter    : "Merkez",\r
+DlgTableAlignRight     : "Sağ",\r
+DlgTableWidth          : "Genişlik",\r
+DlgTableWidthPx                : "piksel",\r
+DlgTableWidthPc                : "yüzde",\r
+DlgTableHeight         : "Yükseklik",\r
+DlgTableCellSpace      : "Izgara kalınlığı",\r
+DlgTableCellPad                : "Izgara yazı arası",\r
+DlgTableCaption                : "Başlık",\r
+DlgTableSummary                : "Özet",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Hücre Özellikleri",\r
+DlgCellWidth           : "Genişlik",\r
+DlgCellWidthPx         : "piksel",\r
+DlgCellWidthPc         : "yüzde",\r
+DlgCellHeight          : "Yükseklik",\r
+DlgCellWordWrap                : "Sözcük Kaydır",\r
+DlgCellWordWrapNotSet  : "<Tanımlanmamış>",\r
+DlgCellWordWrapYes     : "Evet",\r
+DlgCellWordWrapNo      : "Hayır",\r
+DlgCellHorAlign                : "Yatay Hizalama",\r
+DlgCellHorAlignNotSet  : "<Tanımlanmamış>",\r
+DlgCellHorAlignLeft    : "Sol",\r
+DlgCellHorAlignCenter  : "Merkez",\r
+DlgCellHorAlignRight: "Sağ",\r
+DlgCellVerAlign                : "Dikey Hizalama",\r
+DlgCellVerAlignNotSet  : "<Tanımlanmamış>",\r
+DlgCellVerAlignTop     : "Tepe",\r
+DlgCellVerAlignMiddle  : "Orta",\r
+DlgCellVerAlignBottom  : "Alt",\r
+DlgCellVerAlignBaseline        : "Taban Çizgisi",\r
+DlgCellRowSpan         : "Satır Kapla",\r
+DlgCellCollSpan                : "Sütun Kapla",\r
+DlgCellBackColor       : "Arka Plan Rengi",\r
+DlgCellBorderColor     : "Kenar Rengi",\r
+DlgCellBtnSelect       : "Seç...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Bul",\r
+DlgFindFindBtn         : "Bul",\r
+DlgFindNotFoundMsg     : "Belirtilen yazı bulunamadı.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Değiştir",\r
+DlgReplaceFindLbl              : "Aranan:",\r
+DlgReplaceReplaceLbl   : "Bununla değiştir:",\r
+DlgReplaceCaseChk              : "Büyük/küçük harf duyarlı",\r
+DlgReplaceReplaceBtn   : "Değiştir",\r
+DlgReplaceReplAllBtn   : "Tümünü Değiştir",\r
+DlgReplaceWordChk              : "Kelimenin tamamı uysun",\r
+\r
+// Paste Operations / Dialog\r
+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.",\r
+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.",\r
+\r
+PasteAsText            : "Düz Metin Olarak Yapıştır",\r
+PasteFromWord  : "Word'den yapıştır",\r
+\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.",\r
+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\r
+DlgPasteIgnoreFont             : "Yazı Tipi tanımlarını yoksay",\r
+DlgPasteRemoveStyles   : "Biçem Tanımlarını çıkar",\r
+DlgPasteCleanBox               : "Temizlik Kutusu",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Otomatik",\r
+ColorMoreColors        : "Diğer renkler...",\r
+\r
+// Document Properties\r
+DocProps               : "Belge Özellikleri",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Çapa Özellikleri",\r
+DlgAnchorName          : "Çapa Adı",\r
+DlgAnchorErrorName     : "Lütfen çapa için ad giriniz",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Sözlükte Yok",\r
+DlgSpellChangeTo               : "Şuna değiştir:",\r
+DlgSpellBtnIgnore              : "Yoksay",\r
+DlgSpellBtnIgnoreAll   : "Tümünü Yoksay",\r
+DlgSpellBtnReplace             : "Değiştir",\r
+DlgSpellBtnReplaceAll  : "Tümünü Değiştir",\r
+DlgSpellBtnUndo                        : "Geri Al",\r
+DlgSpellNoSuggestions  : "- Öneri Yok -",\r
+DlgSpellProgress               : "Yazım denetimi işlemde...",\r
+DlgSpellNoMispell              : "Yazım denetimi tamamlandı: Yanlış yazıma rastlanmadı",\r
+DlgSpellNoChanges              : "Yazım denetimi tamamlandı: Hiçbir kelime değiştirilmedi",\r
+DlgSpellOneChange              : "Yazım denetimi tamamlandı: Bir kelime değiştirildi",\r
+DlgSpellManyChanges            : "Yazım denetimi tamamlandı: %1 kelime değiştirildi",\r
+\r
+IeSpellDownload                        : "Yazım denetimi yüklenmemiş. Şimdi yüklemek ister misiniz?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Metin (Değer)",\r
+DlgButtonType          : "Tip",\r
+DlgButtonTypeBtn       : "Düğme",\r
+DlgButtonTypeSbm       : "Gönder",\r
+DlgButtonTypeRst       : "Sıfırla",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Ad",\r
+DlgCheckboxValue       : "Değer",\r
+DlgCheckboxSelected    : "Seçili",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Ad",\r
+DlgFormAction  : "İşlem",\r
+DlgFormMethod  : "Yöntem",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Ad",\r
+DlgSelectValue         : "Değer",\r
+DlgSelectSize          : "Boyut",\r
+DlgSelectLines         : "satır",\r
+DlgSelectChkMulti      : "Çoklu seçime izin ver",\r
+DlgSelectOpAvail       : "Mevcut Seçenekler",\r
+DlgSelectOpText                : "Metin",\r
+DlgSelectOpValue       : "Değer",\r
+DlgSelectBtnAdd                : "Ekle",\r
+DlgSelectBtnModify     : "Düzenle",\r
+DlgSelectBtnUp         : "Yukarı",\r
+DlgSelectBtnDown       : "Aşağı",\r
+DlgSelectBtnSetValue : "Seçili değer olarak ata",\r
+DlgSelectBtnDelete     : "Sil",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Ad",\r
+DlgTextareaCols        : "Sütunlar",\r
+DlgTextareaRows        : "Satırlar",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Ad",\r
+DlgTextValue           : "Değer",\r
+DlgTextCharWidth       : "Karakter Genişliği",\r
+DlgTextMaxChars                : "En Fazla Karakter",\r
+DlgTextType                    : "Tür",\r
+DlgTextTypeText                : "Metin",\r
+DlgTextTypePass                : "Parola",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Ad",\r
+DlgHiddenValue : "Değer",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Simgeli Liste Özellikleri",\r
+NumberedListProp       : "Numaralı Liste Özellikleri",\r
+DlgLstStart                    : "Başlangıç",\r
+DlgLstType                     : "Tip",\r
+DlgLstTypeCircle       : "Çember",\r
+DlgLstTypeDisc         : "Disk",\r
+DlgLstTypeSquare       : "Kare",\r
+DlgLstTypeNumbers      : "Sayılar (1, 2, 3)",\r
+DlgLstTypeLCase                : "Küçük Harfler (a, b, c)",\r
+DlgLstTypeUCase                : "Büyük Harfler (A, B, C)",\r
+DlgLstTypeSRoman       : "Küçük Romen Rakamları (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Büyük Romen Rakamları (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Genel",\r
+DlgDocBackTab          : "Arka Plan",\r
+DlgDocColorsTab                : "Renkler ve Kenar Boşlukları",\r
+DlgDocMetaTab          : "Tanım Bilgisi (Meta)",\r
+\r
+DlgDocPageTitle                : "Sayfa Başlığı",\r
+DlgDocLangDir          : "Dil Yönü",\r
+DlgDocLangDirLTR       : "Soldan Sağa (LTR)",\r
+DlgDocLangDirRTL       : "Sağdan Sola (RTL)",\r
+DlgDocLangCode         : "Dil Kodu",\r
+DlgDocCharSet          : "Karakter Kümesi Kodlaması",\r
+DlgDocCharSetCE                : "Orta Avrupa",\r
+DlgDocCharSetCT                : "Geleneksel Çince (Big5)",\r
+DlgDocCharSetCR                : "Kiril",\r
+DlgDocCharSetGR                : "Yunanca",\r
+DlgDocCharSetJP                : "Japonca",\r
+DlgDocCharSetKR                : "Korece",\r
+DlgDocCharSetTR                : "Türkçe",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Batı Avrupa",\r
+DlgDocCharSetOther     : "Diğer Karakter Kümesi Kodlaması",\r
+\r
+DlgDocDocType          : "Belge Türü Başlığı",\r
+DlgDocDocTypeOther     : "Diğer Belge Türü Başlığı",\r
+DlgDocIncXHTML         : "XHTML Bildirimlerini Dahil Et",\r
+DlgDocBgColor          : "Arka Plan Rengi",\r
+DlgDocBgImage          : "Arka Plan Resim URLsi",\r
+DlgDocBgNoScroll       : "Sabit Arka Plan",\r
+DlgDocCText                    : "Metin",\r
+DlgDocCLink                    : "Köprü",\r
+DlgDocCVisited         : "Ziyaret Edilmiş Köprü",\r
+DlgDocCActive          : "Etkin Köprü",\r
+DlgDocMargins          : "Kenar Boşlukları",\r
+DlgDocMaTop                    : "Tepe",\r
+DlgDocMaLeft           : "Sol",\r
+DlgDocMaRight          : "Sağ",\r
+DlgDocMaBottom         : "Alt",\r
+DlgDocMeIndex          : "Belge Dizinleme Anahtar Kelimeleri (virgülle ayrılmış)",\r
+DlgDocMeDescr          : "Belge Tanımı",\r
+DlgDocMeAuthor         : "Yazar",\r
+DlgDocMeCopy           : "Telif",\r
+DlgDocPreview          : "Ön İzleme",\r
+\r
+// Templates Dialog\r
+Templates                      : "Şablonlar",\r
+DlgTemplatesTitle      : "İçerik Şablonları",\r
+DlgTemplatesSelMsg     : "Düzenleyicide açmak için lütfen bir şablon seçin.<br>(hali hazırdaki içerik kaybolacaktır.):",\r
+DlgTemplatesLoading    : "Şablon listesi yüklenmekte. Lütfen bekleyiniz...",\r
+DlgTemplatesNoTpl      : "(Belirli bir şablon seçilmedi)",\r
+DlgTemplatesReplace    : "Mevcut içerik ile değiştir",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Hakkında",\r
+DlgAboutBrowserInfoTab : "Gezgin Bilgisi",\r
+DlgAboutLicenseTab     : "Lisans",\r
+DlgAboutVersion                : "sürüm",\r
+DlgAboutInfo           : "Daha fazla bilgi için:"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/uk.js b/httemplate/elements/fckeditor/editor/lang/uk.js
new file mode 100644 (file)
index 0000000..1defaac
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Ukrainian language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Згорнути панель інструментів",\r
+ToolbarExpand          : "Розгорнути панель інструментів",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Зберегти",\r
+NewPage                                : "Нова сторінка",\r
+Preview                                : "Попередній перегляд",\r
+Cut                                    : "Вирізати",\r
+Copy                           : "Копіювати",\r
+Paste                          : "Вставити",\r
+PasteText                      : "Вставити тільки текст",\r
+PasteWord                      : "Вставити з Word",\r
+Print                          : "Друк",\r
+SelectAll                      : "Виділити все",\r
+RemoveFormat           : "Прибрати форматування",\r
+InsertLinkLbl          : "Посилання",\r
+InsertLink                     : "Вставити/Редагувати посилання",\r
+RemoveLink                     : "Знищити посилання",\r
+Anchor                         : "Вставити/Редагувати якір",\r
+InsertImageLbl         : "Зображення",\r
+InsertImage                    : "Вставити/Редагувати зображення",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Вставити/Редагувати Flash",\r
+InsertTableLbl         : "Таблиця",\r
+InsertTable                    : "Вставити/Редагувати таблицю",\r
+InsertLineLbl          : "Лінія",\r
+InsertLine                     : "Вставити горизонтальну лінію",\r
+InsertSpecialCharLbl: "Спеціальний символ",\r
+InsertSpecialChar      : "Вставити спеціальний символ",\r
+InsertSmileyLbl                : "Смайлик",\r
+InsertSmiley           : "Вставити смайлик",\r
+About                          : "Про FCKeditor",\r
+Bold                           : "Жирний",\r
+Italic                         : "Курсив",\r
+Underline                      : "Підкреслений",\r
+StrikeThrough          : "Закреслений",\r
+Subscript                      : "Підрядковий індекс",\r
+Superscript                    : "Надрядковий индекс",\r
+LeftJustify                    : "По лівому краю",\r
+CenterJustify          : "По центру",\r
+RightJustify           : "По правому краю",\r
+BlockJustify           : "По ширині",\r
+DecreaseIndent         : "Зменшити відступ",\r
+IncreaseIndent         : "Збільшити відступ",\r
+Undo                           : "Повернути",\r
+Redo                           : "Повторити",\r
+NumberedListLbl                : "Нумерований список",\r
+NumberedList           : "Вставити/Видалити нумерований список",\r
+BulletedListLbl                : "Маркований список",\r
+BulletedList           : "Вставити/Видалити маркований список",\r
+ShowTableBorders       : "Показати бордюри таблиці",\r
+ShowDetails                    : "Показати деталі",\r
+Style                          : "Стиль",\r
+FontFormat                     : "Форматування",\r
+Font                           : "Шрифт",\r
+FontSize                       : "Розмір",\r
+TextColor                      : "Колір тексту",\r
+BGColor                                : "Колір фону",\r
+Source                         : "Джерело",\r
+Find                           : "Пошук",\r
+Replace                                : "Заміна",\r
+SpellCheck                     : "Перевірити орфографію",\r
+UniversalKeyboard      : "Універсальна клавіатура",\r
+PageBreakLbl           : "Розривши сторінки",\r
+PageBreak                      : "Вставити розривши сторінки",\r
+\r
+Form                   : "Форма",\r
+Checkbox               : "Флагова кнопка",\r
+RadioButton            : "Кнопка вибору",\r
+TextField              : "Текстове поле",\r
+Textarea               : "Текстова область",\r
+HiddenField            : "Приховане поле",\r
+Button                 : "Кнопка",\r
+SelectionField : "Список",\r
+ImageButton            : "Кнопка із зображенням",\r
+\r
+FitWindow              : "Розвернути вікно редактора",\r
+\r
+// Context Menu\r
+EditLink                       : "Вставити посилання",\r
+CellCM                         : "Осередок",\r
+RowCM                          : "Рядок",\r
+ColumnCM                       : "Колонка",\r
+InsertRow                      : "Вставити строку",\r
+DeleteRows                     : "Видалити строки",\r
+InsertColumn           : "Вставити колонку",\r
+DeleteColumns          : "Видалити колонки",\r
+InsertCell                     : "Вставити комірку",\r
+DeleteCells                    : "Видалити комірки",\r
+MergeCells                     : "Об'єднати комірки",\r
+SplitCell                      : "Роз'єднати комірку",\r
+TableDelete                    : "Видалити таблицю",\r
+CellProperties         : "Властивості комірки",\r
+TableProperties                : "Властивості таблиці",\r
+ImageProperties                : "Властивості зображення",\r
+FlashProperties                : "Властивості Flash",\r
+\r
+AnchorProp                     : "Властивості якоря",\r
+ButtonProp                     : "Властивості кнопки",\r
+CheckboxProp           : "Властивості флагової кнопки",\r
+HiddenFieldProp                : "Властивості прихованого поля",\r
+RadioButtonProp                : "Властивості кнопки вибору",\r
+ImageButtonProp                : "Властивості кнопки із зображенням",\r
+TextFieldProp          : "Властивості текстового поля",\r
+SelectionFieldProp     : "Властивості списку",\r
+TextareaProp           : "Властивості текстової області",\r
+FormProp                       : "Властивості форми",\r
+\r
+FontFormats                    : "Нормальний;Форматований;Адреса;Заголовок 1;Заголовок 2;Заголовок 3;Заголовок 4;Заголовок 5;Заголовок 6;Нормальний (DIV)",                //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Обробка XHTML. Зачекайте, будь ласка...",\r
+Done                           : "Зроблено",\r
+PasteWordConfirm       : "Текст, що ви хочете вставити, схожий на копійований з Word. Ви хочете очистити його перед вставкою?",\r
+NotCompatiblePaste     : "Ця команда доступна для Internet Explorer версії 5.5 або вище. Ви хочете вставити без очищення?",\r
+UnknownToolbarItem     : "Невідомий елемент панелі інструментів \"%1\"",\r
+UnknownCommand         : "Невідоме ім'я команди \"%1\"",\r
+NotImplemented         : "Команда не реалізована",\r
+UnknownToolbarSet      : "Панель інструментів \"%1\" не існує",\r
+NoActiveX                      : "Настройки безпеки вашого браузера можуть обмежувати деякі властивості редактора. Ви повинні включити опцію \"Запускати елементи управління ACTIVEX і плугіни\". Ви можете бачити помилки і помічати відсутність можливостей.",\r
+BrowseServerBlocked : "Ресурси браузера не можуть бути відкриті. Перевірте що блокування спливаючих вікон вимкнені.",\r
+DialogBlocked          : "Не можливо відкрити вікно діалогу. Перевірте що блокування спливаючих вікон вимкнені.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "ОК",\r
+DlgBtnCancel           : "Скасувати",\r
+DlgBtnClose                    : "Зачинити",\r
+DlgBtnBrowseServer     : "Передивитися на сервері",\r
+DlgAdvancedTag         : "Розширений",\r
+DlgOpOther                     : "<Інше>",\r
+DlgInfoTab                     : "Інфо",\r
+DlgAlertUrl                    : "Вставте, будь-ласка, URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<не визначено>",\r
+DlgGenId                       : "Ідентифікатор",\r
+DlgGenLangDir          : "Напрямок мови",\r
+DlgGenLangDirLtr       : "Зліва на право (LTR)",\r
+DlgGenLangDirRtl       : "Зправа на ліво (RTL)",\r
+DlgGenLangCode         : "Мова",\r
+DlgGenAccessKey                : "Гаряча клавіша",\r
+DlgGenName                     : "Им'я",\r
+DlgGenTabIndex         : "Послідовність переходу",\r
+DlgGenLongDescr                : "Довгий опис URL",\r
+DlgGenClass                    : "Клас CSS",\r
+DlgGenTitle                    : "Заголовок",\r
+DlgGenContType         : "Тип вмісту",\r
+DlgGenLinkCharset      : "Кодировка",\r
+DlgGenStyle                    : "Стиль CSS",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Властивості зображення",\r
+DlgImgInfoTab          : "Інформація про изображении",\r
+DlgImgBtnUpload                : "Надіслати на сервер",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Закачати",\r
+DlgImgAlt                      : "Альтернативний текст",\r
+DlgImgWidth                    : "Ширина",\r
+DlgImgHeight           : "Висота",\r
+DlgImgLockRatio                : "Зберегти пропорції",\r
+DlgBtnResetSize                : "Скинути розмір",\r
+DlgImgBorder           : "Бордюр",\r
+DlgImgHSpace           : "Горизонтальний відступ",\r
+DlgImgVSpace           : "Вертикальний відступ",\r
+DlgImgAlign                    : "Вирівнювання",\r
+DlgImgAlignLeft                : "По лівому краю",\r
+DlgImgAlignAbsBottom: "Абс по низу",\r
+DlgImgAlignAbsMiddle: "Абс по середині",\r
+DlgImgAlignBaseline    : "По базовій лінії",\r
+DlgImgAlignBottom      : "По низу",\r
+DlgImgAlignMiddle      : "По середині",\r
+DlgImgAlignRight       : "По правому краю",\r
+DlgImgAlignTextTop     : "Текст на верху",\r
+DlgImgAlignTop         : "По верху",\r
+DlgImgPreview          : "Попередній перегляд",\r
+DlgImgAlertUrl         : "Будь ласка, введіть URL зображення",\r
+DlgImgLinkTab          : "Посилання",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Властивості Flash",\r
+DlgFlashChkPlay                : "Авто програвання",\r
+DlgFlashChkLoop                : "Зациклити",\r
+DlgFlashChkMenu                : "Дозволити меню Flash",\r
+DlgFlashScale          : "Масштаб",\r
+DlgFlashScaleAll       : "Показати всі",\r
+DlgFlashScaleNoBorder  : "Без рамки",\r
+DlgFlashScaleFit       : "Дійсний розмір",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Посилання",\r
+DlgLnkInfoTab          : "Інформація посилання",\r
+DlgLnkTargetTab                : "Ціль",\r
+\r
+DlgLnkType                     : "Тип посилання",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Якір на цю сторінку",\r
+DlgLnkTypeEMail                : "Эл. пошта",\r
+DlgLnkProto                    : "Протокол",\r
+DlgLnkProtoOther       : "<інше>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Оберіть якір",\r
+DlgLnkAnchorByName     : "За ім'ям якоря",\r
+DlgLnkAnchorById       : "За ідентифікатором елемента",\r
+DlgLnkNoAnchors                : "<Немає якорів доступних в цьому документі>",              //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Адреса ел. пошти",\r
+DlgLnkEMailSubject     : "Тема листа",\r
+DlgLnkEMailBody                : "Тіло повідомлення",\r
+DlgLnkUpload           : "Закачати",\r
+DlgLnkBtnUpload                : "Переслати на сервер",\r
+\r
+DlgLnkTarget           : "Ціль",\r
+DlgLnkTargetFrame      : "<фрейм>",\r
+DlgLnkTargetPopup      : "<спливаюче вікно>",\r
+DlgLnkTargetBlank      : "Нове вікно (_blank)",\r
+DlgLnkTargetParent     : "Батьківське вікно (_parent)",\r
+DlgLnkTargetSelf       : "Теж вікно (_self)",\r
+DlgLnkTargetTop                : "Найвище вікно (_top)",\r
+DlgLnkTargetFrameName  : "Ім'я целевого фрейма",\r
+DlgLnkPopWinName       : "Ім'я спливаючого вікна",\r
+DlgLnkPopWinFeat       : "Властивості спливаючого вікна",\r
+DlgLnkPopResize                : "Змінюється в розмірах",\r
+DlgLnkPopLocation      : "Панель локації",\r
+DlgLnkPopMenu          : "Панель меню",\r
+DlgLnkPopScroll                : "Полоси прокрутки",\r
+DlgLnkPopStatus                : "Строка статусу",\r
+DlgLnkPopToolbar       : "Панель інструментів",\r
+DlgLnkPopFullScrn      : "Повний екран (IE)",\r
+DlgLnkPopDependent     : "Залежний (Netscape)",\r
+DlgLnkPopWidth         : "Ширина",\r
+DlgLnkPopHeight                : "Висота",\r
+DlgLnkPopLeft          : "Позиція зліва",\r
+DlgLnkPopTop           : "Позиція зверху",\r
+\r
+DlnLnkMsgNoUrl         : "Будь ласка, занесіть URL посилання",\r
+DlnLnkMsgNoEMail       : "Будь ласка, занесіть адрес эл. почты",\r
+DlnLnkMsgNoAnchor      : "Будь ласка, оберіть якір",\r
+DlnLnkMsgInvPopName    : "Назва спливаючого вікна повинна починатися букви і не може містити пропусків",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Оберіть колір",\r
+DlgColorBtnClear       : "Очистити",\r
+DlgColorHighlight      : "Підсвічений",\r
+DlgColorSelected       : "Обраний",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Вставити смайлик",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Оберіть спеціальний символ",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Властивості таблиці",\r
+DlgTableRows           : "Строки",\r
+DlgTableColumns                : "Колонки",\r
+DlgTableBorder         : "Розмір бордюра",\r
+DlgTableAlign          : "Вирівнювання",\r
+DlgTableAlignNotSet    : "<Не вст.>",\r
+DlgTableAlignLeft      : "Зліва",\r
+DlgTableAlignCenter    : "По центру",\r
+DlgTableAlignRight     : "Зправа",\r
+DlgTableWidth          : "Ширина",\r
+DlgTableWidthPx                : "пікселів",\r
+DlgTableWidthPc                : "відсотків",\r
+DlgTableHeight         : "Висота",\r
+DlgTableCellSpace      : "Проміжок (spacing)",\r
+DlgTableCellPad                : "Відступ (padding)",\r
+DlgTableCaption                : "Заголовок",\r
+DlgTableSummary                : "Резюме",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Властивості комірки",\r
+DlgCellWidth           : "Ширина",\r
+DlgCellWidthPx         : "пікселів",\r
+DlgCellWidthPc         : "відсотків",\r
+DlgCellHeight          : "Висота",\r
+DlgCellWordWrap                : "Згортання текста",\r
+DlgCellWordWrapNotSet  : "<Не вст.>",\r
+DlgCellWordWrapYes     : "Так",\r
+DlgCellWordWrapNo      : "Ні",\r
+DlgCellHorAlign                : "Горизонтальне вирівнювання",\r
+DlgCellHorAlignNotSet  : "<Не вст.>",\r
+DlgCellHorAlignLeft    : "Зліва",\r
+DlgCellHorAlignCenter  : "По центру",\r
+DlgCellHorAlignRight: "Зправа",\r
+DlgCellVerAlign                : "Вертикальное вирівнювання",\r
+DlgCellVerAlignNotSet  : "<Не вст.>",\r
+DlgCellVerAlignTop     : "Зверху",\r
+DlgCellVerAlignMiddle  : "Посередині",\r
+DlgCellVerAlignBottom  : "Знизу",\r
+DlgCellVerAlignBaseline        : "По базовій лінії",\r
+DlgCellRowSpan         : "Діапазон строк (span)",\r
+DlgCellCollSpan                : "Діапазон колонок (span)",\r
+DlgCellBackColor       : "Колір фона",\r
+DlgCellBorderColor     : "Колір бордюра",\r
+DlgCellBtnSelect       : "Оберіть...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Пошук",\r
+DlgFindFindBtn         : "Пошук",\r
+DlgFindNotFoundMsg     : "Вказаний текст не знайдений.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Замінити",\r
+DlgReplaceFindLbl              : "Шукати:",\r
+DlgReplaceReplaceLbl   : "Замінити на:",\r
+DlgReplaceCaseChk              : "Учитывать регистр",\r
+DlgReplaceReplaceBtn   : "Замінити",\r
+DlgReplaceReplAllBtn   : "Замінити все",\r
+DlgReplaceWordChk              : "Збіг цілих слів",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "Настройки безпеки вашого браузера не дозволяють редактору автоматично виконувати операції вирізування. Будь ласка, використовуйте клавіатуру для цього (Ctrl+X).",\r
+PasteErrorCopy : "Настройки безпеки вашого браузера не дозволяють редактору автоматично виконувати операції копіювання. Будь ласка, використовуйте клавіатуру для цього (Ctrl+C).",\r
+\r
+PasteAsText            : "Вставити тільки текст",\r
+PasteFromWord  : "Вставити з Word",\r
+\r
+DlgPasteMsg2   : "Будь-ласка, вставте з буфера обміну в цю область, користуючись комбінацією клавіш (<STRONG>Ctrl+V</STRONG>) та натисніть <STRONG>OK</STRONG>.",\r
+DlgPasteSec            : "Редактор не може отримати прямий доступ до буферу обміну у зв'язку з налаштуваннями вашого браузера. Вам потрібно вставити інформацію повторно в це вікно.",\r
+DlgPasteIgnoreFont             : "Ігнорувати налаштування шрифтів",\r
+DlgPasteRemoveStyles   : "Видалити налаштування стилів",\r
+DlgPasteCleanBox               : "Очистити область",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Автоматичний",\r
+ColorMoreColors        : "Кольори...",\r
+\r
+// Document Properties\r
+DocProps               : "Властивості документа",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Властивості якоря",\r
+DlgAnchorName          : "Ім'я якоря",\r
+DlgAnchorErrorName     : "Будь ласка, занесіть ім'я якоря",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Не має в словнику",\r
+DlgSpellChangeTo               : "Замінити на",\r
+DlgSpellBtnIgnore              : "Ігнорувати",\r
+DlgSpellBtnIgnoreAll   : "Ігнорувати все",\r
+DlgSpellBtnReplace             : "Замінити",\r
+DlgSpellBtnReplaceAll  : "Замінити все",\r
+DlgSpellBtnUndo                        : "Назад",\r
+DlgSpellNoSuggestions  : "- Немає припущень -",\r
+DlgSpellProgress               : "Виконується перевірка орфографії...",\r
+DlgSpellNoMispell              : "Перевірку орфографії завершено: помилок не знайдено",\r
+DlgSpellNoChanges              : "Перевірку орфографії завершено: жодне слово не змінено",\r
+DlgSpellOneChange              : "Перевірку орфографії завершено: змінено одно слово",\r
+DlgSpellManyChanges            : "Перевірку орфографії завершено: 1% слів змінено",\r
+\r
+IeSpellDownload                        : "Модуль перевірки орфографії не встановлено. Бажаєтн завантажити його зараз?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Текст (Значення)",\r
+DlgButtonType          : "Тип",\r
+DlgButtonTypeBtn       : "Кнопка",\r
+DlgButtonTypeSbm       : "Відправити",\r
+DlgButtonTypeRst       : "Скинути",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Ім'я",\r
+DlgCheckboxValue       : "Значення",\r
+DlgCheckboxSelected    : "Обрана",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Ім'я",\r
+DlgFormAction  : "Дія",\r
+DlgFormMethod  : "Метод",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Ім'я",\r
+DlgSelectValue         : "Значення",\r
+DlgSelectSize          : "Розмір",\r
+DlgSelectLines         : "лінії",\r
+DlgSelectChkMulti      : "Дозволити обрання декількох позицій",\r
+DlgSelectOpAvail       : "Доступні варіанти",\r
+DlgSelectOpText                : "Текст",\r
+DlgSelectOpValue       : "Значення",\r
+DlgSelectBtnAdd                : "Добавити",\r
+DlgSelectBtnModify     : "Змінити",\r
+DlgSelectBtnUp         : "Вгору",\r
+DlgSelectBtnDown       : "Вниз",\r
+DlgSelectBtnSetValue : "Встановити як вибране значення",\r
+DlgSelectBtnDelete     : "Видалити",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Ім'я",\r
+DlgTextareaCols        : "Колонки",\r
+DlgTextareaRows        : "Строки",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Ім'я",\r
+DlgTextValue           : "Значення",\r
+DlgTextCharWidth       : "Ширина",\r
+DlgTextMaxChars                : "Макс. кіл-ть символів",\r
+DlgTextType                    : "Тип",\r
+DlgTextTypeText                : "Текст",\r
+DlgTextTypePass                : "Пароль",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Ім'я",\r
+DlgHiddenValue : "Значення",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Властивості маркованого списка",\r
+NumberedListProp       : "Властивості нумерованного списка",\r
+DlgLstStart                    : "Початок",\r
+DlgLstType                     : "Тип",\r
+DlgLstTypeCircle       : "Коло",\r
+DlgLstTypeDisc         : "Диск",\r
+DlgLstTypeSquare       : "Квадрат",\r
+DlgLstTypeNumbers      : "Номери (1, 2, 3)",\r
+DlgLstTypeLCase                : "Літери нижнього регістра(a, b, c)",\r
+DlgLstTypeUCase                : "Букви верхнього регістра (A, B, C)",\r
+DlgLstTypeSRoman       : "Малі римські літери (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Великі римські літери (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Загальні",\r
+DlgDocBackTab          : "Заднє тло",\r
+DlgDocColorsTab                : "Кольори та відступи",\r
+DlgDocMetaTab          : "Мета дані",\r
+\r
+DlgDocPageTitle                : "Заголовок сторінки",\r
+DlgDocLangDir          : "Напрямок тексту",\r
+DlgDocLangDirLTR       : "Зліва на право (LTR)",\r
+DlgDocLangDirRTL       : "Зправа на лево (RTL)",\r
+DlgDocLangCode         : "Код мови",\r
+DlgDocCharSet          : "Кодування набору символів",\r
+DlgDocCharSetCE                : "Центрально-європейська",\r
+DlgDocCharSetCT                : "Китайська традиційна (Big5)",\r
+DlgDocCharSetCR                : "Кирилиця",\r
+DlgDocCharSetGR                : "Грецька",\r
+DlgDocCharSetJP                : "Японська",\r
+DlgDocCharSetKR                : "Корейська",\r
+DlgDocCharSetTR                : "Турецька",\r
+DlgDocCharSetUN                : "Юнікод (UTF-8)",\r
+DlgDocCharSetWE                : "Західно-европейская",\r
+DlgDocCharSetOther     : "Інше кодування набору символів",\r
+\r
+DlgDocDocType          : "Заголовок типу документу",\r
+DlgDocDocTypeOther     : "Інший заголовок типу документу",\r
+DlgDocIncXHTML         : "Ввімкнути XHTML оголошення",\r
+DlgDocBgColor          : "Колір тла",\r
+DlgDocBgImage          : "URL зображення тла",\r
+DlgDocBgNoScroll       : "Тло без прокрутки",\r
+DlgDocCText                    : "Текст",\r
+DlgDocCLink                    : "Посилання",\r
+DlgDocCVisited         : "Відвідане посилання",\r
+DlgDocCActive          : "Активне посилання",\r
+DlgDocMargins          : "Відступи сторінки",\r
+DlgDocMaTop                    : "Верхній",\r
+DlgDocMaLeft           : "Лівий",\r
+DlgDocMaRight          : "Правий",\r
+DlgDocMaBottom         : "Нижній",\r
+DlgDocMeIndex          : "Ключові слова документа (розділені комами)",\r
+DlgDocMeDescr          : "Опис документа",\r
+DlgDocMeAuthor         : "Автор",\r
+DlgDocMeCopy           : "Авторські права",\r
+DlgDocPreview          : "Попередній перегляд",\r
+\r
+// Templates Dialog\r
+Templates                      : "Шаблони",\r
+DlgTemplatesTitle      : "Шаблони змісту",\r
+DlgTemplatesSelMsg     : "Оберіть, будь ласка, шаблон для відкриття в редакторі<br>(поточний зміст буде втрачено):",\r
+DlgTemplatesLoading    : "Завантаження списку шаблонів. Зачекайте, будь ласка...",\r
+DlgTemplatesNoTpl      : "(Не визначено жодного шаблону)",\r
+DlgTemplatesReplace    : "Замінити поточний вміст",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Про програму",\r
+DlgAboutBrowserInfoTab : "Інформація браузера",\r
+DlgAboutLicenseTab     : "Ліцензія",\r
+DlgAboutVersion                : "Версія",\r
+DlgAboutInfo           : "Додаткову інформацію дивіться на "\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/vi.js b/httemplate/elements/fckeditor/editor/lang/vi.js
new file mode 100644 (file)
index 0000000..5c2c608
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Vietnamese language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "Thu gọn Thanh công cụ",\r
+ToolbarExpand          : "Mở rộng Thanh công cụ",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "Lưu",\r
+NewPage                                : "Trang mới",\r
+Preview                                : "Xem trước",\r
+Cut                                    : "Cắt",\r
+Copy                           : "Sao chép",\r
+Paste                          : "Dán",\r
+PasteText                      : "Dán theo dạng văn bản thuần",\r
+PasteWord                      : "Dán với định dạng Word",\r
+Print                          : "In",\r
+SelectAll                      : "Chọn Tất cả",\r
+RemoveFormat           : "Xoá Định dạng",\r
+InsertLinkLbl          : "Liên kết",\r
+InsertLink                     : "Chèn/Sửa Liên kết",\r
+RemoveLink                     : "Xoá Liên kết",\r
+Anchor                         : "Chèn/Sửa Neo",\r
+InsertImageLbl         : "Hình ảnh",\r
+InsertImage                    : "Chèn/Sửa Hình ảnh",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "Chèn/Sửa Flash",\r
+InsertTableLbl         : "Bảng",\r
+InsertTable                    : "Chèn/Sửa Bảng",\r
+InsertLineLbl          : "Đường phân cách ngang",\r
+InsertLine                     : "Chèn Đường phân cách ngang",\r
+InsertSpecialCharLbl: "Ký tự đặc biệt",\r
+InsertSpecialChar      : "Chèn Ký tự đặc biệt",\r
+InsertSmileyLbl                : "Hình biểu lộ cảm xúc (mặt cười)",\r
+InsertSmiley           : "Chèn Hình biểu lộ cảm xúc (mặt cười)",\r
+About                          : "Giới thiệu về FCKeditor",\r
+Bold                           : "Đậm",\r
+Italic                         : "Nghiêng",\r
+Underline                      : "Gạch chân",\r
+StrikeThrough          : "Gạch xuyên ngang",\r
+Subscript                      : "Chỉ số dưới",\r
+Superscript                    : "Chỉ số trên",\r
+LeftJustify                    : "Canh trái",\r
+CenterJustify          : "Canh giữa",\r
+RightJustify           : "Canh phải",\r
+BlockJustify           : "Canh đều",\r
+DecreaseIndent         : "Dịch ra ngoài",\r
+IncreaseIndent         : "Dịch vào trong",\r
+Undo                           : "Khôi phục thao tác",\r
+Redo                           : "Làm lại thao tác",\r
+NumberedListLbl                : "Danh sách có thứ tự",\r
+NumberedList           : "Chèn/Xoá Danh sách có thứ tự",\r
+BulletedListLbl                : "Danh sách không thứ tự",\r
+BulletedList           : "Chèn/Xoá Danh sách không thứ tự",\r
+ShowTableBorders       : "Hiển thị Đường viền bảng",\r
+ShowDetails                    : "Hiển thị Chi tiết",\r
+Style                          : "Mẫu",\r
+FontFormat                     : "Định dạng",\r
+Font                           : "Phông",\r
+FontSize                       : "Cỡ chữ",\r
+TextColor                      : "Màu chữ",\r
+BGColor                                : "Màu nền",\r
+Source                         : "Mã HTML",\r
+Find                           : "Tìm kiếm",\r
+Replace                                : "Thay thế",\r
+SpellCheck                     : "Kiểm tra Chính tả",\r
+UniversalKeyboard      : "Bàn phím Quốc tế",\r
+PageBreakLbl           : "Ngắt trang",\r
+PageBreak                      : "Chèn Ngắt trang",\r
+\r
+Form                   : "Biểu mẫu",\r
+Checkbox               : "Nút kiểm",\r
+RadioButton            : "Nút chọn",\r
+TextField              : "Trường văn bản",\r
+Textarea               : "Vùng văn bản",\r
+HiddenField            : "Trường ẩn",\r
+Button                 : "Nút",\r
+SelectionField : "Ô chọn",\r
+ImageButton            : "Nút hình ảnh",\r
+\r
+FitWindow              : "Mở rộng tối đa kích thước trình biên tập",\r
+\r
+// Context Menu\r
+EditLink                       : "Sửa Liên kết",\r
+CellCM                         : "Ô",\r
+RowCM                          : "Hàng",\r
+ColumnCM                       : "Cột",\r
+InsertRow                      : "Chèn Hàng",\r
+DeleteRows                     : "Xoá Hàng",\r
+InsertColumn           : "Chèn Cột",\r
+DeleteColumns          : "Xoá Cột",\r
+InsertCell                     : "Chèn Ô",\r
+DeleteCells                    : "Xoá Ô",\r
+MergeCells                     : "Trộn Ô",\r
+SplitCell                      : "Chia Ô",\r
+TableDelete                    : "Xóa Bảng",\r
+CellProperties         : "Thuộc tính Ô",\r
+TableProperties                : "Thuộc tính Bảng",\r
+ImageProperties                : "Thuộc tính Hình ảnh",\r
+FlashProperties                : "Thuộc tính Flash",\r
+\r
+AnchorProp                     : "Thuộc tính Neo",\r
+ButtonProp                     : "Thuộc tính Nút",\r
+CheckboxProp           : "Thuộc tính Nút kiểm",\r
+HiddenFieldProp                : "Thuộc tính Trường ẩn",\r
+RadioButtonProp                : "Thuộc tính Nút chọn",\r
+ImageButtonProp                : "Thuộc tính Nút hình ảnh",\r
+TextFieldProp          : "Thuộc tính Trường văn bản",\r
+SelectionFieldProp     : "Thuộc tính Ô chọn",\r
+TextareaProp           : "Thuộc tính Vùng văn bản",\r
+FormProp                       : "Thuộc tính Biểu mẫu",\r
+\r
+FontFormats                    : "Normal;Formatted;Address;Heading 1;Heading 2;Heading 3;Heading 4;Heading 5;Heading 6;Normal (DIV)",          //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "Đang xử lý XHTML. Vui lòng đợi trong giây lát...",\r
+Done                           : "Đã hoàn thành",\r
+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?",\r
+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?",\r
+UnknownToolbarItem     : "Không rõ mục trên thanh công cụ \"%1\"",\r
+UnknownCommand         : "Không rõ lệnh \"%1\"",\r
+NotImplemented         : "Lệnh không được thực hiện",\r
+UnknownToolbarSet      : "Thanh công cụ \"%1\" không tồn tại",\r
+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 đi một số chức năng.",\r
+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.",\r
+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.",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "Đồng ý",\r
+DlgBtnCancel           : "Bỏ qua",\r
+DlgBtnClose                    : "Đóng",\r
+DlgBtnBrowseServer     : "Duyệt trên máy chủ",\r
+DlgAdvancedTag         : "Mở rộng",\r
+DlgOpOther                     : "<Khác>",\r
+DlgInfoTab                     : "Thông tin",\r
+DlgAlertUrl                    : "Hãy nhập vào một URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<không thiết lập>",\r
+DlgGenId                       : "Định danh",\r
+DlgGenLangDir          : "Đường dẫn Ngôn ngữ",\r
+DlgGenLangDirLtr       : "Trái sang Phải (LTR)",\r
+DlgGenLangDirRtl       : "Phải sang Trái (RTL)",\r
+DlgGenLangCode         : "Mã Ngôn ngữ",\r
+DlgGenAccessKey                : "Phím Hỗ trợ truy cập",\r
+DlgGenName                     : "Tên",\r
+DlgGenTabIndex         : "Chỉ số của Tab",\r
+DlgGenLongDescr                : "Mô tả URL",\r
+DlgGenClass                    : "Lớp Stylesheet",\r
+DlgGenTitle                    : "Advisory Title",\r
+DlgGenContType         : "Advisory Content Type",\r
+DlgGenLinkCharset      : "Bảng mã của tài nguyên được liên kết đến",\r
+DlgGenStyle                    : "Mẫu",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "Thuộc tính Hình ảnh",\r
+DlgImgInfoTab          : "Thông tin Hình ảnh",\r
+DlgImgBtnUpload                : "Tải lên Máy chủ",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "Tải lên",\r
+DlgImgAlt                      : "Chú thích Hình ảnh",\r
+DlgImgWidth                    : "Rộng",\r
+DlgImgHeight           : "Cao",\r
+DlgImgLockRatio                : "Giữ tỷ lệ",\r
+DlgBtnResetSize                : "Kích thước gốc",\r
+DlgImgBorder           : "Đường viền",\r
+DlgImgHSpace           : "HSpace",\r
+DlgImgVSpace           : "VSpace",\r
+DlgImgAlign                    : "Vị trí",\r
+DlgImgAlignLeft                : "Trái",\r
+DlgImgAlignAbsBottom: "Dưới tuyệt đối",\r
+DlgImgAlignAbsMiddle: "Giữa tuyệt đối",\r
+DlgImgAlignBaseline    : "Baseline",\r
+DlgImgAlignBottom      : "Dưới",\r
+DlgImgAlignMiddle      : "Giữa",\r
+DlgImgAlignRight       : "Phải",\r
+DlgImgAlignTextTop     : "Phía trên chữ",\r
+DlgImgAlignTop         : "Trên",\r
+DlgImgPreview          : "Xem trước",\r
+DlgImgAlertUrl         : "Hãy đưa vào URL của hình ảnh",\r
+DlgImgLinkTab          : "Liên kết",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Thuộc tính Flash",\r
+DlgFlashChkPlay                : "Tự động chạy",\r
+DlgFlashChkLoop                : "Lặp",\r
+DlgFlashChkMenu                : "Cho phép bật Menu của Flash",\r
+DlgFlashScale          : "Tỷ lệ",\r
+DlgFlashScaleAll       : "Hiển thị tất cả",\r
+DlgFlashScaleNoBorder  : "Không đường viền",\r
+DlgFlashScaleFit       : "Vừa vặn",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "Liên kết",\r
+DlgLnkInfoTab          : "Thông tin Liên kết",\r
+DlgLnkTargetTab                : "Đích",\r
+\r
+DlgLnkType                     : "Kiểu Liên kết",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "Neo trong trang này",\r
+DlgLnkTypeEMail                : "Thư điện tử",\r
+DlgLnkProto                    : "Giao thức",\r
+DlgLnkProtoOther       : "<khác>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "Chọn một Neo",\r
+DlgLnkAnchorByName     : "Theo Tên Neo",\r
+DlgLnkAnchorById       : "Theo Định danh Element",\r
+DlgLnkNoAnchors                : "<Không có Neo nào trong tài liệu>",            //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "Thư điện tử",\r
+DlgLnkEMailSubject     : "Tiêu đề Thông điệp",\r
+DlgLnkEMailBody                : "Nội dung Thông điệp",\r
+DlgLnkUpload           : "Tải lên",\r
+DlgLnkBtnUpload                : "Tải lên Máy chủ",\r
+\r
+DlgLnkTarget           : "Đích",\r
+DlgLnkTargetFrame      : "<khung>",\r
+DlgLnkTargetPopup      : "<cửa sổ popup>",\r
+DlgLnkTargetBlank      : "Cửa sổ mới (_blank)",\r
+DlgLnkTargetParent     : "Cửa sổ cha (_parent)",\r
+DlgLnkTargetSelf       : "Cùng cửa sổ (_self)",\r
+DlgLnkTargetTop                : "Cửa sổ trên cùng(_top)",\r
+DlgLnkTargetFrameName  : "Tên Khung đích",\r
+DlgLnkPopWinName       : "Tên Cửa sổ Popup",\r
+DlgLnkPopWinFeat       : "Đặc điểm của Cửa sổ Popup",\r
+DlgLnkPopResize                : "Kích thước thay đổi",\r
+DlgLnkPopLocation      : "Thanh vị trí",\r
+DlgLnkPopMenu          : "Thanh Menu",\r
+DlgLnkPopScroll                : "Thanh cuộn",\r
+DlgLnkPopStatus                : "Thanh trạng thái",\r
+DlgLnkPopToolbar       : "Thanh công cụ",\r
+DlgLnkPopFullScrn      : "Toàn màn hình (IE)",\r
+DlgLnkPopDependent     : "Phụ thuộc (Netscape)",\r
+DlgLnkPopWidth         : "Rộng",\r
+DlgLnkPopHeight                : "Cao",\r
+DlgLnkPopLeft          : "Vị trí Trái",\r
+DlgLnkPopTop           : "Vị trí Trên",\r
+\r
+DlnLnkMsgNoUrl         : "Hãy đưa vào Liên kết URL",\r
+DlnLnkMsgNoEMail       : "Hãy đưa vào địa chỉ thư điện tử",\r
+DlnLnkMsgNoAnchor      : "Hãy chọn một Neo",\r
+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",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "Chọn màu",\r
+DlgColorBtnClear       : "Xoá",\r
+DlgColorHighlight      : "Tô sáng",\r
+DlgColorSelected       : "Đã chọn",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "Chèn Hình biểu lộ cảm xúc (mặt cười)",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "Hãy chọn Ký tự đặc biệt",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "Thuộc tính bảng",\r
+DlgTableRows           : "Hàng",\r
+DlgTableColumns                : "Cột",\r
+DlgTableBorder         : "Cỡ Đường viền",\r
+DlgTableAlign          : "Canh lề",\r
+DlgTableAlignNotSet    : "<Chưa thiết lập>",\r
+DlgTableAlignLeft      : "Trái",\r
+DlgTableAlignCenter    : "Giữa",\r
+DlgTableAlignRight     : "Phải",\r
+DlgTableWidth          : "Rộng",\r
+DlgTableWidthPx                : "điểm (px)",\r
+DlgTableWidthPc                : "%",\r
+DlgTableHeight         : "Cao",\r
+DlgTableCellSpace      : "Khoảng cách Ô",\r
+DlgTableCellPad                : "Đệm Ô",\r
+DlgTableCaption                : "Đầu đề",\r
+DlgTableSummary                : "Tóm lược",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "Thuộc tính Ô",\r
+DlgCellWidth           : "Rộng",\r
+DlgCellWidthPx         : "điểm (px)",\r
+DlgCellWidthPc         : "%",\r
+DlgCellHeight          : "Cao",\r
+DlgCellWordWrap                : "Bọc từ",\r
+DlgCellWordWrapNotSet  : "<Chưa thiết lập>",\r
+DlgCellWordWrapYes     : "Đồng ý",\r
+DlgCellWordWrapNo      : "Không",\r
+DlgCellHorAlign                : "Canh theo Chiều ngang",\r
+DlgCellHorAlignNotSet  : "<Chưa thiết lập>",\r
+DlgCellHorAlignLeft    : "Trái",\r
+DlgCellHorAlignCenter  : "Giữa",\r
+DlgCellHorAlignRight: "Phải",\r
+DlgCellVerAlign                : "Canh theo Chiều dọc",\r
+DlgCellVerAlignNotSet  : "<Chưa thiết lập>",\r
+DlgCellVerAlignTop     : "Trên",\r
+DlgCellVerAlignMiddle  : "Giữa",\r
+DlgCellVerAlignBottom  : "Dưới",\r
+DlgCellVerAlignBaseline        : "Baseline",\r
+DlgCellRowSpan         : "Nối Hàng",\r
+DlgCellCollSpan                : "Nối Cột",\r
+DlgCellBackColor       : "Màu nền",\r
+DlgCellBorderColor     : "Màu viền",\r
+DlgCellBtnSelect       : "Chọn...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "Tìm kiếm",\r
+DlgFindFindBtn         : "Tìm kiếm",\r
+DlgFindNotFoundMsg     : "Không tìm thấy chuỗi cần tìm.",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "Thay thế",\r
+DlgReplaceFindLbl              : "Tìm chuỗi:",\r
+DlgReplaceReplaceLbl   : "Thay bằng:",\r
+DlgReplaceCaseChk              : "Phân biệt chữ hoa/thường",\r
+DlgReplaceReplaceBtn   : "Thay thế",\r
+DlgReplaceReplAllBtn   : "Thay thế Tất cả",\r
+DlgReplaceWordChk              : "Đúng toàn bộ từ",\r
+\r
+// Paste Operations / Dialog\r
+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).",\r
+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).",\r
+\r
+PasteAsText            : "Dán theo định dạng văn bản thuần",\r
+PasteFromWord  : "Dán với định dạng Word",\r
+\r
+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>.",\r
+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\r
+DlgPasteIgnoreFont             : "Chấp nhận các định dạng phông",\r
+DlgPasteRemoveStyles   : "Gỡ bỏ các định dạng Styles",\r
+DlgPasteCleanBox               : "Xóa nội dung",\r
+\r
+// Color Picker\r
+ColorAutomatic : "Tự động",\r
+ColorMoreColors        : "Màu khác...",\r
+\r
+// Document Properties\r
+DocProps               : "Thuộc tính Tài liệu",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "Thuộc tính Neo",\r
+DlgAnchorName          : "Tên của Neo",\r
+DlgAnchorErrorName     : "Hãy đưa vào tên của Neo",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "Không có trong từ điển",\r
+DlgSpellChangeTo               : "Chuyển thành",\r
+DlgSpellBtnIgnore              : "Bỏ qua",\r
+DlgSpellBtnIgnoreAll   : "Bỏ qua Tất cả",\r
+DlgSpellBtnReplace             : "Thay thế",\r
+DlgSpellBtnReplaceAll  : "Thay thế Tất cả",\r
+DlgSpellBtnUndo                        : "Phục hồi lại",\r
+DlgSpellNoSuggestions  : "- Không đưa ra gợi ý về từ -",\r
+DlgSpellProgress               : "Đang tiến hành kiểm tra chính tả...",\r
+DlgSpellNoMispell              : "Hoàn tất kiểm tra chính tả: Không có lỗi chính tả",\r
+DlgSpellNoChanges              : "Hoàn tất kiểm tra chính tả: Không có từ nào được thay đổi",\r
+DlgSpellOneChange              : "Hoàn tất kiểm tra chính tả: Một từ đã được thay đổi",\r
+DlgSpellManyChanges            : "Hoàn tất kiểm tra chính tả: %1 từ đã được thay đổi",\r
+\r
+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ờ?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "Chuỗi hiển thị (Giá trị)",\r
+DlgButtonType          : "Kiểu",\r
+DlgButtonTypeBtn       : "Nút Bấm",\r
+DlgButtonTypeSbm       : "Nút Gửi",\r
+DlgButtonTypeRst       : "Nút Nhập lại",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "Tên",\r
+DlgCheckboxValue       : "Giá trị",\r
+DlgCheckboxSelected    : "Được chọn",\r
+\r
+// Form Dialog\r
+DlgFormName            : "Tên",\r
+DlgFormAction  : "Hành động",\r
+DlgFormMethod  : "Phương thức",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "Tên",\r
+DlgSelectValue         : "Giá trị",\r
+DlgSelectSize          : "Kích cỡ",\r
+DlgSelectLines         : "dòng",\r
+DlgSelectChkMulti      : "Cho phép chọn nhiều",\r
+DlgSelectOpAvail       : "Các tùy chọn có thể sử dụng",\r
+DlgSelectOpText                : "Văn bản",\r
+DlgSelectOpValue       : "Giá trị",\r
+DlgSelectBtnAdd                : "Thêm",\r
+DlgSelectBtnModify     : "Thay đổi",\r
+DlgSelectBtnUp         : "Lên",\r
+DlgSelectBtnDown       : "Xuống",\r
+DlgSelectBtnSetValue : "Giá trị được chọn",\r
+DlgSelectBtnDelete     : "Xoá",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "Tên",\r
+DlgTextareaCols        : "Cột",\r
+DlgTextareaRows        : "Hàng",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "Tên",\r
+DlgTextValue           : "Giá trị",\r
+DlgTextCharWidth       : "Rộng",\r
+DlgTextMaxChars                : "Số Ký tự tối đa",\r
+DlgTextType                    : "Kiểu",\r
+DlgTextTypeText                : "Ký tự",\r
+DlgTextTypePass                : "Mật khẩu",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "Tên",\r
+DlgHiddenValue : "Giá trị",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "Thuộc tính Danh sách không thứ tự",\r
+NumberedListProp       : "Thuộc tính Danh sách có thứ tự",\r
+DlgLstStart                    : "Bắt đầu",\r
+DlgLstType                     : "Kiểu",\r
+DlgLstTypeCircle       : "Hình tròn",\r
+DlgLstTypeDisc         : "Hình đĩa",\r
+DlgLstTypeSquare       : "Hình vuông",\r
+DlgLstTypeNumbers      : "Số thứ tự (1, 2, 3)",\r
+DlgLstTypeLCase                : "Chữ cái thường (a, b, c)",\r
+DlgLstTypeUCase                : "Chữ cái hoa (A, B, C)",\r
+DlgLstTypeSRoman       : "Số La Mã thường (i, ii, iii)",\r
+DlgLstTypeLRoman       : "Số La Mã hoa (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "Toàn thể",\r
+DlgDocBackTab          : "Nền",\r
+DlgDocColorsTab                : "Màu sắc và Đường biên",\r
+DlgDocMetaTab          : "Siêu dữ liệu",\r
+\r
+DlgDocPageTitle                : "Tiêu đề Trang",\r
+DlgDocLangDir          : "Đường dẫn Ngôn ngữ",\r
+DlgDocLangDirLTR       : "Trái sang Phải (LTR)",\r
+DlgDocLangDirRTL       : "Phải sang Trái (RTL)",\r
+DlgDocLangCode         : "Mã Ngôn ngữ",\r
+DlgDocCharSet          : "Bảng mã ký tự",\r
+DlgDocCharSetCE                : "Trung Âu",\r
+DlgDocCharSetCT                : "Tiếng Trung Quốc (Big5)",\r
+DlgDocCharSetCR                : "Tiếng Kirin",\r
+DlgDocCharSetGR                : "Tiếng Hy Lạp",\r
+DlgDocCharSetJP                : "Tiếng Nhật",\r
+DlgDocCharSetKR                : "Tiếng Hàn",\r
+DlgDocCharSetTR                : "Tiếng Thổ Nhĩ Kỳ",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "Tây Âu",\r
+DlgDocCharSetOther     : "Bảng mã ký tự khác",\r
+\r
+DlgDocDocType          : "Kiểu Đề mục Tài liệu",\r
+DlgDocDocTypeOther     : "Kiểu Đề mục Tài liệu khác",\r
+DlgDocIncXHTML         : "Bao gồm cả định nghĩa XHTML",\r
+DlgDocBgColor          : "Màu nền",\r
+DlgDocBgImage          : "URL của Hình ảnh nền",\r
+DlgDocBgNoScroll       : "Không cuộn nền",\r
+DlgDocCText                    : "Văn bản",\r
+DlgDocCLink                    : "Liên kết",\r
+DlgDocCVisited         : "Liên kết Đã ghé thăm",\r
+DlgDocCActive          : "Liên kết Hiện hành",\r
+DlgDocMargins          : "Đường biên của Trang",\r
+DlgDocMaTop                    : "Trên",\r
+DlgDocMaLeft           : "Trái",\r
+DlgDocMaRight          : "Phải",\r
+DlgDocMaBottom         : "Dưới",\r
+DlgDocMeIndex          : "Các từ khóa chỉ mục tài liệu (phân cách bởi dấu phẩy)",\r
+DlgDocMeDescr          : "Mô tả tài liệu",\r
+DlgDocMeAuthor         : "Tác giả",\r
+DlgDocMeCopy           : "Bản quyền",\r
+DlgDocPreview          : "Xem trước",\r
+\r
+// Templates Dialog\r
+Templates                      : "Mẫu dựng sẵn",\r
+DlgTemplatesTitle      : "Nội dung Mẫu dựng sẵn",\r
+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):",\r
+DlgTemplatesLoading    : "Đang nạp Danh sách Mẫu dựng sẵn. Vui lòng đợi trong giây lát...",\r
+DlgTemplatesNoTpl      : "(Không có Mẫu dựng sẵn nào được định nghĩa)",\r
+DlgTemplatesReplace    : "Thay thế nội dung hiện tại",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "Giới thiệu",\r
+DlgAboutBrowserInfoTab : "Thông tin trình duyệt",\r
+DlgAboutLicenseTab     : "Giấy phép",\r
+DlgAboutVersion                : "phiên bản",\r
+DlgAboutInfo           : "Để biết thêm thông tin, hãy truy cập"\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/zh-cn.js b/httemplate/elements/fckeditor/editor/lang/zh-cn.js
new file mode 100644 (file)
index 0000000..6d6f4f4
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Chinese Simplified language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "折叠工具栏",\r
+ToolbarExpand          : "展开工具栏",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "保存",\r
+NewPage                                : "新建",\r
+Preview                                : "预览",\r
+Cut                                    : "剪切",\r
+Copy                           : "复制",\r
+Paste                          : "粘贴",\r
+PasteText                      : "粘贴为无格式文本",\r
+PasteWord                      : "从 MS Word 粘贴",\r
+Print                          : "打印",\r
+SelectAll                      : "全选",\r
+RemoveFormat           : "清除格式",\r
+InsertLinkLbl          : "超链接",\r
+InsertLink                     : "插入/编辑超链接",\r
+RemoveLink                     : "取消超链接",\r
+Anchor                         : "插入/编辑锚点链接",\r
+InsertImageLbl         : "图象",\r
+InsertImage                    : "插入/编辑图象",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "插入/编辑 Flash",\r
+InsertTableLbl         : "表格",\r
+InsertTable                    : "插入/编辑表格",\r
+InsertLineLbl          : "水平线",\r
+InsertLine                     : "插入水平线",\r
+InsertSpecialCharLbl: "特殊符号",\r
+InsertSpecialChar      : "插入特殊符号",\r
+InsertSmileyLbl                : "表情符",\r
+InsertSmiley           : "插入表情图标",\r
+About                          : "关于 FCKeditor",\r
+Bold                           : "加粗",\r
+Italic                         : "倾斜",\r
+Underline                      : "下划线",\r
+StrikeThrough          : "删除线",\r
+Subscript                      : "下标",\r
+Superscript                    : "上标",\r
+LeftJustify                    : "左对齐",\r
+CenterJustify          : "居中对齐",\r
+RightJustify           : "右对齐",\r
+BlockJustify           : "两端对齐",\r
+DecreaseIndent         : "减少缩进量",\r
+IncreaseIndent         : "增加缩进量",\r
+Undo                           : "撤消",\r
+Redo                           : "重做",\r
+NumberedListLbl                : "编号列表",\r
+NumberedList           : "插入/删除编号列表",\r
+BulletedListLbl                : "项目列表",\r
+BulletedList           : "插入/删除项目列表",\r
+ShowTableBorders       : "显示表格边框",\r
+ShowDetails                    : "显示详细资料",\r
+Style                          : "样式",\r
+FontFormat                     : "格式",\r
+Font                           : "字体",\r
+FontSize                       : "大小",\r
+TextColor                      : "文本颜色",\r
+BGColor                                : "背景颜色",\r
+Source                         : "源代码",\r
+Find                           : "查找",\r
+Replace                                : "替换",\r
+SpellCheck                     : "拼写检查",\r
+UniversalKeyboard      : "软键盘",\r
+PageBreakLbl           : "分页符",\r
+PageBreak                      : "插入分页符",\r
+\r
+Form                   : "表单",\r
+Checkbox               : "复选框",\r
+RadioButton            : "单选按钮",\r
+TextField              : "单行文本",\r
+Textarea               : "多行文本",\r
+HiddenField            : "隐藏域",\r
+Button                 : "按钮",\r
+SelectionField : "列表/菜单",\r
+ImageButton            : "图像域",\r
+\r
+FitWindow              : "全屏编辑",\r
+\r
+// Context Menu\r
+EditLink                       : "编辑超链接",\r
+CellCM                         : "单元格",\r
+RowCM                          : "行",\r
+ColumnCM                       : "列",\r
+InsertRow                      : "插入行",\r
+DeleteRows                     : "删除行",\r
+InsertColumn           : "插入列",\r
+DeleteColumns          : "删除列",\r
+InsertCell                     : "插入单元格",\r
+DeleteCells                    : "删除单元格",\r
+MergeCells                     : "合并单元格",\r
+SplitCell                      : "拆分单元格",\r
+TableDelete                    : "删除表格",\r
+CellProperties         : "单元格属性",\r
+TableProperties                : "表格属性",\r
+ImageProperties                : "图象属性",\r
+FlashProperties                : "Flash 属性",\r
+\r
+AnchorProp                     : "锚点链接属性",\r
+ButtonProp                     : "按钮属性",\r
+CheckboxProp           : "复选框属性",\r
+HiddenFieldProp                : "隐藏域属性",\r
+RadioButtonProp                : "单选按钮属性",\r
+ImageButtonProp                : "图像域属性",\r
+TextFieldProp          : "单行文本属性",\r
+SelectionFieldProp     : "菜单/列表属性",\r
+TextareaProp           : "多行文本属性",\r
+FormProp                       : "表单属性",\r
+\r
+FontFormats                    : "普通;已编排格式;地址;标题 1;标题 2;标题 3;标题 4;标题 5;标题 6;段落(DIV)",            //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "正在处理 XHTML,请稍等...",\r
+Done                           : "完成",\r
+PasteWordConfirm       : "您要粘贴的内容好像是来自 MS Word,是否要清除 MS Word 格式后再粘贴?",\r
+NotCompatiblePaste     : "该命令需要 Internet Explorer 5.5 或更高版本的支持,是否按常规粘贴进行?",\r
+UnknownToolbarItem     : "未知工具栏项目 \"%1\"",\r
+UnknownCommand         : "未知命令名称 \"%1\"",\r
+NotImplemented         : "命令无法执行",\r
+UnknownToolbarSet      : "工具栏设置 \"%1\" 不存在",\r
+NoActiveX                      : "浏览器安全设置限制了本编辑器的某些功能。您必须启用安全设置中的“运行 ActiveX 控件和插件”,否则将出现某些错误并缺少功能。",\r
+BrowseServerBlocked : "无法打开资源浏览器,请确认是否启用了禁止弹出窗口。",\r
+DialogBlocked          : "无法打开对话框窗口,请确认是否启用了禁止弹出窗口或网页对话框(IE)。",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "确定",\r
+DlgBtnCancel           : "取消",\r
+DlgBtnClose                    : "关闭",\r
+DlgBtnBrowseServer     : "浏览服务器",\r
+DlgAdvancedTag         : "高级",\r
+DlgOpOther                     : "<其它>",\r
+DlgInfoTab                     : "信息",\r
+DlgAlertUrl                    : "请插入 URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<没有设置>",\r
+DlgGenId                       : "ID",\r
+DlgGenLangDir          : "语言方向",\r
+DlgGenLangDirLtr       : "从左到右 (LTR)",\r
+DlgGenLangDirRtl       : "从右到左 (RTL)",\r
+DlgGenLangCode         : "语言代码",\r
+DlgGenAccessKey                : "访问键",\r
+DlgGenName                     : "名称",\r
+DlgGenTabIndex         : "Tab 键次序",\r
+DlgGenLongDescr                : "详细说明地址",\r
+DlgGenClass                    : "样式类名称",\r
+DlgGenTitle                    : "标题",\r
+DlgGenContType         : "内容类型",\r
+DlgGenLinkCharset      : "字符编码",\r
+DlgGenStyle                    : "行内样式",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "图象属性",\r
+DlgImgInfoTab          : "图象",\r
+DlgImgBtnUpload                : "发送到服务器上",\r
+DlgImgURL                      : "源文件",\r
+DlgImgUpload           : "上传",\r
+DlgImgAlt                      : "替换文本",\r
+DlgImgWidth                    : "宽度",\r
+DlgImgHeight           : "高度",\r
+DlgImgLockRatio                : "锁定比例",\r
+DlgBtnResetSize                : "恢复尺寸",\r
+DlgImgBorder           : "边框大小",\r
+DlgImgHSpace           : "水平间距",\r
+DlgImgVSpace           : "垂直间距",\r
+DlgImgAlign                    : "对齐方式",\r
+DlgImgAlignLeft                : "左对齐",\r
+DlgImgAlignAbsBottom: "绝对底边",\r
+DlgImgAlignAbsMiddle: "绝对居中",\r
+DlgImgAlignBaseline    : "基线",\r
+DlgImgAlignBottom      : "底边",\r
+DlgImgAlignMiddle      : "居中",\r
+DlgImgAlignRight       : "右对齐",\r
+DlgImgAlignTextTop     : "文本上方",\r
+DlgImgAlignTop         : "顶端",\r
+DlgImgPreview          : "预览",\r
+DlgImgAlertUrl         : "请输入图象地址",\r
+DlgImgLinkTab          : "链接",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash 属性",\r
+DlgFlashChkPlay                : "自动播放",\r
+DlgFlashChkLoop                : "循环",\r
+DlgFlashChkMenu                : "启用 Flash 菜单",\r
+DlgFlashScale          : "缩放",\r
+DlgFlashScaleAll       : "全部显示",\r
+DlgFlashScaleNoBorder  : "无边框",\r
+DlgFlashScaleFit       : "严格匹配",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "超链接",\r
+DlgLnkInfoTab          : "超链接信息",\r
+DlgLnkTargetTab                : "目标",\r
+\r
+DlgLnkType                     : "超链接类型",\r
+DlgLnkTypeURL          : "超链接",\r
+DlgLnkTypeAnchor       : "页内锚点链接",\r
+DlgLnkTypeEMail                : "电子邮件",\r
+DlgLnkProto                    : "协议",\r
+DlgLnkProtoOther       : "<其它>",\r
+DlgLnkURL                      : "地址",\r
+DlgLnkAnchorSel                : "选择一个锚点",\r
+DlgLnkAnchorByName     : "按锚点名称",\r
+DlgLnkAnchorById       : "按锚点 ID",\r
+DlgLnkNoAnchors                : "<此文档没有可用的锚点>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "地址",\r
+DlgLnkEMailSubject     : "主题",\r
+DlgLnkEMailBody                : "内容",\r
+DlgLnkUpload           : "上传",\r
+DlgLnkBtnUpload                : "发送到服务器上",\r
+\r
+DlgLnkTarget           : "目标",\r
+DlgLnkTargetFrame      : "<框架>",\r
+DlgLnkTargetPopup      : "<弹出窗口>",\r
+DlgLnkTargetBlank      : "新窗口 (_blank)",\r
+DlgLnkTargetParent     : "父窗口 (_parent)",\r
+DlgLnkTargetSelf       : "本窗口 (_self)",\r
+DlgLnkTargetTop                : "整页 (_top)",\r
+DlgLnkTargetFrameName  : "目标框架名称",\r
+DlgLnkPopWinName       : "弹出窗口名称",\r
+DlgLnkPopWinFeat       : "弹出窗口属性",\r
+DlgLnkPopResize                : "调整大小",\r
+DlgLnkPopLocation      : "地址栏",\r
+DlgLnkPopMenu          : "菜单栏",\r
+DlgLnkPopScroll                : "滚动条",\r
+DlgLnkPopStatus                : "状态栏",\r
+DlgLnkPopToolbar       : "工具栏",\r
+DlgLnkPopFullScrn      : "全屏 (IE)",\r
+DlgLnkPopDependent     : "依附 (NS)",\r
+DlgLnkPopWidth         : "宽",\r
+DlgLnkPopHeight                : "高",\r
+DlgLnkPopLeft          : "左",\r
+DlgLnkPopTop           : "右",\r
+\r
+DlnLnkMsgNoUrl         : "请输入超链接地址",\r
+DlnLnkMsgNoEMail       : "请输入电子邮件地址",\r
+DlnLnkMsgNoAnchor      : "请选择一个锚点",\r
+DlnLnkMsgInvPopName    : "弹出窗口名称必须以字母开头,并且不能含有空格。",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "选择颜色",\r
+DlgColorBtnClear       : "清除",\r
+DlgColorHighlight      : "预览",\r
+DlgColorSelected       : "选择",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "插入表情图标",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "选择特殊符号",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "表格属性",\r
+DlgTableRows           : "行数",\r
+DlgTableColumns                : "列数",\r
+DlgTableBorder         : "边框",\r
+DlgTableAlign          : "对齐",\r
+DlgTableAlignNotSet    : "<没有设置>",\r
+DlgTableAlignLeft      : "左对齐",\r
+DlgTableAlignCenter    : "居中",\r
+DlgTableAlignRight     : "右对齐",\r
+DlgTableWidth          : "宽度",\r
+DlgTableWidthPx                : "像素",\r
+DlgTableWidthPc                : "百分比",\r
+DlgTableHeight         : "高度",\r
+DlgTableCellSpace      : "间距",\r
+DlgTableCellPad                : "边距",\r
+DlgTableCaption                : "标题",\r
+DlgTableSummary                : "摘要",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "单元格属性",\r
+DlgCellWidth           : "宽度",\r
+DlgCellWidthPx         : "像素",\r
+DlgCellWidthPc         : "百分比",\r
+DlgCellHeight          : "高度",\r
+DlgCellWordWrap                : "自动换行",\r
+DlgCellWordWrapNotSet  : "<没有设置>",\r
+DlgCellWordWrapYes     : "是",\r
+DlgCellWordWrapNo      : "否",\r
+DlgCellHorAlign                : "水平对齐",\r
+DlgCellHorAlignNotSet  : "<没有设置>",\r
+DlgCellHorAlignLeft    : "左对齐",\r
+DlgCellHorAlignCenter  : "居中",\r
+DlgCellHorAlignRight: "右对齐",\r
+DlgCellVerAlign                : "垂直对齐",\r
+DlgCellVerAlignNotSet  : "<没有设置>",\r
+DlgCellVerAlignTop     : "顶端",\r
+DlgCellVerAlignMiddle  : "居中",\r
+DlgCellVerAlignBottom  : "底部",\r
+DlgCellVerAlignBaseline        : "基线",\r
+DlgCellRowSpan         : "纵跨行数",\r
+DlgCellCollSpan                : "横跨列数",\r
+DlgCellBackColor       : "背景颜色",\r
+DlgCellBorderColor     : "边框颜色",\r
+DlgCellBtnSelect       : "选择...",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "查找",\r
+DlgFindFindBtn         : "查找",\r
+DlgFindNotFoundMsg     : "指定文本没有找到。",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "替换",\r
+DlgReplaceFindLbl              : "查找:",\r
+DlgReplaceReplaceLbl   : "替换:",\r
+DlgReplaceCaseChk              : "区分大小写",\r
+DlgReplaceReplaceBtn   : "替换",\r
+DlgReplaceReplAllBtn   : "全部替换",\r
+DlgReplaceWordChk              : "全字匹配",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "您的浏览器安全设置不允许编辑器自动执行剪切操作,请使用键盘快捷键(Ctrl+X)来完成。",\r
+PasteErrorCopy : "您的浏览器安全设置不允许编辑器自动执行复制操作,请使用键盘快捷键(Ctrl+C)来完成。",\r
+\r
+PasteAsText            : "粘贴为无格式文本",\r
+PasteFromWord  : "从 MS Word 粘贴",\r
+\r
+DlgPasteMsg2   : "请使用键盘快捷键(<STRONG>Ctrl+V</STRONG>)把内容粘贴到下面的方框里,再按 <STRONG>确定</STRONG>。",\r
+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\r
+DlgPasteIgnoreFont             : "忽略 Font 标签",\r
+DlgPasteRemoveStyles   : "清理 CSS 样式",\r
+DlgPasteCleanBox               : "清空上面内容",\r
+\r
+// Color Picker\r
+ColorAutomatic : "自动",\r
+ColorMoreColors        : "其它颜色...",\r
+\r
+// Document Properties\r
+DocProps               : "页面属性",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "命名锚点",\r
+DlgAnchorName          : "锚点名称",\r
+DlgAnchorErrorName     : "请输入锚点名称",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "没有在字典里",\r
+DlgSpellChangeTo               : "更改为",\r
+DlgSpellBtnIgnore              : "忽略",\r
+DlgSpellBtnIgnoreAll   : "全部忽略",\r
+DlgSpellBtnReplace             : "替换",\r
+DlgSpellBtnReplaceAll  : "全部替换",\r
+DlgSpellBtnUndo                        : "撤消",\r
+DlgSpellNoSuggestions  : "- 没有建议 -",\r
+DlgSpellProgress               : "正在进行拼写检查...",\r
+DlgSpellNoMispell              : "拼写检查完成:没有发现拼写错误",\r
+DlgSpellNoChanges              : "拼写检查完成:没有更改任何单词",\r
+DlgSpellOneChange              : "拼写检查完成:更改了一个单词",\r
+DlgSpellManyChanges            : "拼写检查完成:更改了 %1 个单词",\r
+\r
+IeSpellDownload                        : "拼写检查插件还没安装,你是否想现在就下载?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "标签(值)",\r
+DlgButtonType          : "类型",\r
+DlgButtonTypeBtn       : "按钮",\r
+DlgButtonTypeSbm       : "提交",\r
+DlgButtonTypeRst       : "重设",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "名称",\r
+DlgCheckboxValue       : "选定值",\r
+DlgCheckboxSelected    : "已勾选",\r
+\r
+// Form Dialog\r
+DlgFormName            : "名称",\r
+DlgFormAction  : "动作",\r
+DlgFormMethod  : "方法",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "名称",\r
+DlgSelectValue         : "选定",\r
+DlgSelectSize          : "高度",\r
+DlgSelectLines         : "行",\r
+DlgSelectChkMulti      : "允许多选",\r
+DlgSelectOpAvail       : "列表值",\r
+DlgSelectOpText                : "标签",\r
+DlgSelectOpValue       : "值",\r
+DlgSelectBtnAdd                : "新增",\r
+DlgSelectBtnModify     : "修改",\r
+DlgSelectBtnUp         : "上移",\r
+DlgSelectBtnDown       : "下移",\r
+DlgSelectBtnSetValue : "设为初始化时选定",\r
+DlgSelectBtnDelete     : "删除",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "名称",\r
+DlgTextareaCols        : "字符宽度",\r
+DlgTextareaRows        : "行数",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "名称",\r
+DlgTextValue           : "初始值",\r
+DlgTextCharWidth       : "字符宽度",\r
+DlgTextMaxChars                : "最多字符数",\r
+DlgTextType                    : "类型",\r
+DlgTextTypeText                : "文本",\r
+DlgTextTypePass                : "密码",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "名称",\r
+DlgHiddenValue : "初始值",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "项目列表属性",\r
+NumberedListProp       : "编号列表属性",\r
+DlgLstStart                    : "开始序号",\r
+DlgLstType                     : "列表类型",\r
+DlgLstTypeCircle       : "圆圈",\r
+DlgLstTypeDisc         : "圆点",\r
+DlgLstTypeSquare       : "方块",\r
+DlgLstTypeNumbers      : "数字 (1, 2, 3)",\r
+DlgLstTypeLCase                : "小写字母 (a, b, c)",\r
+DlgLstTypeUCase                : "大写字母 (A, B, C)",\r
+DlgLstTypeSRoman       : "小写罗马数字 (i, ii, iii)",\r
+DlgLstTypeLRoman       : "大写罗马数字 (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "常规",\r
+DlgDocBackTab          : "背景",\r
+DlgDocColorsTab                : "颜色和边距",\r
+DlgDocMetaTab          : "Meta 数据",\r
+\r
+DlgDocPageTitle                : "页面标题",\r
+DlgDocLangDir          : "语言方向",\r
+DlgDocLangDirLTR       : "从左到右 (LTR)",\r
+DlgDocLangDirRTL       : "从右到左 (RTL)",\r
+DlgDocLangCode         : "语言代码",\r
+DlgDocCharSet          : "字符编码",\r
+DlgDocCharSetCE                : "中欧",\r
+DlgDocCharSetCT                : "繁体中文 (Big5)",\r
+DlgDocCharSetCR                : "西里尔文",\r
+DlgDocCharSetGR                : "希腊文",\r
+DlgDocCharSetJP                : "日文",\r
+DlgDocCharSetKR                : "韩文",\r
+DlgDocCharSetTR                : "土耳其文",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "西欧",\r
+DlgDocCharSetOther     : "其它字符编码",\r
+\r
+DlgDocDocType          : "文档类型",\r
+DlgDocDocTypeOther     : "其它文档类型",\r
+DlgDocIncXHTML         : "包含 XHTML 声明",\r
+DlgDocBgColor          : "背景颜色",\r
+DlgDocBgImage          : "背景图像",\r
+DlgDocBgNoScroll       : "不滚动背景图像",\r
+DlgDocCText                    : "文本",\r
+DlgDocCLink                    : "超链接",\r
+DlgDocCVisited         : "已访问的超链接",\r
+DlgDocCActive          : "活动超链接",\r
+DlgDocMargins          : "页面边距",\r
+DlgDocMaTop                    : "上",\r
+DlgDocMaLeft           : "左",\r
+DlgDocMaRight          : "右",\r
+DlgDocMaBottom         : "下",\r
+DlgDocMeIndex          : "页面索引关键字 (用半角逗号[,]分隔)",\r
+DlgDocMeDescr          : "页面说明",\r
+DlgDocMeAuthor         : "作者",\r
+DlgDocMeCopy           : "版权",\r
+DlgDocPreview          : "预览",\r
+\r
+// Templates Dialog\r
+Templates                      : "模板",\r
+DlgTemplatesTitle      : "内容模板",\r
+DlgTemplatesSelMsg     : "请选择编辑器内容模板<br>(当前内容将会被清除替换):",\r
+DlgTemplatesLoading    : "正在加载模板列表,请稍等...",\r
+DlgTemplatesNoTpl      : "(没有模板)",\r
+DlgTemplatesReplace    : "替换当前内容",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "关于",\r
+DlgAboutBrowserInfoTab : "浏览器信息",\r
+DlgAboutLicenseTab     : "许可证",\r
+DlgAboutVersion                : "版本",\r
+DlgAboutInfo           : "要获得更多信息请访问 "\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/lang/zh.js b/httemplate/elements/fckeditor/editor/lang/zh.js
new file mode 100644 (file)
index 0000000..b5cd239
--- /dev/null
@@ -0,0 +1,504 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Chinese Traditional language file.\r
+ */\r
+\r
+var FCKLang =\r
+{\r
+// Language direction : "ltr" (left to right) or "rtl" (right to left).\r
+Dir                                    : "ltr",\r
+\r
+ToolbarCollapse                : "隱藏面板",\r
+ToolbarExpand          : "顯示面板",\r
+\r
+// Toolbar Items and Context Menu\r
+Save                           : "儲存",\r
+NewPage                                : "開新檔案",\r
+Preview                                : "預覽",\r
+Cut                                    : "剪下",\r
+Copy                           : "複製",\r
+Paste                          : "貼上",\r
+PasteText                      : "貼為純文字格式",\r
+PasteWord                      : "自 Word 貼上",\r
+Print                          : "列印",\r
+SelectAll                      : "全選",\r
+RemoveFormat           : "清除格式",\r
+InsertLinkLbl          : "超連結",\r
+InsertLink                     : "插入/編輯超連結",\r
+RemoveLink                     : "移除超連結",\r
+Anchor                         : "插入/編輯錨點",\r
+InsertImageLbl         : "影像",\r
+InsertImage                    : "插入/編輯影像",\r
+InsertFlashLbl         : "Flash",\r
+InsertFlash                    : "插入/編輯 Flash",\r
+InsertTableLbl         : "表格",\r
+InsertTable                    : "插入/編輯表格",\r
+InsertLineLbl          : "水平線",\r
+InsertLine                     : "插入水平線",\r
+InsertSpecialCharLbl: "特殊符號",\r
+InsertSpecialChar      : "插入特殊符號",\r
+InsertSmileyLbl                : "表情符號",\r
+InsertSmiley           : "插入表情符號",\r
+About                          : "關於 FCKeditor",\r
+Bold                           : "粗體",\r
+Italic                         : "斜體",\r
+Underline                      : "底線",\r
+StrikeThrough          : "刪除線",\r
+Subscript                      : "下標",\r
+Superscript                    : "上標",\r
+LeftJustify                    : "靠左對齊",\r
+CenterJustify          : "置中",\r
+RightJustify           : "靠右對齊",\r
+BlockJustify           : "左右對齊",\r
+DecreaseIndent         : "減少縮排",\r
+IncreaseIndent         : "增加縮排",\r
+Undo                           : "復原",\r
+Redo                           : "重複",\r
+NumberedListLbl                : "編號清單",\r
+NumberedList           : "插入/移除編號清單",\r
+BulletedListLbl                : "項目清單",\r
+BulletedList           : "插入/移除項目清單",\r
+ShowTableBorders       : "顯示表格邊框",\r
+ShowDetails                    : "顯示詳細資料",\r
+Style                          : "樣式",\r
+FontFormat                     : "格式",\r
+Font                           : "字體",\r
+FontSize                       : "大小",\r
+TextColor                      : "文字顏色",\r
+BGColor                                : "背景顏色",\r
+Source                         : "原始碼",\r
+Find                           : "尋找",\r
+Replace                                : "取代",\r
+SpellCheck                     : "拼字檢查",\r
+UniversalKeyboard      : "萬國鍵盤",\r
+PageBreakLbl           : "分頁符號",\r
+PageBreak                      : "插入分頁符號",\r
+\r
+Form                   : "表單",\r
+Checkbox               : "核取方塊",\r
+RadioButton            : "選項按鈕",\r
+TextField              : "文字方塊",\r
+Textarea               : "文字區域",\r
+HiddenField            : "隱藏欄位",\r
+Button                 : "按鈕",\r
+SelectionField : "清單/選單",\r
+ImageButton            : "影像按鈕",\r
+\r
+FitWindow              : "編輯器最大化",\r
+\r
+// Context Menu\r
+EditLink                       : "編輯超連結",\r
+CellCM                         : "儲存格",\r
+RowCM                          : "列",\r
+ColumnCM                       : "欄",\r
+InsertRow                      : "插入列",\r
+DeleteRows                     : "刪除列",\r
+InsertColumn           : "插入欄",\r
+DeleteColumns          : "刪除欄",\r
+InsertCell                     : "插入儲存格",\r
+DeleteCells                    : "刪除儲存格",\r
+MergeCells                     : "合併儲存格",\r
+SplitCell                      : "分割儲存格",\r
+TableDelete                    : "刪除表格",\r
+CellProperties         : "儲存格屬性",\r
+TableProperties                : "表格屬性",\r
+ImageProperties                : "影像屬性",\r
+FlashProperties                : "Flash 屬性",\r
+\r
+AnchorProp                     : "錨點屬性",\r
+ButtonProp                     : "按鈕屬性",\r
+CheckboxProp           : "核取方塊屬性",\r
+HiddenFieldProp                : "隱藏欄位屬性",\r
+RadioButtonProp                : "選項按鈕屬性",\r
+ImageButtonProp                : "影像按鈕屬性",\r
+TextFieldProp          : "文字方塊屬性",\r
+SelectionFieldProp     : "清單/選單屬性",\r
+TextareaProp           : "文字區域屬性",\r
+FormProp                       : "表單屬性",\r
+\r
+FontFormats                    : "本文;已格式化;位址;標題 1;標題 2;標題 3;標題 4;標題 5;標題 6;本文 (DIV)",              //REVIEW : Check _getfontformat.html\r
+\r
+// Alerts and Messages\r
+ProcessingXHTML                : "處理 XHTML 中,請稍候…",\r
+Done                           : "完成",\r
+PasteWordConfirm       : "您想貼上的文字似乎是自 Word 複製而來,請問您是否要先清除 Word 的格式後再行貼上?",\r
+NotCompatiblePaste     : "此指令僅在 Internet Explorer 5.5 或以上的版本有效。請問您是否同意不清除格式即貼上?",\r
+UnknownToolbarItem     : "未知工具列項目 \"%1\"",\r
+UnknownCommand         : "未知指令名稱 \"%1\"",\r
+NotImplemented         : "尚未安裝此指令",\r
+UnknownToolbarSet      : "工具列設定 \"%1\" 不存在",\r
+NoActiveX                      : "瀏覽器的安全性設定限制了本編輯器的某些功能。您必須啟用安全性設定中的「執行ActiveX控制項與外掛程式」項目,否則本編輯器將會出現錯誤並缺少某些功能",\r
+BrowseServerBlocked : "無法開啟資源瀏覽器,請確定所有快顯視窗封鎖程式是否關閉",\r
+DialogBlocked          : "無法開啟對話視窗,請確定所有快顯視窗封鎖程式是否關閉",\r
+\r
+// Dialogs\r
+DlgBtnOK                       : "確定",\r
+DlgBtnCancel           : "取消",\r
+DlgBtnClose                    : "關閉",\r
+DlgBtnBrowseServer     : "瀏覽伺服器端",\r
+DlgAdvancedTag         : "進階",\r
+DlgOpOther                     : "<其他>",\r
+DlgInfoTab                     : "資訊",\r
+DlgAlertUrl                    : "請插入 URL",\r
+\r
+// General Dialogs Labels\r
+DlgGenNotSet           : "<尚未設定>",\r
+DlgGenId                       : "ID",\r
+DlgGenLangDir          : "語言方向",\r
+DlgGenLangDirLtr       : "由左而右 (LTR)",\r
+DlgGenLangDirRtl       : "由右而左 (RTL)",\r
+DlgGenLangCode         : "語言代碼",\r
+DlgGenAccessKey                : "存取鍵",\r
+DlgGenName                     : "名稱",\r
+DlgGenTabIndex         : "定位順序",\r
+DlgGenLongDescr                : "詳細 URL",\r
+DlgGenClass                    : "樣式表類別",\r
+DlgGenTitle                    : "標題",\r
+DlgGenContType         : "內容類型",\r
+DlgGenLinkCharset      : "連結資源之編碼",\r
+DlgGenStyle                    : "樣式",\r
+\r
+// Image Dialog\r
+DlgImgTitle                    : "影像屬性",\r
+DlgImgInfoTab          : "影像資訊",\r
+DlgImgBtnUpload                : "上傳至伺服器",\r
+DlgImgURL                      : "URL",\r
+DlgImgUpload           : "上傳",\r
+DlgImgAlt                      : "替代文字",\r
+DlgImgWidth                    : "寬度",\r
+DlgImgHeight           : "高度",\r
+DlgImgLockRatio                : "等比例",\r
+DlgBtnResetSize                : "重設為原大小",\r
+DlgImgBorder           : "邊框",\r
+DlgImgHSpace           : "水平距離",\r
+DlgImgVSpace           : "垂直距離",\r
+DlgImgAlign                    : "對齊",\r
+DlgImgAlignLeft                : "靠左對齊",\r
+DlgImgAlignAbsBottom: "絕對下方",\r
+DlgImgAlignAbsMiddle: "絕對中間",\r
+DlgImgAlignBaseline    : "基準線",\r
+DlgImgAlignBottom      : "靠下對齊",\r
+DlgImgAlignMiddle      : "置中對齊",\r
+DlgImgAlignRight       : "靠右對齊",\r
+DlgImgAlignTextTop     : "文字上方",\r
+DlgImgAlignTop         : "靠上對齊",\r
+DlgImgPreview          : "預覽",\r
+DlgImgAlertUrl         : "請輸入影像 URL",\r
+DlgImgLinkTab          : "超連結",\r
+\r
+// Flash Dialog\r
+DlgFlashTitle          : "Flash 屬性",\r
+DlgFlashChkPlay                : "自動播放",\r
+DlgFlashChkLoop                : "重複",\r
+DlgFlashChkMenu                : "開啟選單",\r
+DlgFlashScale          : "縮放",\r
+DlgFlashScaleAll       : "全部顯示",\r
+DlgFlashScaleNoBorder  : "無邊框",\r
+DlgFlashScaleFit       : "精確符合",\r
+\r
+// Link Dialog\r
+DlgLnkWindowTitle      : "超連結",\r
+DlgLnkInfoTab          : "超連結資訊",\r
+DlgLnkTargetTab                : "目標",\r
+\r
+DlgLnkType                     : "超連接類型",\r
+DlgLnkTypeURL          : "URL",\r
+DlgLnkTypeAnchor       : "本頁錨點",\r
+DlgLnkTypeEMail                : "電子郵件",\r
+DlgLnkProto                    : "通訊協定",\r
+DlgLnkProtoOther       : "<其他>",\r
+DlgLnkURL                      : "URL",\r
+DlgLnkAnchorSel                : "請選擇錨點",\r
+DlgLnkAnchorByName     : "依錨點名稱",\r
+DlgLnkAnchorById       : "依元件 ID",\r
+DlgLnkNoAnchors                : "<本文件尚無可用之錨點>",           //REVIEW : Change < and > with ( and )\r
+DlgLnkEMail                    : "電子郵件",\r
+DlgLnkEMailSubject     : "郵件主旨",\r
+DlgLnkEMailBody                : "郵件內容",\r
+DlgLnkUpload           : "上傳",\r
+DlgLnkBtnUpload                : "傳送至伺服器",\r
+\r
+DlgLnkTarget           : "目標",\r
+DlgLnkTargetFrame      : "<框架>",\r
+DlgLnkTargetPopup      : "<快顯視窗>",\r
+DlgLnkTargetBlank      : "新視窗 (_blank)",\r
+DlgLnkTargetParent     : "父視窗 (_parent)",\r
+DlgLnkTargetSelf       : "本視窗 (_self)",\r
+DlgLnkTargetTop                : "最上層視窗 (_top)",\r
+DlgLnkTargetFrameName  : "目標框架名稱",\r
+DlgLnkPopWinName       : "快顯視窗名稱",\r
+DlgLnkPopWinFeat       : "快顯視窗屬性",\r
+DlgLnkPopResize                : "可調整大小",\r
+DlgLnkPopLocation      : "網址列",\r
+DlgLnkPopMenu          : "選單列",\r
+DlgLnkPopScroll                : "捲軸",\r
+DlgLnkPopStatus                : "狀態列",\r
+DlgLnkPopToolbar       : "工具列",\r
+DlgLnkPopFullScrn      : "全螢幕 (IE)",\r
+DlgLnkPopDependent     : "從屬 (NS)",\r
+DlgLnkPopWidth         : "寬",\r
+DlgLnkPopHeight                : "高",\r
+DlgLnkPopLeft          : "左",\r
+DlgLnkPopTop           : "右",\r
+\r
+DlnLnkMsgNoUrl         : "請輸入欲連結的 URL",\r
+DlnLnkMsgNoEMail       : "請輸入電子郵件位址",\r
+DlnLnkMsgNoAnchor      : "請選擇錨點",\r
+DlnLnkMsgInvPopName    : "快顯名稱必須以「英文字母」為開頭,且不得含有空白",\r
+\r
+// Color Dialog\r
+DlgColorTitle          : "請選擇顏色",\r
+DlgColorBtnClear       : "清除",\r
+DlgColorHighlight      : "預覽",\r
+DlgColorSelected       : "選擇",\r
+\r
+// Smiley Dialog\r
+DlgSmileyTitle         : "插入表情符號",\r
+\r
+// Special Character Dialog\r
+DlgSpecialCharTitle    : "請選擇特殊符號",\r
+\r
+// Table Dialog\r
+DlgTableTitle          : "表格屬性",\r
+DlgTableRows           : "列數",\r
+DlgTableColumns                : "欄數",\r
+DlgTableBorder         : "邊框",\r
+DlgTableAlign          : "對齊",\r
+DlgTableAlignNotSet    : "<未設定>",\r
+DlgTableAlignLeft      : "靠左對齊",\r
+DlgTableAlignCenter    : "置中",\r
+DlgTableAlignRight     : "靠右對齊",\r
+DlgTableWidth          : "寬度",\r
+DlgTableWidthPx                : "像素",\r
+DlgTableWidthPc                : "百分比",\r
+DlgTableHeight         : "高度",\r
+DlgTableCellSpace      : "間距",\r
+DlgTableCellPad                : "內距",\r
+DlgTableCaption                : "標題",\r
+DlgTableSummary                : "摘要",\r
+\r
+// Table Cell Dialog\r
+DlgCellTitle           : "儲存格屬性",\r
+DlgCellWidth           : "寬度",\r
+DlgCellWidthPx         : "像素",\r
+DlgCellWidthPc         : "百分比",\r
+DlgCellHeight          : "高度",\r
+DlgCellWordWrap                : "自動換行",\r
+DlgCellWordWrapNotSet  : "<尚未設定>",\r
+DlgCellWordWrapYes     : "是",\r
+DlgCellWordWrapNo      : "否",\r
+DlgCellHorAlign                : "水平對齊",\r
+DlgCellHorAlignNotSet  : "<尚未設定>",\r
+DlgCellHorAlignLeft    : "靠左對齊",\r
+DlgCellHorAlignCenter  : "置中",\r
+DlgCellHorAlignRight: "靠右對齊",\r
+DlgCellVerAlign                : "垂直對齊",\r
+DlgCellVerAlignNotSet  : "<尚未設定>",\r
+DlgCellVerAlignTop     : "靠上對齊",\r
+DlgCellVerAlignMiddle  : "置中",\r
+DlgCellVerAlignBottom  : "靠下對齊",\r
+DlgCellVerAlignBaseline        : "基準線",\r
+DlgCellRowSpan         : "合併列數",\r
+DlgCellCollSpan                : "合併欄数",\r
+DlgCellBackColor       : "背景顏色",\r
+DlgCellBorderColor     : "邊框顏色",\r
+DlgCellBtnSelect       : "請選擇…",\r
+\r
+// Find Dialog\r
+DlgFindTitle           : "尋找",\r
+DlgFindFindBtn         : "尋找",\r
+DlgFindNotFoundMsg     : "未找到指定的文字。",\r
+\r
+// Replace Dialog\r
+DlgReplaceTitle                        : "取代",\r
+DlgReplaceFindLbl              : "尋找:",\r
+DlgReplaceReplaceLbl   : "取代:",\r
+DlgReplaceCaseChk              : "大小寫須相符",\r
+DlgReplaceReplaceBtn   : "取代",\r
+DlgReplaceReplAllBtn   : "全部取代",\r
+DlgReplaceWordChk              : "全字相符",\r
+\r
+// Paste Operations / Dialog\r
+PasteErrorCut  : "瀏覽器的安全性設定不允許編輯器自動執行剪下動作。請使用快捷鍵 (Ctrl+X) 剪下。",\r
+PasteErrorCopy : "瀏覽器的安全性設定不允許編輯器自動執行複製動作。請使用快捷鍵 (Ctrl+C) 複製。",\r
+\r
+PasteAsText            : "貼為純文字格式",\r
+PasteFromWord  : "自 Word 貼上",\r
+\r
+DlgPasteMsg2   : "請使用快捷鍵 (<strong>Ctrl+V</strong>) 貼到下方區域中並按下 <strong>確定</strong>",\r
+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\r
+DlgPasteIgnoreFont             : "移除字型設定",\r
+DlgPasteRemoveStyles   : "移除樣式設定",\r
+DlgPasteCleanBox               : "清除文字區域",\r
+\r
+// Color Picker\r
+ColorAutomatic : "自動",\r
+ColorMoreColors        : "更多顏色…",\r
+\r
+// Document Properties\r
+DocProps               : "文件屬性",\r
+\r
+// Anchor Dialog\r
+DlgAnchorTitle         : "命名錨點",\r
+DlgAnchorName          : "錨點名稱",\r
+DlgAnchorErrorName     : "請輸入錨點名稱",\r
+\r
+// Speller Pages Dialog\r
+DlgSpellNotInDic               : "不在字典中",\r
+DlgSpellChangeTo               : "更改為",\r
+DlgSpellBtnIgnore              : "忽略",\r
+DlgSpellBtnIgnoreAll   : "全部忽略",\r
+DlgSpellBtnReplace             : "取代",\r
+DlgSpellBtnReplaceAll  : "全部取代",\r
+DlgSpellBtnUndo                        : "復原",\r
+DlgSpellNoSuggestions  : "- 無建議值 -",\r
+DlgSpellProgress               : "進行拼字檢查中…",\r
+DlgSpellNoMispell              : "拼字檢查完成:未發現拼字錯誤",\r
+DlgSpellNoChanges              : "拼字檢查完成:未更改任何單字",\r
+DlgSpellOneChange              : "拼字檢查完成:更改了 1 個單字",\r
+DlgSpellManyChanges            : "拼字檢查完成:更改了 %1 個單字",\r
+\r
+IeSpellDownload                        : "尚未安裝拼字檢查元件。您是否想要現在下載?",\r
+\r
+// Button Dialog\r
+DlgButtonText          : "顯示文字 (值)",\r
+DlgButtonType          : "類型",\r
+DlgButtonTypeBtn       : "按鈕 (Button)",\r
+DlgButtonTypeSbm       : "送出 (Submit)",\r
+DlgButtonTypeRst       : "重設 (Reset)",\r
+\r
+// Checkbox and Radio Button Dialogs\r
+DlgCheckboxName                : "名稱",\r
+DlgCheckboxValue       : "選取值",\r
+DlgCheckboxSelected    : "已選取",\r
+\r
+// Form Dialog\r
+DlgFormName            : "名稱",\r
+DlgFormAction  : "動作",\r
+DlgFormMethod  : "方法",\r
+\r
+// Select Field Dialog\r
+DlgSelectName          : "名稱",\r
+DlgSelectValue         : "選取值",\r
+DlgSelectSize          : "大小",\r
+DlgSelectLines         : "行",\r
+DlgSelectChkMulti      : "可多選",\r
+DlgSelectOpAvail       : "可用選項",\r
+DlgSelectOpText                : "顯示文字",\r
+DlgSelectOpValue       : "值",\r
+DlgSelectBtnAdd                : "新增",\r
+DlgSelectBtnModify     : "修改",\r
+DlgSelectBtnUp         : "上移",\r
+DlgSelectBtnDown       : "下移",\r
+DlgSelectBtnSetValue : "設為預設值",\r
+DlgSelectBtnDelete     : "刪除",\r
+\r
+// Textarea Dialog\r
+DlgTextareaName        : "名稱",\r
+DlgTextareaCols        : "字元寬度",\r
+DlgTextareaRows        : "列數",\r
+\r
+// Text Field Dialog\r
+DlgTextName                    : "名稱",\r
+DlgTextValue           : "值",\r
+DlgTextCharWidth       : "字元寬度",\r
+DlgTextMaxChars                : "最多字元數",\r
+DlgTextType                    : "類型",\r
+DlgTextTypeText                : "文字",\r
+DlgTextTypePass                : "密碼",\r
+\r
+// Hidden Field Dialog\r
+DlgHiddenName  : "名稱",\r
+DlgHiddenValue : "值",\r
+\r
+// Bulleted List Dialog\r
+BulletedListProp       : "項目清單屬性",\r
+NumberedListProp       : "編號清單屬性",\r
+DlgLstStart                    : "起始編號",\r
+DlgLstType                     : "清單類型",\r
+DlgLstTypeCircle       : "圓圈",\r
+DlgLstTypeDisc         : "圓點",\r
+DlgLstTypeSquare       : "方塊",\r
+DlgLstTypeNumbers      : "數字 (1, 2, 3)",\r
+DlgLstTypeLCase                : "小寫字母 (a, b, c)",\r
+DlgLstTypeUCase                : "大寫字母 (A, B, C)",\r
+DlgLstTypeSRoman       : "小寫羅馬數字 (i, ii, iii)",\r
+DlgLstTypeLRoman       : "大寫羅馬數字 (I, II, III)",\r
+\r
+// Document Properties Dialog\r
+DlgDocGeneralTab       : "一般",\r
+DlgDocBackTab          : "背景",\r
+DlgDocColorsTab                : "顯色與邊界",\r
+DlgDocMetaTab          : "Meta 資料",\r
+\r
+DlgDocPageTitle                : "頁面標題",\r
+DlgDocLangDir          : "語言方向",\r
+DlgDocLangDirLTR       : "由左而右 (LTR)",\r
+DlgDocLangDirRTL       : "由右而左 (RTL)",\r
+DlgDocLangCode         : "語言代碼",\r
+DlgDocCharSet          : "字元編碼",\r
+DlgDocCharSetCE                : "中歐語系",\r
+DlgDocCharSetCT                : "正體中文 (Big5)",\r
+DlgDocCharSetCR                : "斯拉夫文",\r
+DlgDocCharSetGR                : "希臘文",\r
+DlgDocCharSetJP                : "日文",\r
+DlgDocCharSetKR                : "韓文",\r
+DlgDocCharSetTR                : "土耳其文",\r
+DlgDocCharSetUN                : "Unicode (UTF-8)",\r
+DlgDocCharSetWE                : "西歐語系",\r
+DlgDocCharSetOther     : "其他字元編碼",\r
+\r
+DlgDocDocType          : "文件類型",\r
+DlgDocDocTypeOther     : "其他文件類型",\r
+DlgDocIncXHTML         : "包含 XHTML 定義",\r
+DlgDocBgColor          : "背景顏色",\r
+DlgDocBgImage          : "背景影像",\r
+DlgDocBgNoScroll       : "浮水印",\r
+DlgDocCText                    : "文字",\r
+DlgDocCLink                    : "超連結",\r
+DlgDocCVisited         : "已瀏覽過的超連結",\r
+DlgDocCActive          : "作用中的超連結",\r
+DlgDocMargins          : "頁面邊界",\r
+DlgDocMaTop                    : "上",\r
+DlgDocMaLeft           : "左",\r
+DlgDocMaRight          : "右",\r
+DlgDocMaBottom         : "下",\r
+DlgDocMeIndex          : "文件索引關鍵字 (用半形逗號[,]分隔)",\r
+DlgDocMeDescr          : "文件說明",\r
+DlgDocMeAuthor         : "作者",\r
+DlgDocMeCopy           : "版權所有",\r
+DlgDocPreview          : "預覽",\r
+\r
+// Templates Dialog\r
+Templates                      : "樣版",\r
+DlgTemplatesTitle      : "內容樣版",\r
+DlgTemplatesSelMsg     : "請選擇欲開啟的樣版<br> (原有的內容將會被清除):",\r
+DlgTemplatesLoading    : "讀取樣版清單中,請稍候…",\r
+DlgTemplatesNoTpl      : "(無樣版)",\r
+DlgTemplatesReplace    : "取代原有內容",\r
+\r
+// About Dialog\r
+DlgAboutAboutTab       : "關於",\r
+DlgAboutBrowserInfoTab : "瀏覽器資訊",\r
+DlgAboutLicenseTab     : "許可證",\r
+DlgAboutVersion                : "版本",\r
+DlgAboutInfo           : "想獲得更多資訊請至 "\r
+};
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/plugins/autogrow/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/autogrow/fckplugin.js
new file mode 100644 (file)
index 0000000..7ce1c1c
--- /dev/null
@@ -0,0 +1,92 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Plugin: automatically resizes the editor until a configurable maximun\r
+ * height (FCKConfig.AutoGrowMax), based on its contents.\r
+ */\r
+\r
+var FCKAutoGrow_Min = window.frameElement.offsetHeight ;\r
+\r
+function FCKAutoGrow_Check()\r
+{\r
+       var oInnerDoc = FCK.EditorDocument ;\r
+\r
+       var iFrameHeight, iInnerHeight ;\r
+\r
+       if ( FCKBrowserInfo.IsIE )\r
+       {\r
+               iFrameHeight = FCK.EditorWindow.frameElement.offsetHeight ;\r
+               iInnerHeight = oInnerDoc.body.scrollHeight ;\r
+       }\r
+       else\r
+       {\r
+               iFrameHeight = FCK.EditorWindow.innerHeight ;\r
+               iInnerHeight = oInnerDoc.body.offsetHeight ;\r
+       }\r
+\r
+       var iDiff = iInnerHeight - iFrameHeight ;\r
+\r
+       if ( iDiff != 0 )\r
+       {\r
+               var iMainFrameSize = window.frameElement.offsetHeight ;\r
+\r
+               if ( iDiff > 0 && iMainFrameSize < FCKConfig.AutoGrowMax )\r
+               {\r
+                       iMainFrameSize += iDiff ;\r
+                       if ( iMainFrameSize > FCKConfig.AutoGrowMax )\r
+                               iMainFrameSize = FCKConfig.AutoGrowMax ;\r
+               }\r
+               else if ( iDiff < 0 && iMainFrameSize > FCKAutoGrow_Min )\r
+               {\r
+                       iMainFrameSize += iDiff ;\r
+                       if ( iMainFrameSize < FCKAutoGrow_Min )\r
+                               iMainFrameSize = FCKAutoGrow_Min ;\r
+               }\r
+               else\r
+                       return ;\r
+\r
+               window.frameElement.height = iMainFrameSize ;\r
+       }\r
+}\r
+\r
+FCK.AttachToOnSelectionChange( FCKAutoGrow_Check ) ;\r
+\r
+function FCKAutoGrow_SetListeners()\r
+{\r
+       if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )\r
+               return ;\r
+\r
+       FCK.EditorWindow.attachEvent( 'onscroll', FCKAutoGrow_Check ) ;\r
+       FCK.EditorDocument.attachEvent( 'onkeyup', FCKAutoGrow_Check ) ;\r
+}\r
+\r
+if ( FCKBrowserInfo.IsIE )\r
+{\r
+//     FCKAutoGrow_SetListeners() ;\r
+       FCK.Events.AttachEvent( 'OnAfterSetHTML', FCKAutoGrow_SetListeners ) ;\r
+}\r
+\r
+function FCKAutoGrow_CheckEditorStatus( sender, status )\r
+{\r
+       if ( status == FCK_STATUS_COMPLETE )\r
+               FCKAutoGrow_Check() ;\r
+}\r
+\r
+FCK.Events.AttachEvent( 'OnStatusChange', FCKAutoGrow_CheckEditorStatus ) ;
\ No newline at end of file
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 (file)
index 0000000..a334206
--- /dev/null
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Placeholder Plugin.\r
+-->\r
+<html>\r
+       <head>\r
+               <title>Placeholder Properties</title>\r
+               <meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+               <meta content="noindex, nofollow" name="robots">\r
+               <script language="javascript">\r
+\r
+var oEditor = window.parent.InnerDialogLoaded() ;\r
+var FCKLang = oEditor.FCKLang ;\r
+var FCKPlaceholders = oEditor.FCKPlaceholders ;\r
+\r
+window.onload = function ()\r
+{\r
+       // First of all, translate the dialog box texts\r
+       oEditor.FCKLanguageManager.TranslatePage( document ) ;\r
+\r
+       LoadSelected() ;\r
+\r
+       // Show the "Ok" button.\r
+       window.parent.SetOkButton( true ) ;\r
+}\r
+\r
+var eSelected = oEditor.FCKSelection.GetSelectedElement() ;\r
+\r
+function LoadSelected()\r
+{\r
+       if ( !eSelected )\r
+               return ;\r
+\r
+       if ( eSelected.tagName == 'SPAN' && eSelected._fckplaceholder )\r
+               document.getElementById('txtName').value = eSelected._fckplaceholder ;\r
+       else\r
+               eSelected == null ;\r
+}\r
+\r
+function Ok()\r
+{\r
+       var sValue = document.getElementById('txtName').value ;\r
+\r
+       if ( eSelected && eSelected._fckplaceholder == sValue )\r
+               return true ;\r
+\r
+       if ( sValue.length == 0 )\r
+       {\r
+               alert( FCKLang.PlaceholderErrNoName ) ;\r
+               return false ;\r
+       }\r
+\r
+       if ( FCKPlaceholders.Exist( sValue ) )\r
+       {\r
+               alert( FCKLang.PlaceholderErrNameInUse ) ;\r
+               return false ;\r
+       }\r
+\r
+       FCKPlaceholders.Add( sValue ) ;\r
+       return true ;\r
+}\r
+\r
+               </script>\r
+       </head>\r
+       <body scroll="no" style="OVERFLOW: hidden">\r
+               <table height="100%" cellSpacing="0" cellPadding="0" width="100%" border="0">\r
+                       <tr>\r
+                               <td>\r
+                                       <table cellSpacing="0" cellPadding="0" align="center" border="0">\r
+                                               <tr>\r
+                                                       <td>\r
+                                                               <span fckLang="PlaceholderDlgName">Placeholder Name</span><br>\r
+                                                               <input id="txtName" type="text">\r
+                                                       </td>\r
+                                               </tr>\r
+                                       </table>\r
+                               </td>\r
+                       </tr>\r
+               </table>\r
+       </body>\r
+</html>
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/placeholder/fckplugin.js
new file mode 100644 (file)
index 0000000..26489b8
--- /dev/null
@@ -0,0 +1,187 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Plugin to insert "Placeholders" in the editor.\r
+ */\r
+\r
+// Register the related command.\r
+FCKCommands.RegisterCommand( 'Placeholder', new FCKDialogCommand( 'Placeholder', FCKLang.PlaceholderDlgTitle, FCKPlugins.Items['placeholder'].Path + 'fck_placeholder.html', 340, 170 ) ) ;\r
+\r
+// Create the "Plaholder" toolbar button.\r
+var oPlaceholderItem = new FCKToolbarButton( 'Placeholder', FCKLang.PlaceholderBtn ) ;\r
+oPlaceholderItem.IconPath = FCKPlugins.Items['placeholder'].Path + 'placeholder.gif' ;\r
+\r
+FCKToolbarItems.RegisterItem( 'Placeholder', oPlaceholderItem ) ;\r
+\r
+\r
+// The object used for all Placeholder operations.\r
+var FCKPlaceholders = new Object() ;\r
+\r
+// Add a new placeholder at the actual selection.\r
+FCKPlaceholders.Add = function( name )\r
+{\r
+       var oSpan = FCK.CreateElement( 'SPAN' ) ;\r
+       this.SetupSpan( oSpan, name ) ;\r
+}\r
+\r
+FCKPlaceholders.SetupSpan = function( span, name )\r
+{\r
+       span.innerHTML = '[[ ' + name + ' ]]' ;\r
+\r
+       span.style.backgroundColor = '#ffff00' ;\r
+       span.style.color = '#000000' ;\r
+\r
+       if ( FCKBrowserInfo.IsGecko )\r
+               span.style.cursor = 'default' ;\r
+\r
+       span._fckplaceholder = name ;\r
+       span.contentEditable = false ;\r
+\r
+       // To avoid it to be resized.\r
+       span.onresizestart = function()\r
+       {\r
+               FCK.EditorWindow.event.returnValue = false ;\r
+               return false ;\r
+       }\r
+}\r
+\r
+// On Gecko we must do this trick so the user select all the SPAN when clicking on it.\r
+FCKPlaceholders._SetupClickListener = function()\r
+{\r
+       FCKPlaceholders._ClickListener = function( e )\r
+       {\r
+               if ( e.target.tagName == 'SPAN' && e.target._fckplaceholder )\r
+                       FCKSelection.SelectNode( e.target ) ;\r
+       }\r
+\r
+       FCK.EditorDocument.addEventListener( 'click', FCKPlaceholders._ClickListener, true ) ;\r
+}\r
+\r
+// Open the Placeholder dialog on double click.\r
+FCKPlaceholders.OnDoubleClick = function( span )\r
+{\r
+       if ( span.tagName == 'SPAN' && span._fckplaceholder )\r
+               FCKCommands.GetCommand( 'Placeholder' ).Execute() ;\r
+}\r
+\r
+FCK.RegisterDoubleClickHandler( FCKPlaceholders.OnDoubleClick, 'SPAN' ) ;\r
+\r
+// Check if a Placholder name is already in use.\r
+FCKPlaceholders.Exist = function( name )\r
+{\r
+       var aSpans = FCK.EditorDocument.getElementsByTagName( 'SPAN' ) ;\r
+\r
+       for ( var i = 0 ; i < aSpans.length ; i++ )\r
+       {\r
+               if ( aSpans[i]._fckplaceholder == name )\r
+                       return true ;\r
+       }\r
+\r
+       return false ;\r
+}\r
+\r
+if ( FCKBrowserInfo.IsIE )\r
+{\r
+       FCKPlaceholders.Redraw = function()\r
+       {\r
+               if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )\r
+                       return ;\r
+\r
+               var aPlaholders = FCK.EditorDocument.body.innerText.match( /\[\[[^\[\]]+\]\]/g ) ;\r
+               if ( !aPlaholders )\r
+                       return ;\r
+\r
+               var oRange = FCK.EditorDocument.body.createTextRange() ;\r
+\r
+               for ( var i = 0 ; i < aPlaholders.length ; i++ )\r
+               {\r
+                       if ( oRange.findText( aPlaholders[i] ) )\r
+                       {\r
+                               var sName = aPlaholders[i].match( /\[\[\s*([^\]]*?)\s*\]\]/ )[1] ;\r
+                               oRange.pasteHTML( '<span style="color: #000000; background-color: #ffff00" contenteditable="false" _fckplaceholder="' + sName + '">' + aPlaholders[i] + '</span>' ) ;\r
+                       }\r
+               }\r
+       }\r
+}\r
+else\r
+{\r
+       FCKPlaceholders.Redraw = function()\r
+       {\r
+               if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG )\r
+                       return ;\r
+\r
+               var oInteractor = FCK.EditorDocument.createTreeWalker( FCK.EditorDocument.body, NodeFilter.SHOW_TEXT, FCKPlaceholders._AcceptNode, true ) ;\r
+\r
+               var     aNodes = new Array() ;\r
+\r
+               while ( ( oNode = oInteractor.nextNode() ) )\r
+               {\r
+                       aNodes[ aNodes.length ] = oNode ;\r
+               }\r
+\r
+               for ( var n = 0 ; n < aNodes.length ; n++ )\r
+               {\r
+                       var aPieces = aNodes[n].nodeValue.split( /(\[\[[^\[\]]+\]\])/g ) ;\r
+\r
+                       for ( var i = 0 ; i < aPieces.length ; i++ )\r
+                       {\r
+                               if ( aPieces[i].length > 0 )\r
+                               {\r
+                                       if ( aPieces[i].indexOf( '[[' ) == 0 )\r
+                                       {\r
+                                               var sName = aPieces[i].match( /\[\[\s*([^\]]*?)\s*\]\]/ )[1] ;\r
+\r
+                                               var oSpan = FCK.EditorDocument.createElement( 'span' ) ;\r
+                                               FCKPlaceholders.SetupSpan( oSpan, sName ) ;\r
+\r
+                                               aNodes[n].parentNode.insertBefore( oSpan, aNodes[n] ) ;\r
+                                       }\r
+                                       else\r
+                                               aNodes[n].parentNode.insertBefore( FCK.EditorDocument.createTextNode( aPieces[i] ) , aNodes[n] ) ;\r
+                               }\r
+                       }\r
+\r
+                       aNodes[n].parentNode.removeChild( aNodes[n] ) ;\r
+               }\r
+\r
+               FCKPlaceholders._SetupClickListener() ;\r
+       }\r
+\r
+       FCKPlaceholders._AcceptNode = function( node )\r
+       {\r
+               if ( /\[\[[^\[\]]+\]\]/.test( node.nodeValue ) )\r
+                       return NodeFilter.FILTER_ACCEPT ;\r
+               else\r
+                       return NodeFilter.FILTER_SKIP ;\r
+       }\r
+}\r
+\r
+FCK.Events.AttachEvent( 'OnAfterSetHTML', FCKPlaceholders.Redraw ) ;\r
+\r
+// We must process the SPAN tags to replace then with the real resulting value of the placeholder.\r
+FCKXHtml.TagProcessors['span'] = function( node, htmlNode )\r
+{\r
+       if ( htmlNode._fckplaceholder )\r
+               node = FCKXHtml.XML.createTextNode( '[[' + htmlNode._fckplaceholder + ']]' ) ;\r
+       else\r
+               FCKXHtml._AppendChildNodes( node, htmlNode, false ) ;\r
+\r
+       return node ;\r
+}
\ No newline at end of file
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 (file)
index 0000000..a666f8b
--- /dev/null
@@ -0,0 +1,27 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Placholder German language file.\r
+ */\r
+FCKLang.PlaceholderBtn                 = 'Einfügen/editieren Platzhalter' ;\r
+FCKLang.PlaceholderDlgTitle            = 'Platzhalter Eigenschaften' ;\r
+FCKLang.PlaceholderDlgName             = 'Platzhalter Name' ;\r
+FCKLang.PlaceholderErrNoName   = 'Bitte den Namen des Platzhalters schreiben' ;\r
+FCKLang.PlaceholderErrNameInUse        = 'Der angegebene Namen ist schon in Gebrauch' ;
\ No newline at end of file
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 (file)
index 0000000..290a3fb
--- /dev/null
@@ -0,0 +1,27 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Placholder English language file.\r
+ */\r
+FCKLang.PlaceholderBtn                 = 'Insert/Edit Placeholder' ;\r
+FCKLang.PlaceholderDlgTitle            = 'Placeholder Properties' ;\r
+FCKLang.PlaceholderDlgName             = 'Placeholder Name' ;\r
+FCKLang.PlaceholderErrNoName   = 'Please type the placeholder name' ;\r
+FCKLang.PlaceholderErrNameInUse        = 'The specified name is already in use' ;
\ No newline at end of file
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 (file)
index 0000000..f5ac26e
--- /dev/null
@@ -0,0 +1,27 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Placeholder French language file.\r
+ */\r
+FCKLang.PlaceholderBtn                 = "Insérer/Modifier l'Espace réservé" ;\r
+FCKLang.PlaceholderDlgTitle            = "Propriétés de l'Espace réservé" ;\r
+FCKLang.PlaceholderDlgName             = "Nom de l'Espace réservé" ;\r
+FCKLang.PlaceholderErrNoName   = "Veuillez saisir le nom de l'Espace réservé" ;\r
+FCKLang.PlaceholderErrNameInUse        = "Ce nom est déjà utilisé" ;\r
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 (file)
index 0000000..51d75c0
--- /dev/null
@@ -0,0 +1,27 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Placholder Italian language file.\r
+ */\r
+FCKLang.PlaceholderBtn                 = 'Aggiungi/Modifica Placeholder' ;\r
+FCKLang.PlaceholderDlgTitle            = 'Proprietà del Placeholder' ;\r
+FCKLang.PlaceholderDlgName             = 'Nome del Placeholder' ;\r
+FCKLang.PlaceholderErrNoName   = 'Digitare il nome del placeholder' ;\r
+FCKLang.PlaceholderErrNameInUse        = 'Il nome inserito è già in uso' ;\r
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 (file)
index 0000000..bc55b38
--- /dev/null
@@ -0,0 +1,27 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Placholder Polish language file.\r
+ */\r
+FCKLang.PlaceholderBtn                 = 'Wstaw/Edytuj nagłówek' ;\r
+FCKLang.PlaceholderDlgTitle            = 'Właśności nagłówka' ;\r
+FCKLang.PlaceholderDlgName             = 'Nazwa nagłówka' ;\r
+FCKLang.PlaceholderErrNoName   = 'Proszę wprowadzić nazwę nagłówka' ;\r
+FCKLang.PlaceholderErrNameInUse        = 'Podana nazwa jest już w użyciu' ;
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gif b/httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gif
new file mode 100644 (file)
index 0000000..c07078c
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/plugins/placeholder/placeholder.gif 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 (file)
index 0000000..cd25b6a
--- /dev/null
@@ -0,0 +1,29 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This plugin register Toolbar items for the combos modifying the style to\r
+ * not show the box.\r
+ */\r
+\r
+FCKToolbarItems.RegisterItem( 'SourceSimple'   , new FCKToolbarButton( 'Source', FCKLang.Source, null, FCK_TOOLBARITEM_ONLYICON, true, true, 1 ) ) ;\r
+FCKToolbarItems.RegisterItem( 'StyleSimple'            , new FCKToolbarStyleCombo( null, FCK_TOOLBARITEM_ONLYTEXT ) ) ;\r
+FCKToolbarItems.RegisterItem( 'FontNameSimple' , new FCKToolbarFontsCombo( null, FCK_TOOLBARITEM_ONLYTEXT ) ) ;\r
+FCKToolbarItems.RegisterItem( 'FontSizeSimple' , new FCKToolbarFontSizeCombo( null, FCK_TOOLBARITEM_ONLYTEXT ) ) ;\r
+FCKToolbarItems.RegisterItem( 'FontFormatSimple', new FCKToolbarFontFormatCombo( null, FCK_TOOLBARITEM_ONLYTEXT ) ) ;\r
diff --git a/httemplate/elements/fckeditor/editor/plugins/tablecommands/fckplugin.js b/httemplate/elements/fckeditor/editor/plugins/tablecommands/fckplugin.js
new file mode 100644 (file)
index 0000000..88dac9c
--- /dev/null
@@ -0,0 +1,32 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This plugin register the required Toolbar items to be able to insert the\r
+ * toolbar commands in the toolbar.\r
+ */\r
+\r
+FCKToolbarItems.RegisterItem( 'TableInsertRow'         , new FCKToolbarButton( 'TableInsertRow'        , FCKLang.InsertRow, null, null, null, null, 62 ) ) ;\r
+FCKToolbarItems.RegisterItem( 'TableDeleteRows'                , new FCKToolbarButton( 'TableDeleteRows'       , FCKLang.DeleteRows, null, null, null, null, 63 ) ) ;\r
+FCKToolbarItems.RegisterItem( 'TableInsertColumn'      , new FCKToolbarButton( 'TableInsertColumn'     , FCKLang.InsertColumn, null, null, null, null, 64 ) ) ;\r
+FCKToolbarItems.RegisterItem( 'TableDeleteColumns'     , new FCKToolbarButton( 'TableDeleteColumns', FCKLang.DeleteColumns, null, null, null, null, 65 ) ) ;\r
+FCKToolbarItems.RegisterItem( 'TableInsertCell'                , new FCKToolbarButton( 'TableInsertCell'       , FCKLang.InsertCell, null, null, null, null, 58 ) ) ;\r
+FCKToolbarItems.RegisterItem( 'TableDeleteCells'       , new FCKToolbarButton( 'TableDeleteCells'      , FCKLang.DeleteCells, null, null, null, null, 59 ) ) ;\r
+FCKToolbarItems.RegisterItem( 'TableMergeCells'                , new FCKToolbarButton( 'TableMergeCells'       , FCKLang.MergeCells, null, null, null, null, 60 ) ) ;\r
+FCKToolbarItems.RegisterItem( 'TableSplitCell'         , new FCKToolbarButton( 'TableSplitCell'        , FCKLang.SplitCell, null, null, null, null, 61 ) ) ;
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/editor/skins/_fckviewstrips.html b/httemplate/elements/fckeditor/editor/skins/_fckviewstrips.html
new file mode 100644 (file)
index 0000000..b28b941
--- /dev/null
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Useful page that enumerates all icons in the skins strips.\r
+-->\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+       <title>FCKeditor - View Icons Strips</title>\r
+       <style type="text/css">\r
+               .TB_Button_Image\r
+               {\r
+                       overflow: hidden;\r
+                       width: 16px;\r
+                       height: 16px;\r
+                       margin: 3px;\r
+                       background-repeat: no-repeat;\r
+               }\r
+\r
+               .TB_Button_Image img\r
+               {\r
+                       position: relative;\r
+               }\r
+       </style>\r
+       <script type="text/javascript">\r
+\r
+window.onload = function()\r
+{\r
+       var eImg1 = document.createElement( 'img' ) ;\r
+       eImg1.onload = Img_OnLoad ;\r
+       eImg1.src = 'default/fck_strip.gif' ;\r
+\r
+       var eImg2 = document.createElement( 'img' ) ;\r
+       eImg2.onload = Img_OnLoad ;\r
+       eImg2.src = 'office2003/fck_strip.gif' ;\r
+\r
+       var eImg3 = document.createElement( 'img' ) ;\r
+       eImg3.onload = Img_OnLoad ;\r
+       eImg3.src = 'silver/fck_strip.gif' ;\r
+}\r
+\r
+var iTotalStrips = 3 ;\r
+var iMaxHeight = 0 ;\r
+\r
+function Img_OnLoad()\r
+{\r
+       if ( iMaxHeight < this.height )\r
+               iMaxHeight = this.height ;\r
+\r
+       iTotalStrips-- ;\r
+\r
+       if ( iTotalStrips == 0 )\r
+               LoadIcons( iMaxHeight / 16 ) ;\r
+}\r
+\r
+function LoadIcons( total )\r
+{\r
+       var xIconsTable = document.getElementById( 'xIconsTable' ) ;\r
+\r
+       for ( var i = 0 ; i < total ; i++ )\r
+       {\r
+               var eRow = xIconsTable.insertRow(-1) ;\r
+\r
+               var eCell = eRow.insertCell(-1) ;\r
+               eCell.innerHTML = i + 1 ;\r
+\r
+               eCell = eRow.insertCell(-1) ;\r
+               eCell.align = 'center' ;\r
+               eCell.style.border = '#dcdcdc 1px solid' ;\r
+               eCell.innerHTML = '<div class="TB_Button_Image"><img src="default/fck_strip.gif" style="top:-' + ( i * 16 ) + 'px;"><\/div>' ;\r
+\r
+               eCell = eRow.insertCell(-1) ;\r
+               eCell.align = 'center' ;\r
+               eCell.style.border = '#dcdcdc 1px solid' ;\r
+               eCell.innerHTML = '<div class="TB_Button_Image"><img src="office2003/fck_strip.gif" style="top:-' + ( i * 16 ) + 'px;"><\/div>' ;\r
+\r
+               eCell = eRow.insertCell(-1) ;\r
+               eCell.align = 'center' ;\r
+               eCell.style.border = '#dcdcdc 1px solid' ;\r
+               eCell.innerHTML = '<div class="TB_Button_Image"><img src="silver/fck_strip.gif" style="top:-' + ( i * 16 ) + 'px;"><\/div>' ;\r
+       }\r
+}\r
+\r
+       </script>\r
+</head>\r
+<body>\r
+       <table id="xIconsTable">\r
+               <tr>\r
+                       <td rowspan="2">\r
+                               Index</td>\r
+                       <td align="center" colspan="3">\r
+                               Skins</td>\r
+               </tr>\r
+               <tr>\r
+                       <td width="80" align="center">\r
+                               default</td>\r
+                       <td width="80" align="center">\r
+                               office2003</td>\r
+                       <td width="80" align="center">\r
+                               silver</td>\r
+               </tr>\r
+       </table>\r
+</body>\r
+</html>\r
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 (file)
index 0000000..9725f6c
--- /dev/null
@@ -0,0 +1,137 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Styles used by the dialog boxes.\r
+ */\r
+\r
+body\r
+{\r
+       margin: 0px;\r
+       padding: 10px;\r
+}\r
+\r
+body, td, input, select, textarea\r
+{\r
+       font-size: 11px;\r
+       font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;\r
+}\r
+\r
+body, .BackColor\r
+{\r
+       background-color: #f1f1e3;\r
+}\r
+\r
+.PopupBody\r
+{\r
+       margin: 0px;\r
+       padding: 0px;\r
+}\r
+\r
+.PopupTitle\r
+{\r
+       font-weight: bold;\r
+       font-size: 14pt;\r
+       color: #737357;\r
+       background-color: #e3e3c7;\r
+       padding: 3px 10px 3px 10px;\r
+}\r
+\r
+.PopupButtons\r
+{\r
+       border-top: #d5d59d 1px solid;\r
+       background-color: #e3e3c7;\r
+       padding: 7px 10px 7px 10px;\r
+}\r
+\r
+.Button\r
+{\r
+       border: #737357 1px solid;\r
+       color: #3b3b1f;\r
+       background-color: #c7c78f;\r
+}\r
+\r
+#btnOk\r
+{\r
+       width: 100px;\r
+}\r
+\r
+.DarkBackground\r
+{\r
+       background-color: #d7d79f;\r
+}\r
+\r
+.LightBackground\r
+{\r
+       background-color: #ffffbe;\r
+}\r
+\r
+.PopupTitleBorder\r
+{\r
+       border-bottom: #d5d59d 1px solid;\r
+}\r
+\r
+.PopupTabArea\r
+{\r
+       color: #737357;\r
+       background-color: #e3e3c7;\r
+}\r
+\r
+.PopupTabEmptyArea\r
+{\r
+       padding-left: 10px ;\r
+       border-bottom: #d5d59d 1px solid;\r
+}\r
+\r
+.PopupTab, .PopupTabSelected\r
+{\r
+       border-right: #d5d59d 1px solid;\r
+       border-top: #d5d59d 1px solid;\r
+       border-left: #d5d59d 1px solid;\r
+       padding-right: 5px;\r
+       padding-left: 5px;\r
+       padding-bottom: 3px;\r
+       padding-top: 3px;\r
+       color: #737357;\r
+}\r
+\r
+.PopupTab\r
+{\r
+       margin-top: 1px;\r
+       border-bottom: #d5d59d 1px solid;\r
+       cursor: pointer;\r
+       cursor: hand;\r
+}\r
+\r
+.PopupTabSelected\r
+{\r
+       font-weight:bold;\r
+       cursor: default;\r
+       padding-top: 4px;\r
+       border-bottom: #f1f1e3 1px solid;\r
+       background-color: #f1f1e3;\r
+}\r
+\r
+.PopupSelectionBox\r
+{\r
+       border: #ff9933 1px solid !important;\r
+       background-color: #fffacd !important;\r
+       cursor: pointer;\r
+       cursor: hand;\r
+}
\ No newline at end of file
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 (file)
index 0000000..b849cca
--- /dev/null
@@ -0,0 +1,464 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Styles used by the editor IFRAME and Toolbar.\r
+ */\r
+\r
+/*\r
+       ### Basic Editor IFRAME Styles.\r
+*/\r
+\r
+body\r
+{\r
+    padding: 1px 1px 1px 1px;\r
+    margin: 0px 0px 0px 0px;\r
+}\r
+\r
+#xEditingArea\r
+{\r
+    border: #696969 1px solid;\r
+}\r
+\r
+.SourceField\r
+{\r
+    padding: 5px;\r
+    margin: 0px;\r
+    font-family: Monospace;\r
+}\r
+\r
+/*\r
+       Toolbar\r
+*/\r
+\r
+.TB_ToolbarSet, .TB_Expand, .TB_Collapse\r
+{\r
+    cursor: default;\r
+    background-color: #efefde;\r
+}\r
+\r
+.TB_ToolbarSet\r
+{\r
+    border-top: #efefde 1px outset;\r
+    border-bottom: #efefde 1px outset;\r
+}\r
+\r
+.TB_ToolbarSet TD\r
+{\r
+    font-size: 11px;\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.TB_Toolbar\r
+{\r
+       height: 24px;\r
+    display: inline-table;     /* inline = Opera jumping buttons bug */\r
+}\r
+\r
+.TB_Separator\r
+{\r
+    width: 1px;\r
+    height: 16px;\r
+    margin: 2px;\r
+    background-color: #999966;\r
+}\r
+\r
+.TB_Start\r
+{\r
+    background-image: url(images/toolbar.start.gif);\r
+    margin: 2px;\r
+    width: 3px;\r
+    background-repeat: no-repeat;\r
+    height: 16px;\r
+}\r
+\r
+.TB_End\r
+{\r
+    display: none;\r
+}\r
+\r
+.TB_ExpandImg\r
+{\r
+    background-image: url(images/toolbar.expand.gif);\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.TB_CollapseImg\r
+{\r
+    background-image: url(images/toolbar.collapse.gif);\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.TB_SideBorder\r
+{\r
+    background-color: #696969;\r
+}\r
+\r
+.TB_Expand, .TB_Collapse\r
+{\r
+    padding: 2px 2px 2px 2px;\r
+    border: #efefde 1px outset;\r
+}\r
+\r
+.TB_Collapse\r
+{\r
+    width: 5px;\r
+}\r
+\r
+.TB_Break\r
+{\r
+    height: 24px; /* IE needs the height to be set, otherwise no break */\r
+}\r
+\r
+/*\r
+       Toolbar Button\r
+*/\r
+\r
+.TB_Button_On, .TB_Button_Off, .TB_Button_On_Over, .TB_Button_Off_Over, .TB_Button_Disabled\r
+{\r
+    border: #efefde 1px solid; /* This is the default border */\r
+    height: 22px; /* The height is necessary, otherwise IE will not apply the alpha */\r
+}\r
+\r
+.TB_Button_On\r
+{\r
+    border: #316ac5 1px solid;\r
+    background-color: #c1d2ee;\r
+}\r
+\r
+.TB_Button_On_Over, .TB_Button_Off_Over\r
+{\r
+    border: #316ac5 1px solid;\r
+    background-color: #dff1ff;\r
+}\r
+\r
+.TB_Button_Off\r
+{\r
+    filter: alpha(opacity=70); /* IE */\r
+    opacity: 0.70; /* Safari, Opera and Mozilla */\r
+}\r
+\r
+.TB_Button_Disabled\r
+{\r
+    filter: gray() alpha(opacity=30); /* IE */\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+}\r
+\r
+.TB_Button_Padding\r
+{\r
+    visibility: hidden;\r
+    width: 3px;\r
+    height: 22px;\r
+}\r
+\r
+.TB_Button_Image\r
+{\r
+    overflow: hidden;\r
+    width: 16px;\r
+    height: 16px;\r
+    margin: 3px;\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.TB_Button_Image img\r
+{\r
+    position: relative;\r
+}\r
+\r
+.TB_Button_Off .TB_Button_Text\r
+{\r
+       background-color: #efefde;  /* Needed because of a bug on Clear Type */\r
+}\r
+\r
+.TB_ConnectionLine\r
+{\r
+    background-color: #ffffff;\r
+    height: 1px;\r
+    margin-left: 1px;   /* ltr */\r
+    margin-right: 1px;  /* rtl */\r
+}\r
+\r
+.TB_Text\r
+{\r
+       height: 22px;\r
+}\r
+\r
+.TB_Button_Off .TB_Text\r
+{\r
+       background-color: #efefde ;  /* Needed because of a bug on ClearType */\r
+}\r
+\r
+.TB_Button_On_Over .TB_Text\r
+{\r
+       background-color: #dff1ff ;  /* Needed because of a bug on ClearType */\r
+}\r
+\r
+/*\r
+       Menu\r
+*/\r
+\r
+.MN_Menu\r
+{\r
+    border: 1px solid #8f8f73;\r
+    padding: 2px;\r
+    background-color: #ffffff;\r
+    cursor: default;\r
+}\r
+\r
+.MN_Menu, .MN_Menu .MN_Label\r
+{\r
+    font-size: 11px;\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.MN_Item_Padding\r
+{\r
+    visibility: hidden;\r
+    width: 3px;\r
+    height: 20px;\r
+}\r
+\r
+.MN_Icon\r
+{\r
+    background-color: #e3e3c7;\r
+    text-align: center;\r
+    height: 20px;\r
+}\r
+\r
+.MN_Label\r
+{\r
+    padding-left: 3px;\r
+    padding-right: 3px;\r
+}\r
+\r
+.MN_Separator\r
+{\r
+    height: 3px;\r
+}\r
+\r
+.MN_Separator_Line\r
+{\r
+    border-top: #b9b99d 1px solid;\r
+}\r
+\r
+.MN_Item .MN_Icon IMG\r
+{\r
+    filter: alpha(opacity=70);\r
+    opacity: 0.70;\r
+}\r
+\r
+.MN_Item_Over\r
+{\r
+    color: #ffffff;\r
+    background-color: #8f8f73;\r
+}\r
+\r
+.MN_Item_Over .MN_Icon\r
+{\r
+    background-color: #737357;\r
+}\r
+\r
+.MN_Item_Disabled IMG\r
+{\r
+    filter: gray() alpha(opacity=30); /* IE */\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+}\r
+\r
+.MN_Item_Disabled .MN_Label\r
+{\r
+    color: #b7b7b7;\r
+}\r
+\r
+.MN_Arrow\r
+{\r
+    padding-right: 3px;\r
+    padding-left: 3px;\r
+}\r
+\r
+.MN_ConnectionLine\r
+{\r
+    background-color: #ffffff;\r
+}\r
+\r
+.Menu .TB_Button_On, .Menu .TB_Button_On_Over\r
+{\r
+    border: #8f8f73 1px solid;\r
+    background-color: #ffffff;\r
+}\r
+\r
+/*\r
+       ### Panel Styles\r
+*/\r
+\r
+.FCK_Panel\r
+{\r
+    border: #8f8f73 1px solid;\r
+    padding: 2px;\r
+    background-color: #ffffff;\r
+}\r
+\r
+.FCK_Panel, .FCK_Panel TD\r
+{\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+    font-size: 11px;\r
+}\r
+\r
+/*\r
+       ### Special Combos\r
+*/\r
+\r
+.SC_Panel\r
+{\r
+    overflow: auto;\r
+    white-space: nowrap;\r
+    cursor: default;\r
+    border: 1px solid #8f8f73;\r
+    padding-left: 2px;\r
+    padding-right: 2px;\r
+    background-color: #ffffff;\r
+}\r
+\r
+.SC_Panel, .SC_Panel TD\r
+{\r
+    font-size: 11px;\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.SC_Item, .SC_ItemSelected\r
+{\r
+    margin-top: 2px;\r
+    margin-bottom: 2px;\r
+    background-position: left center;\r
+    padding-left: 11px;\r
+    padding-right: 3px;\r
+    padding-top: 2px;\r
+    padding-bottom: 2px;\r
+    text-overflow: ellipsis;\r
+    overflow: hidden;\r
+    background-repeat: no-repeat;\r
+    border: #dddddd 1px solid;\r
+}\r
+\r
+.SC_Item *, .SC_ItemSelected *\r
+{\r
+    margin-top: 0px;\r
+    margin-bottom: 0px;\r
+}\r
+\r
+.SC_ItemSelected\r
+{\r
+    border: #9a9afb 1px solid;\r
+    background-image: url(images/toolbar.arrowright.gif);\r
+}\r
+\r
+.SC_ItemOver\r
+{\r
+    border: #316ac5 1px solid;\r
+}\r
+\r
+.SC_Field\r
+{\r
+    border: #b7b7a6 1px solid;\r
+    cursor: default;\r
+}\r
+\r
+.SC_FieldCaption\r
+{\r
+    overflow: visible;\r
+    padding-right: 5px;\r
+    padding-left: 5px;\r
+    opacity: 0.75; /* Safari, Opera and Mozilla */\r
+    filter: alpha(opacity=70); /* IE */ /* -moz-opacity: 0.75; Mozilla (Old) */\r
+    height: 23px;\r
+    background-color: #efefde;\r
+}\r
+\r
+.SC_FieldLabel\r
+{\r
+    white-space: nowrap;\r
+    padding: 2px;\r
+    width: 100%;\r
+    cursor: default;\r
+    background-color: #ffffff;\r
+    text-overflow: ellipsis;\r
+    overflow: hidden;\r
+}\r
+\r
+.SC_FieldButton\r
+{\r
+    background-position: center center;\r
+    background-image: url(images/toolbar.buttonarrow.gif);\r
+    border-left: #b7b7a6 1px solid;\r
+    width: 14px;\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.SC_FieldDisabled .SC_FieldButton, .SC_FieldDisabled .SC_FieldCaption\r
+{\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+    filter: gray() alpha(opacity=30); /* IE */ /* -moz-opacity: 0.30; Mozilla (Old) */\r
+}\r
+\r
+.SC_FieldOver\r
+{\r
+    border: #316ac5 1px solid;\r
+}\r
+\r
+.SC_FieldOver .SC_FieldButton\r
+{\r
+    border-left: #316ac5 1px solid;\r
+}\r
+\r
+/*\r
+       ### Color Selector Panel\r
+*/\r
+\r
+.ColorBoxBorder\r
+{\r
+    border: #808080 1px solid;\r
+    position: static;\r
+}\r
+\r
+.ColorBox\r
+{\r
+    font-size: 1px;\r
+    width: 10px;\r
+    position: static;\r
+    height: 10px;\r
+}\r
+\r
+.ColorDeselected, .ColorSelected\r
+{\r
+    cursor: default;\r
+}\r
+\r
+.ColorDeselected\r
+{\r
+    border: #ffffff 1px solid;\r
+    padding: 2px;\r
+    float: left;\r
+}\r
+\r
+.ColorSelected\r
+{\r
+    border: #330066 1px solid;\r
+    padding: 2px;\r
+    float: left;\r
+    background-color: #c4cdd6;\r
+}\r
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 (file)
index 0000000..d5ba74e
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/default/fck_strip.gif 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 (file)
index 0000000..6843c8d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.arrowright.gif 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 (file)
index 0000000..ea60995
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.buttonarrow.gif 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 (file)
index 0000000..87aa56d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.collapse.gif 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 (file)
index 0000000..5bfd67a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.end.gif 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 (file)
index 0000000..79075e7
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.expand.gif 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 (file)
index 0000000..eaed04a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.separator.gif 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 (file)
index 0000000..1774246
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/default/images/toolbar.start.gif 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 (file)
index 0000000..54a958b
--- /dev/null
@@ -0,0 +1,138 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Styles used by the dialog boxes.\r
+ */\r
+\r
+body\r
+{\r
+       margin: 0px;\r
+       padding: 10px;\r
+       background-color: #f7f8fd;\r
+}\r
+\r
+body, td, input, select, textarea\r
+{\r
+       font-size: 11px;\r
+       font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;\r
+}\r
+\r
+body, .BackColor\r
+{\r
+       background-color: #f7f8fd;\r
+}\r
+\r
+.PopupBody\r
+{\r
+       margin: 0px;\r
+       padding: 0px;\r
+}\r
+\r
+.PopupTitle\r
+{\r
+       font-weight: bold;\r
+       font-size: 14pt;\r
+       color: #0e3460;\r
+       background-color: #8cb2fd;\r
+       padding: 3px 10px 3px 10px;\r
+}\r
+\r
+.PopupButtons\r
+{\r
+       border-top: #466ca6 1px solid;\r
+       background-color: #8cb2fd;\r
+       padding: 7px 10px 7px 10px;\r
+}\r
+\r
+.Button\r
+{\r
+       border: #1c3460 1px solid;\r
+       color: #000a28;\r
+       background-color: #7096d3;\r
+}\r
+\r
+#btnOk\r
+{\r
+       width: 100px;\r
+}\r
+\r
+.DarkBackground\r
+{\r
+       background-color: #d7d79f;\r
+}\r
+\r
+.LightBackground\r
+{\r
+       background-color: #ffffbe;\r
+}\r
+\r
+.PopupTitleBorder\r
+{\r
+       border-bottom: #d5d59d 1px solid;\r
+}\r
+\r
+.PopupTabArea\r
+{\r
+       color: #0e3460;\r
+       background-color: #8cb2fd;\r
+}\r
+\r
+.PopupTabEmptyArea\r
+{\r
+       padding-left: 10px ;\r
+       border-bottom: #466ca6 1px solid;\r
+}\r
+\r
+.PopupTab, .PopupTabSelected\r
+{\r
+       border-right: #466ca6 1px solid;\r
+       border-top: #466ca6 1px solid;\r
+       border-left: #466ca6 1px solid;\r
+       padding-right: 5px;\r
+       padding-left: 5px;\r
+       padding-bottom: 3px;\r
+       padding-top: 3px;\r
+       color: #0e3460;\r
+}\r
+\r
+.PopupTab\r
+{\r
+       margin-top: 1px;\r
+       border-bottom: #466ca6 1px solid;\r
+       cursor: pointer;\r
+       cursor: hand;\r
+}\r
+\r
+.PopupTabSelected\r
+{\r
+       font-weight:bold;\r
+       cursor: default;\r
+       padding-top: 4px;\r
+       border-bottom: #f7f8fd 1px solid;\r
+       background-color: #f7f8fd;\r
+}\r
+\r
+.PopupSelectionBox\r
+{\r
+       border: #1e90ff 1px solid !important;\r
+       background-color: #add8e6 !important;\r
+       cursor: pointer;\r
+       cursor: hand;\r
+}
\ No newline at end of file
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 (file)
index 0000000..f68c06f
--- /dev/null
@@ -0,0 +1,476 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Styles used by the editor IFRAME and Toolbar.\r
+ */\r
+\r
+/*\r
+       ### Basic Editor IFRAME Styles.\r
+*/\r
+\r
+body\r
+{\r
+    padding: 1px 1px 1px 1px;\r
+    margin: 0px 0px 0px 0px;\r
+}\r
+\r
+#xEditingArea\r
+{\r
+    border: #696969 1px solid;\r
+}\r
+\r
+.SourceField\r
+{\r
+    padding: 5px;\r
+    margin: 0px;\r
+    font-family: Monospace;\r
+}\r
+\r
+/*\r
+       Toolbar\r
+*/\r
+\r
+.TB_ToolbarSet, .TB_Expand, .TB_Collapse\r
+{\r
+    cursor: default;\r
+    background-color: #f7f8fd;\r
+}\r
+\r
+.TB_ToolbarSet\r
+{\r
+    border-top: #f7f8fd 1px outset;\r
+    border-bottom: #f7f8fd 1px outset;\r
+}\r
+\r
+.TB_ToolbarSet TD\r
+{\r
+    font-size: 11px;\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.TB_Toolbar\r
+{\r
+       background-color: #d6dff7;\r
+       background-image: url(images/toolbar.bg.gif);\r
+       background-repeat: repeat-x;\r
+    display: inline-table;\r
+}\r
+\r
+.TB_Separator\r
+{\r
+    width: 1px;\r
+    height: 16px;\r
+    margin: 2px;\r
+    background-color: #B2CBFF;\r
+}\r
+\r
+.TB_Start\r
+{\r
+    background-image: url(images/toolbar.start.gif);\r
+    background-repeat: no-repeat;\r
+    background-position: center center;\r
+    margin: 0px;\r
+    width: 7px;\r
+    height: 24px;\r
+}\r
+\r
+.TB_End\r
+{\r
+    background-image: url(images/toolbar.end.gif);\r
+    background-repeat: no-repeat;\r
+    background-position: center left;\r
+    height: 24px;\r
+    width: 4px;\r
+}\r
+\r
+.TB_ExpandImg\r
+{\r
+    background-image: url(images/toolbar.expand.gif);\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.TB_CollapseImg\r
+{\r
+    background-image: url(images/toolbar.collapse.gif);\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.TB_SideBorder\r
+{\r
+    background-color: #696969;\r
+}\r
+\r
+.TB_Expand, .TB_Collapse\r
+{\r
+    padding: 2px 2px 2px 2px;\r
+    border: #f7f8fd 1px outset;\r
+}\r
+\r
+.TB_Collapse\r
+{\r
+    width: 5px;\r
+}\r
+\r
+.TB_Break\r
+{\r
+    height: 24px; /* IE needs the height to be set, otherwise no break */\r
+}\r
+\r
+/*\r
+       Toolbar Button\r
+*/\r
+\r
+.TB_Button_On, .TB_Button_Off, .TB_Button_On_Over, .TB_Button_Off_Over, .TB_Button_Disabled\r
+{\r
+    margin: 1px;\r
+    height: 22px; /* The height is necessary, otherwise IE will not apply the alpha */\r
+}\r
+\r
+.TB_Button_On\r
+{\r
+    margin: 0px;\r
+    border: #316ac5 1px solid;\r
+    background-color: #c1d2ee;\r
+}\r
+\r
+.TB_Button_On_Over, .TB_Button_Off_Over\r
+{\r
+    margin: 0px ;\r
+    border: #316ac5 1px solid;\r
+    background-color: #dff1ff;\r
+}\r
+\r
+.TB_Button_Off\r
+{\r
+    filter: alpha(opacity=70); /* IE */\r
+    opacity: 0.70; /* Safari, Opera and Mozilla */\r
+}\r
+\r
+.TB_Button_Disabled\r
+{\r
+    filter: gray() alpha(opacity=30); /* IE */\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+}\r
+\r
+.TB_Button_Padding\r
+{\r
+    visibility: hidden;\r
+    width: 3px;\r
+    height: 22px;\r
+}\r
+\r
+.TB_Button_Image\r
+{\r
+    overflow: hidden;\r
+    width: 16px;\r
+    height: 16px;\r
+    margin: 3px;\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.TB_Button_Image img\r
+{\r
+    position: relative;\r
+}\r
+\r
+.TB_Button_Off .TB_Button_Text\r
+{\r
+       background-color: #d6dff7;  /* Needed because of a bug on ClearType */\r
+       background-image: url(images/toolbar.bg.gif);\r
+       background-repeat: repeat-x;\r
+}\r
+\r
+.TB_ConnectionLine\r
+{\r
+    background-color: #f7f8fd;\r
+    height: 1px;\r
+    margin-left: 1px;   /* ltr */\r
+    margin-right: 1px;  /* rtl */\r
+}\r
+\r
+.TB_Button_Off .TB_Text\r
+{\r
+       background-color: #d6dff7;  /* Needed because of a bug on ClearType */\r
+       background-image: url(images/toolbar.bg.gif);\r
+       background-repeat: repeat-x;\r
+}\r
+\r
+.TB_Button_On_Over .TB_Text\r
+{\r
+       background-color: #dff1ff ;  /* Needed because of a bug on ClearType */\r
+}\r
+\r
+/*\r
+       Menu\r
+*/\r
+\r
+.MN_Menu\r
+{\r
+    border: 1px solid #8f8f73;\r
+    padding: 2px;\r
+    background-color: #f7f8fd;\r
+    cursor: default;\r
+}\r
+\r
+.MN_Menu, .MN_Menu .MN_Label\r
+{\r
+    font-size: 11px;\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.MN_Item_Padding\r
+{\r
+    visibility: hidden;\r
+    width: 3px;\r
+    height: 20px;\r
+}\r
+\r
+.MN_Icon\r
+{\r
+    background-color: #d6dff7;\r
+    text-align: center;\r
+    height: 20px;\r
+}\r
+\r
+.MN_Label\r
+{\r
+    padding-left: 3px;\r
+    padding-right: 3px;\r
+}\r
+\r
+.MN_Separator\r
+{\r
+    height: 3px;\r
+}\r
+\r
+.MN_Separator_Line\r
+{\r
+    border-top: #b9b99d 1px solid;\r
+}\r
+\r
+.MN_Item .MN_Icon IMG\r
+{\r
+    filter: alpha(opacity=70);\r
+    opacity: 0.70;\r
+}\r
+\r
+.MN_Item_Over\r
+{\r
+    color: #ffffff;\r
+    background-color: #7096FA;\r
+}\r
+\r
+.MN_Item_Over .MN_Icon\r
+{\r
+    background-color: #466ca6;\r
+}\r
+\r
+.MN_Item_Disabled IMG\r
+{\r
+    filter: gray() alpha(opacity=30); /* IE */\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+}\r
+\r
+.MN_Item_Disabled .MN_Label\r
+{\r
+    color: #b7b7b7;\r
+}\r
+\r
+.MN_Arrow\r
+{\r
+    padding-right: 3px;\r
+    padding-left: 3px;\r
+}\r
+\r
+.MN_ConnectionLine\r
+{\r
+    background-color: #f7f8fd;\r
+}\r
+\r
+.Menu .TB_Button_On, .Menu .TB_Button_On_Over\r
+{\r
+    border: #8f8f73 1px solid;\r
+    background-color: #f7f8fd;\r
+}\r
+\r
+/*\r
+       ### Panel Styles\r
+*/\r
+\r
+.FCK_Panel\r
+{\r
+    border: #8f8f73 1px solid;\r
+    padding: 2px;\r
+    background-color: #f7f8fd;\r
+}\r
+\r
+.FCK_Panel, .FCK_Panel TD\r
+{\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+    font-size: 11px;\r
+}\r
+\r
+/*\r
+       ### Special Combos\r
+*/\r
+\r
+.SC_Panel\r
+{\r
+    overflow: auto;\r
+    white-space: nowrap;\r
+    cursor: default;\r
+    border: 1px solid #8f8f73;\r
+    padding-left: 2px;\r
+    padding-right: 2px;\r
+    background-color: #ffffff;\r
+}\r
+\r
+.SC_Panel, .SC_Panel TD\r
+{\r
+    font-size: 11px;\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.SC_Item, .SC_ItemSelected\r
+{\r
+    margin-top: 2px;\r
+    margin-bottom: 2px;\r
+    background-position: left center;\r
+    padding-left: 11px;\r
+    padding-right: 3px;\r
+    padding-top: 2px;\r
+    padding-bottom: 2px;\r
+    text-overflow: ellipsis;\r
+    overflow: hidden;\r
+    background-repeat: no-repeat;\r
+    border: #dddddd 1px solid;\r
+}\r
+\r
+.SC_Item *, .SC_ItemSelected *\r
+{\r
+    margin-top: 0px;\r
+    margin-bottom: 0px;\r
+}\r
+\r
+.SC_ItemSelected\r
+{\r
+    border: #9a9afb 1px solid;\r
+    background-image: url(images/toolbar.arrowright.gif);\r
+}\r
+\r
+.SC_ItemOver\r
+{\r
+    border: #316ac5 1px solid;\r
+}\r
+\r
+.SC_Field\r
+{\r
+    margin-top: 2px ;\r
+    border: #b7b7a6 1px solid;\r
+    cursor: default;\r
+}\r
+\r
+.SC_FieldCaption\r
+{\r
+    overflow: visible;\r
+    padding-right: 5px;\r
+    padding-left: 5px;\r
+    opacity: 0.75; /* Safari, Opera and Mozilla */\r
+    filter: alpha(opacity=70); /* IE */ /* -moz-opacity: 0.75; Mozilla (Old) */\r
+    height: 23px;\r
+       background-color: #d6dff7;  /* Needed because of a bug on ClearType */\r
+       background-image: url(images/toolbar.bg.gif);\r
+       background-repeat: repeat-x;\r
+/*    background-color:  inherit;     Maybe this is needed wait to check */\r
+}\r
+\r
+.SC_FieldLabel\r
+{\r
+    white-space: nowrap;\r
+    padding: 2px;\r
+    width: 100%;\r
+    cursor: default;\r
+    background-color: #ffffff;\r
+    text-overflow: ellipsis;\r
+    overflow: hidden;\r
+}\r
+\r
+.SC_FieldButton\r
+{\r
+    background-position: center center;\r
+    background-image: url(images/toolbar.buttonarrow.gif);\r
+    border-left: #b7b7a6 1px solid;\r
+    width: 14px;\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.SC_FieldDisabled .SC_FieldButton, .SC_FieldDisabled .SC_FieldCaption\r
+{\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+    filter: gray() alpha(opacity=30); /* IE */ /* -moz-opacity: 0.30; Mozilla (Old) */\r
+}\r
+\r
+.SC_FieldOver\r
+{\r
+    border: #316ac5 1px solid;\r
+}\r
+\r
+.SC_FieldOver .SC_FieldButton\r
+{\r
+    border-left: #316ac5 1px solid;\r
+}\r
+\r
+/*\r
+       ### Color Selector Panel\r
+*/\r
+\r
+.ColorBoxBorder\r
+{\r
+    border: #808080 1px solid;\r
+    position: static;\r
+}\r
+\r
+.ColorBox\r
+{\r
+    font-size: 1px;\r
+    width: 10px;\r
+    position: static;\r
+    height: 10px;\r
+}\r
+\r
+.ColorDeselected, .ColorSelected\r
+{\r
+    cursor: default;\r
+}\r
+\r
+.ColorDeselected\r
+{\r
+    border: #ffffff 1px solid;\r
+    padding: 2px;\r
+    float: left;\r
+}\r
+\r
+.ColorSelected\r
+{\r
+    border: #330066 1px solid;\r
+    padding: 2px;\r
+    float: left;\r
+    background-color: #c4cdd6;\r
+}\r
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 (file)
index 0000000..a7282f2
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/fck_strip.gif 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 (file)
index 0000000..6843c8d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.arrowright.gif 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 (file)
index 0000000..b03960b
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.bg.gif 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 (file)
index 0000000..ea60995
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.buttonarrow.gif 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 (file)
index 0000000..d549166
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.collapse.gif 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 (file)
index 0000000..7ff599d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.end.gif 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 (file)
index 0000000..c4a7326
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.expand.gif 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 (file)
index 0000000..27db9c3
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.separator.gif 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 (file)
index 0000000..41f1241
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/office2003/images/toolbar.start.gif 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 (file)
index 0000000..e05c173
--- /dev/null
@@ -0,0 +1,141 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Styles used by the dialog boxes.\r
+ */\r
+\r
+body\r
+{\r
+       margin: 0px;\r
+       padding: 10px;\r
+       background-color: #f7f7f7;\r
+}\r
+\r
+body, td, input, select, textarea\r
+{\r
+       font-size: 11px;\r
+       font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana;\r
+}\r
+\r
+body, .BackColor\r
+{\r
+       background-color: #f7f7f7;\r
+}\r
+\r
+.PopupBody\r
+{\r
+       margin: 0px;\r
+       padding: 0px;\r
+}\r
+\r
+.PopupTitle\r
+{\r
+       padding-right: 10px;\r
+       padding-left: 10px;\r
+       font-weight: bold;\r
+       font-size: 14pt;\r
+       padding-bottom: 3px;\r
+       color: #504845;\r
+       padding-top: 3px;\r
+       background-color: #dedede;\r
+}\r
+\r
+.PopupButtons\r
+{\r
+       border-top: #cec6b5 1px solid;\r
+       background-color: #DEDEDE;\r
+       padding: 7px 10px 7px 10px;\r
+}\r
+\r
+.Button\r
+{\r
+       border: #7a7261 1px solid;\r
+       color: #504845;\r
+       background-color: #cec6b5;\r
+}\r
+\r
+#btnOk\r
+{\r
+       width: 100px;\r
+}\r
+\r
+.DarkBackground\r
+{\r
+       background-color: #d7d79f;\r
+}\r
+\r
+.LightBackground\r
+{\r
+       background-color: #ffffbe;\r
+}\r
+\r
+.PopupTitleBorder\r
+{\r
+       border-bottom: #cec6b5 1px solid;\r
+}\r
+\r
+.PopupTabArea\r
+{\r
+       color: #504845;\r
+       background-color: #DEDEDE;\r
+}\r
+\r
+.PopupTabEmptyArea\r
+{\r
+       padding-left: 10px ;\r
+       border-bottom: #cec6b5 1px solid;\r
+}\r
+\r
+.PopupTab, .PopupTabSelected\r
+{\r
+       border-right: #cec6b5 1px solid;\r
+       border-top: #cec6b5 1px solid;\r
+       border-left: #cec6b5 1px solid;\r
+       padding-right: 5px;\r
+       padding-left: 5px;\r
+       padding-bottom: 3px;\r
+       padding-top: 3px;\r
+       color: #504845;\r
+}\r
+\r
+.PopupTab\r
+{\r
+       margin-top: 1px;\r
+       border-bottom: #cec6b5 1px solid;\r
+       cursor: pointer;\r
+       cursor: hand;\r
+}\r
+\r
+.PopupTabSelected\r
+{\r
+       font-weight:bold;\r
+       cursor: default;\r
+       padding-top: 4px;\r
+       border-bottom: #f1f1e3 1px solid;\r
+       background-color: #f7f7f7;\r
+}\r
+\r
+.PopupSelectionBox\r
+{\r
+       border: #a9a9a9 1px solid !important;\r
+       background-color: #dcdcdc !important;\r
+       cursor: pointer;\r
+       cursor: hand;\r
+}
\ No newline at end of file
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 (file)
index 0000000..656dcdb
--- /dev/null
@@ -0,0 +1,473 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Styles used by the editor IFRAME and Toolbar.\r
+ */\r
+\r
+/*\r
+       ### Basic Editor IFRAME Styles.\r
+*/\r
+\r
+body\r
+{\r
+       padding: 1px 1px 1px 1px;\r
+       margin: 0px 0px 0px 0px;\r
+}\r
+\r
+#xEditingArea\r
+{\r
+       border: #696969 1px solid;\r
+}\r
+\r
+.SourceField\r
+{\r
+       padding: 5px;\r
+       margin: 0px;\r
+       font-family: Monospace;\r
+}\r
+\r
+/*\r
+       Toolbar\r
+*/\r
+\r
+.TB_ToolbarSet, .TB_Expand, .TB_Collapse\r
+{\r
+    cursor: default;\r
+       background-color: #f7f7f7;\r
+}\r
+\r
+.TB_ToolbarSet\r
+{\r
+       padding: 1px;\r
+       border-top: #efefde 1px outset;\r
+       border-bottom: #efefde 1px outset;\r
+}\r
+\r
+.TB_ToolbarSet TD\r
+{\r
+       font-size: 11px;\r
+       font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.TB_Toolbar\r
+{\r
+    display: inline-table;\r
+}\r
+\r
+.TB_Separator\r
+{\r
+    width: 1px;\r
+    height: 21px;\r
+    margin: 2px;\r
+    background-color: #C6C3BD;\r
+}\r
+\r
+.TB_Start\r
+{\r
+    background-image: url(images/toolbar.start.gif);\r
+    margin-left: 2px;\r
+    margin-right: 2px;\r
+    width: 3px;\r
+    background-repeat: no-repeat;\r
+    height: 27px;\r
+    background-position: center center;\r
+}\r
+\r
+.TB_End\r
+{\r
+       display: none;\r
+}\r
+\r
+.TB_ExpandImg\r
+{\r
+       background-image: url(images/toolbar.expand.gif);\r
+       background-repeat: no-repeat;\r
+}\r
+\r
+.TB_CollapseImg\r
+{\r
+       background-image: url(images/toolbar.collapse.gif);\r
+       background-repeat: no-repeat;\r
+}\r
+\r
+.TB_SideBorder\r
+{\r
+       background-color: #696969;\r
+}\r
+\r
+.TB_Expand, .TB_Collapse\r
+{\r
+       padding: 2px 2px 2px 2px;\r
+       border: #efefde 1px outset;\r
+}\r
+\r
+.TB_Collapse\r
+{\r
+       border: #efefde 1px outset;\r
+       width: 5px;\r
+}\r
+\r
+.TB_Break\r
+{\r
+       height: 27px;\r
+}\r
+\r
+/*\r
+       Toolbar Button\r
+*/\r
+\r
+.TB_Button_On, .TB_Button_Off, .TB_Button_On_Over, .TB_Button_Off_Over, .TB_Button_Disabled\r
+{\r
+       padding: 1px ;\r
+       margin:1px;\r
+       height: 21px;\r
+}\r
+\r
+.TB_Button_On, .TB_Button_Off, .TB_Button_On_Over, .TB_Button_Off_Over, .TB_Button_Disabled\r
+{\r
+       border: #cec6b5 1px solid;\r
+}\r
+\r
+.TB_Button_On\r
+{\r
+       border-color: #316ac5;\r
+       background-color: #c1d2ee;\r
+}\r
+\r
+.TB_Button_On_Over, .TB_Button_Off_Over\r
+{\r
+    border: #316ac5 1px solid;\r
+    background-color: #dff1ff;\r
+}\r
+\r
+.TB_Button_Off\r
+{\r
+       background: #efefef url(images/toolbar.buttonbg.gif) repeat-x;\r
+}\r
+\r
+.TB_Button_Off, .TB_Combo_Off\r
+{\r
+       opacity: 0.70; /* Safari, Opera and Mozilla */\r
+       filter: alpha(opacity=70); /* IE */\r
+       /* -moz-opacity: 0.70; Mozilla (Old) */\r
+}\r
+\r
+.TB_Button_Disabled\r
+{\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+    filter: gray() alpha(opacity=30); /* IE */\r
+}\r
+\r
+.TB_Button_Padding\r
+{\r
+    visibility: hidden;\r
+    width: 3px;\r
+    height: 21px;\r
+}\r
+\r
+.TB_Button_Image\r
+{\r
+    overflow: hidden;\r
+    width: 16px;\r
+    height: 16px;\r
+    margin: 3px;\r
+    margin-top: 4px;\r
+    margin-bottom: 2px;\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+/* For composed button ( icon + text, icon + arrow ), we must compensate the table */\r
+.TB_Button_On TABLE .TB_Button_Image,\r
+.TB_Button_Off TABLE .TB_Button_Image,\r
+.TB_Button_On_Over TABLE .TB_Button_Image,\r
+.TB_Button_Off_Over TABLE .TB_Button_Image,\r
+.TB_Button_Disabled TABLE .TB_Button_Image\r
+{\r
+    margin-top: 3px;\r
+}\r
+\r
+.TB_Button_Image img\r
+{\r
+    position: relative;\r
+}\r
+\r
+.TB_ConnectionLine\r
+{\r
+    background-color: #ffffff;\r
+    height: 1px;\r
+    margin-left: 1px;   /* ltr */\r
+    margin-right: 1px;  /* rtl */\r
+}\r
+\r
+/*\r
+       Menu\r
+*/\r
+\r
+.MN_Menu\r
+{\r
+    border: 1px solid #8f8f73;\r
+    padding: 2px;\r
+    background-color: #f7f7f7;\r
+    cursor: default;\r
+}\r
+\r
+.MN_Menu, .MN_Menu .MN_Label\r
+{\r
+    font-size: 11px;\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.MN_Item_Padding\r
+{\r
+    visibility: hidden;\r
+    width: 3px;\r
+    height: 20px;\r
+}\r
+\r
+.MN_Icon\r
+{\r
+    background-color: #dedede;\r
+    text-align: center;\r
+    height: 20px;\r
+}\r
+\r
+.MN_Label\r
+{\r
+    padding-left: 3px;\r
+    padding-right: 3px;\r
+}\r
+\r
+.MN_Separator\r
+{\r
+    height: 3px;\r
+}\r
+\r
+.MN_Separator_Line\r
+{\r
+    border-top: #b9b99d 1px solid;\r
+}\r
+\r
+.MN_Item .MN_Icon IMG\r
+{\r
+    filter: alpha(opacity=70);\r
+    opacity: 0.70;\r
+}\r
+\r
+.MN_Item_Over\r
+{\r
+    color: #ffffff;\r
+    background-color: #8a857d;\r
+}\r
+\r
+.MN_Item_Over .MN_Icon\r
+{\r
+    background-color: #6c6761;\r
+}\r
+\r
+.MN_Item_Disabled IMG\r
+{\r
+    filter: gray() alpha(opacity=30); /* IE */\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+}\r
+\r
+.MN_Item_Disabled .MN_Label\r
+{\r
+    color: #b7b7b7;\r
+}\r
+\r
+.MN_Arrow\r
+{\r
+    padding-right: 3px;\r
+    padding-left: 3px;\r
+}\r
+\r
+.MN_ConnectionLine\r
+{\r
+    background-color: #ffffff;\r
+}\r
+\r
+.Menu .TB_Button_On, .Menu .TB_Button_On_Over\r
+{\r
+    border: #8f8f73 1px solid;\r
+    background-color: #ffffff;\r
+}\r
+\r
+/*\r
+       ### Panel Styles\r
+*/\r
+\r
+.FCK_Panel\r
+{\r
+    border: #8f8f73 1px solid;\r
+    padding: 2px;\r
+    background-color: #ffffff;\r
+}\r
+\r
+.FCK_Panel, .FCK_Panel TD\r
+{\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+    font-size: 11px;\r
+}\r
+\r
+/*\r
+       ### Special Combos\r
+*/\r
+\r
+.SC_Panel\r
+{\r
+    overflow: auto;\r
+    white-space: nowrap;\r
+    cursor: default;\r
+    border: 1px solid #8f8f73;\r
+    padding-left: 2px;\r
+    padding-right: 2px;\r
+    background-color: #ffffff;\r
+}\r
+\r
+.SC_Panel, .SC_Panel TD\r
+{\r
+    font-size: 11px;\r
+    font-family: 'Microsoft Sans Serif' , Tahoma, Arial, Verdana, Sans-Serif;\r
+}\r
+\r
+.SC_Item, .SC_ItemSelected\r
+{\r
+    margin-top: 2px;\r
+    margin-bottom: 2px;\r
+    background-position: left center;\r
+    padding-left: 11px;\r
+    padding-right: 3px;\r
+    padding-top: 2px;\r
+    padding-bottom: 2px;\r
+    text-overflow: ellipsis;\r
+    overflow: hidden;\r
+    background-repeat: no-repeat;\r
+    border: #dddddd 1px solid;\r
+}\r
+\r
+.SC_Item *, .SC_ItemSelected *\r
+{\r
+    margin-top: 0px;\r
+    margin-bottom: 0px;\r
+}\r
+\r
+.SC_ItemSelected\r
+{\r
+    border: #9a9afb 1px solid;\r
+    background-image: url(images/toolbar.arrowright.gif);\r
+}\r
+\r
+.SC_ItemOver\r
+{\r
+    border: #316ac5 1px solid;\r
+}\r
+\r
+.SC_Field\r
+{\r
+    margin-top:1px ;\r
+    border: #b7b7a6 1px solid;\r
+    cursor: default;\r
+}\r
+\r
+.SC_FieldCaption\r
+{\r
+    padding-top: 1px ;\r
+    overflow: visible;\r
+    padding-right: 5px;\r
+    padding-left: 5px;\r
+    opacity: 0.75; /* Safari, Opera and Mozilla */\r
+    filter: alpha(opacity=70); /* IE */ /* -moz-opacity: 0.75; Mozilla (Old) */\r
+    height: 23px;\r
+    background-color: #f7f7f7;\r
+}\r
+\r
+.SC_FieldLabel\r
+{\r
+    white-space: nowrap;\r
+    padding: 2px;\r
+    width: 100%;\r
+    cursor: default;\r
+    background-color: #ffffff;\r
+    text-overflow: ellipsis;\r
+    overflow: hidden;\r
+}\r
+\r
+.SC_FieldButton\r
+{\r
+    background-position: center center;\r
+    background-image: url(images/toolbar.buttonarrow.gif);\r
+    border-left: #b7b7a6 1px solid;\r
+    width: 14px;\r
+    background-repeat: no-repeat;\r
+}\r
+\r
+.SC_FieldDisabled .SC_FieldButton, .SC_FieldDisabled .SC_FieldCaption\r
+{\r
+    opacity: 0.30; /* Safari, Opera and Mozilla */\r
+    filter: gray() alpha(opacity=30); /* IE */ /* -moz-opacity: 0.30; Mozilla (Old) */\r
+}\r
+\r
+.SC_FieldOver\r
+{\r
+    border: #316ac5 1px solid;\r
+}\r
+\r
+.SC_FieldOver .SC_FieldButton\r
+{\r
+    border-left: #316ac5 1px solid;\r
+}\r
+\r
+/*\r
+       ### Color Selector Panel\r
+*/\r
+\r
+.ColorBoxBorder\r
+{\r
+    border: #808080 1px solid;\r
+    position: static;\r
+}\r
+\r
+.ColorBox\r
+{\r
+    font-size: 1px;\r
+    width: 10px;\r
+    position: static;\r
+    height: 10px;\r
+}\r
+\r
+.ColorDeselected, .ColorSelected\r
+{\r
+    cursor: default;\r
+}\r
+\r
+.ColorDeselected\r
+{\r
+    border: #ffffff 1px solid;\r
+    padding: 2px;\r
+    float: left;\r
+}\r
+\r
+.ColorSelected\r
+{\r
+    border: #316ac5 1px solid;\r
+    padding: 2px;\r
+    float: left;\r
+    background-color: #c1d2ee;\r
+}\r
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 (file)
index 0000000..d5ba74e
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/fck_strip.gif 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 (file)
index 0000000..6843c8d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.arrowright.gif 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 (file)
index 0000000..ea60995
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonarrow.gif 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 (file)
index 0000000..a93ffca
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.buttonbg.gif 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 (file)
index 0000000..87aa56d
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.collapse.gif 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 (file)
index 0000000..5bfd67a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.end.gif 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 (file)
index 0000000..79075e7
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.expand.gif 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 (file)
index 0000000..eaed04a
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.separator.gif 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 (file)
index 0000000..1774246
Binary files /dev/null and b/httemplate/elements/fckeditor/editor/skins/silver/images/toolbar.start.gif differ
diff --git a/httemplate/elements/fckeditor/fckconfig.js b/httemplate/elements/fckeditor/fckconfig.js
new file mode 100644 (file)
index 0000000..215bc0a
--- /dev/null
@@ -0,0 +1,245 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * Editor configuration settings.\r
+ *\r
+ * Follow this link for more information:\r
+ * http://wiki.fckeditor.net/Developer%27s_Guide/Configuration/Configurations_Settings\r
+ */\r
+\r
+// Disable the custom Enter Key Handler. This option will be removed in version 2.5.\r
+FCKConfig.DisableEnterKeyHandler = false ;\r
+\r
+FCKConfig.CustomConfigurationsPath = '' ;\r
+\r
+FCKConfig.EditorAreaCSS = FCKConfig.BasePath + 'css/fck_editorarea.css' ;\r
+FCKConfig.ToolbarComboPreviewCSS = '' ;\r
+\r
+FCKConfig.DocType = '' ;\r
+\r
+FCKConfig.BaseHref = '' ;\r
+\r
+FCKConfig.FullPage = false ;\r
+\r
+FCKConfig.Debug = false ;\r
+FCKConfig.AllowQueryStringDebug = true ;\r
+\r
+FCKConfig.SkinPath = FCKConfig.BasePath + 'skins/default/' ;\r
+//FCKConfig.SkinPath = FCKConfig.BasePath + 'editor/skins/silver/' ;\r
+FCKConfig.PreloadImages = [ FCKConfig.SkinPath + 'images/toolbar.start.gif', FCKConfig.SkinPath + 'images/toolbar.buttonarrow.gif' ] ;\r
+\r
+FCKConfig.PluginsPath = FCKConfig.BasePath + 'plugins/' ;\r
+\r
+// FCKConfig.Plugins.Add( 'autogrow' ) ;\r
+FCKConfig.AutoGrowMax = 400 ;\r
+\r
+// FCKConfig.ProtectedSource.Add( /<%[\s\S]*?%>/g ) ;  // ASP style server side code <%...%>\r
+// FCKConfig.ProtectedSource.Add( /<\?[\s\S]*?\?>/g ) ;        // PHP style server side code\r
+// FCKConfig.ProtectedSource.Add( /(<asp:[^\>]+>[\s|\S]*?<\/asp:[^\>]+>)|(<asp:[^\>]+\/>)/gi ) ;       // ASP.Net style tags <asp:control>\r
+\r
+FCKConfig.AutoDetectLanguage   = true ;\r
+FCKConfig.DefaultLanguage              = 'en' ;\r
+FCKConfig.ContentLangDirection = 'ltr' ;\r
+\r
+FCKConfig.ProcessHTMLEntities  = true ;\r
+FCKConfig.IncludeLatinEntities = true ;\r
+FCKConfig.IncludeGreekEntities = true ;\r
+\r
+FCKConfig.ProcessNumericEntities = false ;\r
+\r
+FCKConfig.AdditionalNumericEntities = ''  ;            // Single Quote: "'"\r
+\r
+FCKConfig.FillEmptyBlocks      = true ;\r
+\r
+FCKConfig.FormatSource         = true ;\r
+FCKConfig.FormatOutput         = true ;\r
+FCKConfig.FormatIndentator     = '    ' ;\r
+\r
+FCKConfig.ForceStrongEm = true ;\r
+FCKConfig.GeckoUseSPAN = false ;\r
+FCKConfig.StartupFocus = false ;\r
+FCKConfig.ForcePasteAsPlainText        = false ;\r
+FCKConfig.AutoDetectPasteFromWord = true ;     // IE only.\r
+FCKConfig.ForceSimpleAmpersand = false ;\r
+FCKConfig.TabSpaces            = 0 ;\r
+FCKConfig.ShowBorders  = true ;\r
+FCKConfig.SourcePopup  = false ;\r
+FCKConfig.ToolbarStartExpanded = true ;\r
+FCKConfig.ToolbarCanCollapse   = true ;\r
+FCKConfig.IgnoreEmptyParagraphValue = true ;\r
+FCKConfig.PreserveSessionOnFileBrowser = false ;\r
+FCKConfig.FloatingPanelsZIndex = 10000 ;\r
+\r
+FCKConfig.TemplateReplaceAll = true ;\r
+FCKConfig.TemplateReplaceCheckbox = true ;\r
+\r
+FCKConfig.ToolbarLocation = 'In' ;\r
+\r
+//FCKConfig.ToolbarSets["Default"] = [\r
+//     ['Source','DocProps','-','Save','NewPage','Preview','-','Templates'],\r
+//     ['Cut','Copy','Paste','PasteText','PasteWord','-','Print','SpellCheck'],\r
+//     ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],\r
+//     ['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],\r
+//     '/',\r
+//     ['Bold','Italic','Underline','StrikeThrough','-','Subscript','Superscript'],\r
+//     ['OrderedList','UnorderedList','-','Outdent','Indent'],\r
+//     ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],\r
+//     ['Link','Unlink','Anchor'],\r
+//     ['Image','Flash','Table','Rule','Smiley','SpecialChar','PageBreak'],\r
+//     '/',\r
+//     ['Style','FontFormat','FontName','FontSize'],\r
+//     ['TextColor','BGColor'],\r
+//     ['FitWindow','-','About']\r
+//] ;\r
+FCKConfig.ToolbarSets["Default"] = [\r
+       ['Source','DocProps','-','Save','Preview','-'],\r
+       ['Cut','Copy','Paste','PasteText','PasteWord','-','Print','SpellCheck'],\r
+       ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],\r
+       //['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],\r
+       '/',\r
+       ['Bold','Italic','Underline','StrikeThrough','-','Subscript','Superscript'],\r
+       ['OrderedList','UnorderedList','-','Outdent','Indent'],\r
+       ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],\r
+       ['Link','Unlink','Anchor'],\r
+       ['Image','Flash','Table','Rule','Smiley','SpecialChar','PageBreak'],\r
+       '/',\r
+       ['Style','FontFormat','FontName','FontSize'],\r
+       ['TextColor','BGColor'],\r
+       ['FitWindow','-','About']\r
+] ;\r
+\r
+FCKConfig.ToolbarSets["Basic"] = [\r
+       ['Bold','Italic','-','OrderedList','UnorderedList','-','Link','Unlink','-','About']\r
+] ;\r
+\r
+FCKConfig.EnterMode = 'p' ;                    // p | div | br\r
+FCKConfig.ShiftEnterMode = 'br' ;      // p | div | br\r
+\r
+FCKConfig.Keystrokes = [\r
+       [ CTRL + 65 /*A*/, true ],\r
+       [ CTRL + 67 /*C*/, true ],\r
+       [ CTRL + 70 /*F*/, true ],\r
+       [ CTRL + 83 /*S*/, true ],\r
+       [ CTRL + 88 /*X*/, true ],\r
+       [ CTRL + 86 /*V*/, 'Paste' ],\r
+       [ SHIFT + 45 /*INS*/, 'Paste' ],\r
+       [ CTRL + 90 /*Z*/, 'Undo' ],\r
+       [ CTRL + 89 /*Y*/, 'Redo' ],\r
+       [ CTRL + SHIFT + 90 /*Z*/, 'Redo' ],\r
+       [ CTRL + 76 /*L*/, 'Link' ],\r
+       [ CTRL + 66 /*B*/, 'Bold' ],\r
+       [ CTRL + 73 /*I*/, 'Italic' ],\r
+       [ CTRL + 85 /*U*/, 'Underline' ],\r
+       [ CTRL + SHIFT + 83 /*S*/, 'Save' ],\r
+       [ CTRL + ALT + 13 /*ENTER*/, 'FitWindow' ],\r
+       [ CTRL + 9 /*TAB*/, 'Source' ]\r
+] ;\r
+\r
+FCKConfig.ContextMenu = ['Generic','Link','Anchor','Image','Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField','ImageButton','Button','BulletedList','NumberedList','Table','Form'] ;\r
+FCKConfig.BrowserContextMenuOnCtrl = false ;\r
+\r
+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' ;\r
+\r
+FCKConfig.FontNames            = 'Arial;Comic Sans MS;Courier New;Tahoma;Times New Roman;Verdana' ;\r
+FCKConfig.FontSizes            = '1/xx-small;2/x-small;3/small;4/medium;5/large;6/x-large;7/xx-large' ;\r
+FCKConfig.FontFormats  = 'p;div;pre;address;h1;h2;h3;h4;h5;h6' ;\r
+\r
+FCKConfig.StylesXmlPath                = FCKConfig.EditorPath + 'fckstyles.xml' ;\r
+FCKConfig.TemplatesXmlPath     = FCKConfig.EditorPath + 'fcktemplates.xml' ;\r
+\r
+FCKConfig.SpellChecker                 = 'ieSpell' ;   // 'ieSpell' | 'SpellerPages'\r
+FCKConfig.IeSpellDownloadUrl   = 'http://www.iespell.com/download.php' ;\r
+FCKConfig.SpellerPagesServerScript = 'server-scripts/spellchecker.php' ;       // Available extension: .php .cfm .pl\r
+FCKConfig.FirefoxSpellChecker  = false ;\r
+\r
+FCKConfig.MaxUndoLevels = 15 ;\r
+\r
+FCKConfig.DisableObjectResizing = false ;\r
+FCKConfig.DisableFFTableHandles = true ;\r
+\r
+FCKConfig.LinkDlgHideTarget            = false ;\r
+FCKConfig.LinkDlgHideAdvanced  = false ;\r
+\r
+FCKConfig.ImageDlgHideLink             = false ;\r
+FCKConfig.ImageDlgHideAdvanced = false ;\r
+\r
+FCKConfig.FlashDlgHideAdvanced = false ;\r
+\r
+FCKConfig.ProtectedTags = '' ;\r
+\r
+// This will be applied to the body element of the editor\r
+FCKConfig.BodyId = '' ;\r
+FCKConfig.BodyClass = '' ;\r
+\r
+FCKConfig.DefaultLinkTarget = '' ;\r
+\r
+// The option switches between trying to keep the html structure or do the changes so the content looks like it was in Word\r
+FCKConfig.CleanWordKeepsStructure = false ;\r
+\r
+// The following value defines which File Browser connector and Quick Upload\r
+// "uploader" to use. It is valid for the default implementaion and it is here\r
+// just to make this configuration file cleaner.\r
+// It is not possible to change this value using an external file or even\r
+// inline when creating the editor instance. In that cases you must set the\r
+// values of LinkBrowserURL, ImageBrowserURL and so on.\r
+// Custom implementations should just ignore it.\r
+var _FileBrowserLanguage       = 'asp' ;       // asp | aspx | cfm | lasso | perl | php | py\r
+var _QuickUploadLanguage       = 'asp' ;       // asp | aspx | cfm | lasso | php\r
+\r
+\r
+// Don't care about the following line. It just calculates the correct connector\r
+// extension to use for the default File Browser (Perl uses "cgi").\r
+var _FileBrowserExtension = _FileBrowserLanguage == 'perl' ? 'cgi' : _FileBrowserLanguage ;\r
+\r
+FCKConfig.LinkBrowser = true ;\r
+FCKConfig.LinkBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Connector=connectors/' + _FileBrowserLanguage + '/connector.' + _FileBrowserExtension ;\r
+FCKConfig.LinkBrowserWindowWidth       = FCKConfig.ScreenWidth * 0.7 ;         // 70%\r
+FCKConfig.LinkBrowserWindowHeight      = FCKConfig.ScreenHeight * 0.7 ;        // 70%\r
+\r
+FCKConfig.ImageBrowser = true ;\r
+FCKConfig.ImageBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Type=Image&Connector=connectors/' + _FileBrowserLanguage + '/connector.' + _FileBrowserExtension ;\r
+FCKConfig.ImageBrowserWindowWidth  = FCKConfig.ScreenWidth * 0.7 ;     // 70% ;\r
+FCKConfig.ImageBrowserWindowHeight = FCKConfig.ScreenHeight * 0.7 ;    // 70% ;\r
+\r
+FCKConfig.FlashBrowser = true ;\r
+FCKConfig.FlashBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Type=Flash&Connector=connectors/' + _FileBrowserLanguage + '/connector.' + _FileBrowserExtension ;\r
+FCKConfig.FlashBrowserWindowWidth  = FCKConfig.ScreenWidth * 0.7 ;     //70% ;\r
+FCKConfig.FlashBrowserWindowHeight = FCKConfig.ScreenHeight * 0.7 ;    //70% ;\r
+\r
+FCKConfig.LinkUpload = true ;\r
+FCKConfig.LinkUploadURL = FCKConfig.BasePath + 'filemanager/upload/' + _QuickUploadLanguage + '/upload.' + _QuickUploadLanguage ;\r
+FCKConfig.LinkUploadAllowedExtensions  = "" ;                  // empty for all\r
+FCKConfig.LinkUploadDeniedExtensions   = ".(html|htm|php|php2|php3|php4|php5|phtml|pwml|inc|asp|aspx|ascx|jsp|cfm|cfc|pl|bat|exe|com|dll|vbs|js|reg|cgi|htaccess|asis|sh|shtml|shtm|phtm)$" ;  // empty for no one\r
+\r
+FCKConfig.ImageUpload = true ;\r
+FCKConfig.ImageUploadURL = FCKConfig.BasePath + 'filemanager/upload/' + _QuickUploadLanguage + '/upload.' + _QuickUploadLanguage + '?Type=Image' ;\r
+FCKConfig.ImageUploadAllowedExtensions = ".(jpg|gif|jpeg|png|bmp)$" ;          // empty for all\r
+FCKConfig.ImageUploadDeniedExtensions  = "" ;                                                  // empty for no one\r
+\r
+FCKConfig.FlashUpload = true ;\r
+FCKConfig.FlashUploadURL = FCKConfig.BasePath + 'filemanager/upload/' + _QuickUploadLanguage + '/upload.' + _QuickUploadLanguage + '?Type=Flash' ;\r
+FCKConfig.FlashUploadAllowedExtensions = ".(swf|fla)$" ;               // empty for all\r
+FCKConfig.FlashUploadDeniedExtensions  = "" ;                                  // empty for no one\r
+\r
+FCKConfig.SmileyPath   = FCKConfig.BasePath + 'images/smiley/msn/' ;\r
+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'] ;\r
+FCKConfig.SmileyColumns = 8 ;\r
+FCKConfig.SmileyWindowWidth            = 320 ;\r
+FCKConfig.SmileyWindowHeight   = 240 ;\r
diff --git a/httemplate/elements/fckeditor/fckeditor.js b/httemplate/elements/fckeditor/fckeditor.js
new file mode 100644 (file)
index 0000000..63ec41f
--- /dev/null
@@ -0,0 +1,214 @@
+/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This is the integration file for JavaScript.\r
+ *\r
+ * It defines the FCKeditor class that can be used to create editor\r
+ * instances in a HTML page in the client side. For server side\r
+ * operations, use the specific integration system.\r
+ */\r
+\r
+// FCKeditor Class\r
+var FCKeditor = function( instanceName, width, height, toolbarSet, value )\r
+{\r
+       // Properties\r
+       this.InstanceName       = instanceName ;\r
+       this.Width                      = width                 || '100%' ;\r
+       this.Height                     = height                || '200' ;\r
+       this.ToolbarSet         = toolbarSet    || 'Default' ;\r
+       this.Value                      = value                 || '' ;\r
+       this.BasePath           = '/fckeditor/' ;\r
+       this.CheckBrowser       = true ;\r
+       this.DisplayErrors      = true ;\r
+       this.EnableSafari       = false ;               // This is a temporary property, while Safari support is under development.\r
+       this.EnableOpera        = false ;               // This is a temporary property, while Opera support is under development.\r
+\r
+       this.Config                     = new Object() ;\r
+\r
+       // Events\r
+       this.OnError            = null ;        // function( source, errorNumber, errorDescription )\r
+}\r
+\r
+FCKeditor.prototype.Version                    = '2.4.3' ;\r
+FCKeditor.prototype.VersionBuild       = '15657' ;\r
+\r
+FCKeditor.prototype.Create = function()\r
+{\r
+       document.write( this.CreateHtml() ) ;\r
+}\r
+\r
+FCKeditor.prototype.CreateHtml = function()\r
+{\r
+       // Check for errors\r
+       if ( !this.InstanceName || this.InstanceName.length == 0 )\r
+       {\r
+               this._ThrowError( 701, 'You must specify an instance name.' ) ;\r
+               return '' ;\r
+       }\r
+\r
+       var sHtml = '<div>' ;\r
+\r
+       if ( !this.CheckBrowser || this._IsCompatibleBrowser() )\r
+       {\r
+               sHtml += '<input type="hidden" id="' + this.InstanceName + '" name="' + this.InstanceName + '" value="' + this._HTMLEncode( this.Value ) + '" style="display:none" />' ;\r
+               sHtml += this._GetConfigHtml() ;\r
+               sHtml += this._GetIFrameHtml() ;\r
+       }\r
+       else\r
+       {\r
+               var sWidth  = this.Width.toString().indexOf('%')  > 0 ? this.Width  : this.Width  + 'px' ;\r
+               var sHeight = this.Height.toString().indexOf('%') > 0 ? this.Height : this.Height + 'px' ;\r
+               sHtml += '<textarea name="' + this.InstanceName + '" rows="4" cols="40" style="width:' + sWidth + ';height:' + sHeight + '">' + this._HTMLEncode( this.Value ) + '<\/textarea>' ;\r
+       }\r
+\r
+       sHtml += '</div>' ;\r
+\r
+       return sHtml ;\r
+}\r
+\r
+FCKeditor.prototype.ReplaceTextarea = function()\r
+{\r
+       if ( !this.CheckBrowser || this._IsCompatibleBrowser() )\r
+       {\r
+               // We must check the elements firstly using the Id and then the name.\r
+               var oTextarea = document.getElementById( this.InstanceName ) ;\r
+               var colElementsByName = document.getElementsByName( this.InstanceName ) ;\r
+               var i = 0;\r
+               while ( oTextarea || i == 0 )\r
+               {\r
+                       if ( oTextarea && oTextarea.tagName.toLowerCase() == 'textarea' )\r
+                               break ;\r
+                       oTextarea = colElementsByName[i++] ;\r
+               }\r
+\r
+               if ( !oTextarea )\r
+               {\r
+                       alert( 'Error: The TEXTAREA with id or name set to "' + this.InstanceName + '" was not found' ) ;\r
+                       return ;\r
+               }\r
+\r
+               oTextarea.style.display = 'none' ;\r
+               this._InsertHtmlBefore( this._GetConfigHtml(), oTextarea ) ;\r
+               this._InsertHtmlBefore( this._GetIFrameHtml(), oTextarea ) ;\r
+       }\r
+}\r
+\r
+FCKeditor.prototype._InsertHtmlBefore = function( html, element )\r
+{\r
+       if ( element.insertAdjacentHTML )       // IE\r
+               element.insertAdjacentHTML( 'beforeBegin', html ) ;\r
+       else                                                            // Gecko\r
+       {\r
+               var oRange = document.createRange() ;\r
+               oRange.setStartBefore( element ) ;\r
+               var oFragment = oRange.createContextualFragment( html );\r
+               element.parentNode.insertBefore( oFragment, element ) ;\r
+       }\r
+}\r
+\r
+FCKeditor.prototype._GetConfigHtml = function()\r
+{\r
+       var sConfig = '' ;\r
+       for ( var o in this.Config )\r
+       {\r
+               if ( sConfig.length > 0 ) sConfig += '&amp;' ;\r
+               sConfig += encodeURIComponent( o ) + '=' + encodeURIComponent( this.Config[o] ) ;\r
+       }\r
+\r
+       return '<input type="hidden" id="' + this.InstanceName + '___Config" value="' + sConfig + '" style="display:none" />' ;\r
+}\r
+\r
+FCKeditor.prototype._GetIFrameHtml = function()\r
+{\r
+       var sFile = 'fckeditor.html' ;\r
+\r
+       try\r
+       {\r
+               if ( (/fcksource=true/i).test( window.top.location.search ) )\r
+                       sFile = 'fckeditor.original.html' ;\r
+       }\r
+       catch (e) { /* Ignore it. Much probably we are inside a FRAME where the "top" is in another domain (security error). */ }\r
+\r
+       var sLink = this.BasePath + 'editor/' + sFile + '?InstanceName=' + encodeURIComponent( this.InstanceName ) ;\r
+       if (this.ToolbarSet) sLink += '&amp;Toolbar=' + this.ToolbarSet ;\r
+\r
+       return '<iframe id="' + this.InstanceName + '___Frame" src="' + sLink + '" width="' + this.Width + '" height="' + this.Height + '" frameborder="0" scrolling="no"></iframe>' ;\r
+}\r
+\r
+FCKeditor.prototype._IsCompatibleBrowser = function()\r
+{\r
+       return FCKeditor_IsCompatibleBrowser( this.EnableSafari, this.EnableOpera ) ;\r
+}\r
+\r
+FCKeditor.prototype._ThrowError = function( errorNumber, errorDescription )\r
+{\r
+       this.ErrorNumber                = errorNumber ;\r
+       this.ErrorDescription   = errorDescription ;\r
+\r
+       if ( this.DisplayErrors )\r
+       {\r
+               document.write( '<div style="COLOR: #ff0000">' ) ;\r
+               document.write( '[ FCKeditor Error ' + this.ErrorNumber + ': ' + this.ErrorDescription + ' ]' ) ;\r
+               document.write( '</div>' ) ;\r
+       }\r
+\r
+       if ( typeof( this.OnError ) == 'function' )\r
+               this.OnError( this, errorNumber, errorDescription ) ;\r
+}\r
+\r
+FCKeditor.prototype._HTMLEncode = function( text )\r
+{\r
+       if ( typeof( text ) != "string" )\r
+               text = text.toString() ;\r
+\r
+       text = text.replace(\r
+               /&/g, "&amp;").replace(\r
+               /"/g, "&quot;").replace(\r
+               /</g, "&lt;").replace(\r
+               />/g, "&gt;") ;\r
+\r
+       return text ;\r
+}\r
+\r
+function FCKeditor_IsCompatibleBrowser( enableSafari, enableOpera )\r
+{\r
+       var sAgent = navigator.userAgent.toLowerCase() ;\r
+\r
+       // Internet Explorer\r
+       if ( sAgent.indexOf("msie") != -1 && sAgent.indexOf("mac") == -1 && sAgent.indexOf("opera") == -1 )\r
+       {\r
+               var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1] ;\r
+               return ( sBrowserVersion >= 5.5 ) ;\r
+       }\r
+\r
+       // Gecko (Opera 9 tries to behave like Gecko at this point).\r
+       if ( navigator.product == "Gecko" && navigator.productSub >= 20030210 && !( typeof(opera) == 'object' && opera.postError ) )\r
+               return true ;\r
+\r
+       // Opera\r
+       if ( enableOpera && sAgent.indexOf( 'opera' ) == 0 && parseInt( navigator.appVersion, 10 ) >= 9 )\r
+                       return true ;\r
+\r
+       // Safari\r
+       if ( enableSafari && sAgent.indexOf( 'safari' ) != -1 )\r
+               return ( sAgent.match( /safari\/(\d+)/ )[1] >= 312 ) ;  // Build must be at least 312 (1.3)\r
+\r
+       return false ;\r
+}
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/fckpackager.xml b/httemplate/elements/fckeditor/fckpackager.xml
new file mode 100644 (file)
index 0000000..3cae595
--- /dev/null
@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="utf-8" ?>\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This is the configuration file to be used with FCKpackager to generate the\r
+ * compressed code files in the "js" folder.\r
+ *\r
+ * Please check http://www.fckeditor.net for more info.\r
+-->\r
+<Package>\r
+       <Header><![CDATA[/*\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ * \r
+ * == BEGIN LICENSE ==\r
+ * \r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ * \r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ * \r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ * \r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ * \r
+ * == END LICENSE ==\r
+ * \r
+ * This file has been compressed for better performance. The original source\r
+ * can be found at "editor/_source".\r
+ */\r
+]]></Header>\r
+       <Constants removeDeclaration="false">\r
+               <Constant name="FCK_STATUS_NOTLOADED" value="0" />\r
+               <Constant name="FCK_STATUS_ACTIVE" value="1" />\r
+               <Constant name="FCK_STATUS_COMPLETE" value="2" />\r
+               <Constant name="FCK_TRISTATE_OFF" value="0" />\r
+               <Constant name="FCK_TRISTATE_ON" value="1" />\r
+               <Constant name="FCK_TRISTATE_DISABLED" value="-1" />\r
+               <Constant name="FCK_UNKNOWN" value="-9" />\r
+               <Constant name="FCK_TOOLBARITEM_ONLYICON" value="0" />\r
+               <Constant name="FCK_TOOLBARITEM_ONLYTEXT" value="1" />\r
+               <Constant name="FCK_TOOLBARITEM_ICONTEXT" value="2" />\r
+               <Constant name="FCK_EDITMODE_WYSIWYG" value="0" />\r
+               <Constant name="FCK_EDITMODE_SOURCE" value="1" />\r
+       </Constants>\r
+       <PackageFile path="editor/js/fckeditorcode_ie.js">\r
+               <File path="editor/_source/fckconstants.js" />\r
+               <File path="editor/_source/fckjscoreextensions.js" />\r
+               <File path="editor/_source/classes/fckiecleanup.js" />\r
+               <File path="editor/_source/internals/fckbrowserinfo.js" />\r
+               <File path="editor/_source/internals/fckurlparams.js" />\r
+               <File path="editor/_source/classes/fckevents.js" />\r
+               <File path="editor/_source/internals/fck.js" />\r
+               <File path="editor/_source/internals/fck_ie.js" />\r
+               <File path="editor/_source/internals/fckconfig.js" />\r
+               <File path="editor/_source/internals/fckdebug.js" />\r
+               <File path="editor/_source/internals/fckdomtools.js" />\r
+               <File path="editor/_source/internals/fcktools.js" />\r
+               <File path="editor/_source/internals/fcktools_ie.js" />\r
+               <File path="editor/_source/fckeditorapi.js" />\r
+               <File path="editor/_source/classes/fckimagepreloader.js" />\r
+\r
+               <File path="editor/_source/internals/fckregexlib.js" />\r
+               <File path="editor/_source/internals/fcklistslib.js" />\r
+               <File path="editor/_source/internals/fcklanguagemanager.js" />\r
+               <File path="editor/_source/internals/fckxhtmlentities.js" />\r
+               <File path="editor/_source/internals/fckxhtml.js" />\r
+               <File path="editor/_source/internals/fckxhtml_ie.js" />\r
+               <File path="editor/_source/internals/fckcodeformatter.js" />\r
+               <File path="editor/_source/internals/fckundo_ie.js" />\r
+               <File path="editor/_source/classes/fckeditingarea.js" />\r
+               <File path="editor/_source/classes/fckkeystrokehandler.js" />\r
+\r
+               <File path="editor/_source/internals/fcklisthandler.js" />\r
+               <File path="editor/_source/classes/fckelementpath.js" />\r
+               <File path="editor/_source/classes/fckdomrange.js" />\r
+               <File path="editor/_source/classes/fckdomrange_ie.js" />\r
+               <File path="editor/_source/classes/fckdocumentfragment_ie.js" />\r
+               <File path="editor/_source/classes/fckw3crange.js" />\r
+               <File path="editor/_source/classes/fckenterkey.js" />\r
+\r
+               <File path="editor/_source/internals/fckdocumentprocessor.js" />\r
+               <File path="editor/_source/internals/fckselection.js" />\r
+               <File path="editor/_source/internals/fckselection_ie.js" />\r
+\r
+               <File path="editor/_source/internals/fcktablehandler.js" />\r
+               <File path="editor/_source/internals/fcktablehandler_ie.js" />\r
+               <File path="editor/_source/classes/fckxml_ie.js" />\r
+               <File path="editor/_source/classes/fckstyledef.js" />\r
+               <File path="editor/_source/classes/fckstyledef_ie.js" />\r
+               <File path="editor/_source/classes/fckstylesloader.js" />\r
+\r
+               <File path="editor/_source/commandclasses/fcknamedcommand.js" />\r
+               <File path="editor/_source/commandclasses/fck_othercommands.js" />\r
+               <File path="editor/_source/commandclasses/fckspellcheckcommand_ie.js" />\r
+               <File path="editor/_source/commandclasses/fcktextcolorcommand.js" />\r
+               <File path="editor/_source/commandclasses/fckpasteplaintextcommand.js" />\r
+               <File path="editor/_source/commandclasses/fckpastewordcommand.js" />\r
+               <File path="editor/_source/commandclasses/fcktablecommand.js" />\r
+               <File path="editor/_source/commandclasses/fckstylecommand.js" />\r
+               <File path="editor/_source/commandclasses/fckfitwindow.js" />\r
+               <File path="editor/_source/internals/fckcommands.js" />\r
+\r
+               <File path="editor/_source/classes/fckpanel.js" />\r
+               <File path="editor/_source/classes/fckicon.js" />\r
+               <File path="editor/_source/classes/fcktoolbarbuttonui.js" />\r
+               <File path="editor/_source/classes/fcktoolbarbutton.js" />\r
+               <File path="editor/_source/classes/fckspecialcombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarspecialcombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarfontscombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarfontsizecombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarfontformatcombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarstylecombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarpanelbutton.js" />\r
+               <File path="editor/_source/internals/fcktoolbaritems.js" />\r
+               <File path="editor/_source/classes/fcktoolbar.js" />\r
+               <File path="editor/_source/classes/fcktoolbarbreak_ie.js" />\r
+               <File path="editor/_source/internals/fcktoolbarset.js" />\r
+               <File path="editor/_source/internals/fckdialog.js" />\r
+               <File path="editor/_source/internals/fckdialog_ie.js" />\r
+\r
+               <File path="editor/_source/classes/fckmenuitem.js" />\r
+               <File path="editor/_source/classes/fckmenublock.js" />\r
+               <File path="editor/_source/classes/fckmenublockpanel.js" />\r
+               <File path="editor/_source/classes/fckcontextmenu.js" />\r
+               <File path="editor/_source/internals/fck_contextmenu.js" />\r
+\r
+               <File path="editor/_source/classes/fckplugin.js" />\r
+               <File path="editor/_source/internals/fckplugins.js" />\r
+       </PackageFile>\r
+\r
+       <PackageFile path="editor/js/fckeditorcode_gecko.js">\r
+               <File path="editor/_source/fckconstants.js" />\r
+               <File path="editor/_source/fckjscoreextensions.js" />\r
+               <File path="editor/_source/internals/fckbrowserinfo.js" />\r
+               <File path="editor/_source/internals/fckurlparams.js" />\r
+               <File path="editor/_source/classes/fckevents.js" />\r
+               <File path="editor/_source/internals/fck.js" />\r
+               <File path="editor/_source/internals/fck_gecko.js" />\r
+               <File path="editor/_source/internals/fckconfig.js" />\r
+               <File path="editor/_source/internals/fckdebug.js" />\r
+               <File path="editor/_source/internals/fckdomtools.js" />\r
+               <File path="editor/_source/internals/fcktools.js" />\r
+               <File path="editor/_source/internals/fcktools_gecko.js" />\r
+               <File path="editor/_source/fckeditorapi.js" />\r
+               <File path="editor/_source/classes/fckimagepreloader.js" />\r
+\r
+               <File path="editor/_source/internals/fckregexlib.js" />\r
+               <File path="editor/_source/internals/fcklistslib.js" />\r
+               <File path="editor/_source/internals/fcklanguagemanager.js" />\r
+               <File path="editor/_source/internals/fckxhtmlentities.js" />\r
+               <File path="editor/_source/internals/fckxhtml.js" />\r
+               <File path="editor/_source/internals/fckxhtml_gecko.js" />\r
+               <File path="editor/_source/internals/fckcodeformatter.js" />\r
+               <File path="editor/_source/internals/fckundo_gecko.js" />\r
+               <File path="editor/_source/classes/fckeditingarea.js" />\r
+               <File path="editor/_source/classes/fckkeystrokehandler.js" />\r
+\r
+               <File path="editor/_source/internals/fcklisthandler.js" />\r
+               <File path="editor/_source/classes/fckelementpath.js" />\r
+               <File path="editor/_source/classes/fckdomrange.js" />\r
+               <File path="editor/_source/classes/fckdomrange_gecko.js" />\r
+               <File path="editor/_source/classes/fckdocumentfragment_gecko.js" />\r
+               <File path="editor/_source/classes/fckw3crange.js" />\r
+               <File path="editor/_source/classes/fckenterkey.js" />\r
+\r
+               <File path="editor/_source/internals/fckdocumentprocessor.js" />\r
+               <File path="editor/_source/internals/fckselection.js" />\r
+               <File path="editor/_source/internals/fckselection_gecko.js" />\r
+\r
+               <File path="editor/_source/internals/fcktablehandler.js" />\r
+               <File path="editor/_source/internals/fcktablehandler_gecko.js" />\r
+               <File path="editor/_source/classes/fckxml_gecko.js" />\r
+               <File path="editor/_source/classes/fckstyledef.js" />\r
+               <File path="editor/_source/classes/fckstyledef_gecko.js" />\r
+               <File path="editor/_source/classes/fckstylesloader.js" />\r
+\r
+               <File path="editor/_source/commandclasses/fcknamedcommand.js" />\r
+               <File path="editor/_source/commandclasses/fck_othercommands.js" />\r
+               <File path="editor/_source/commandclasses/fckspellcheckcommand_gecko.js" />\r
+               <File path="editor/_source/commandclasses/fcktextcolorcommand.js" />\r
+               <File path="editor/_source/commandclasses/fckpasteplaintextcommand.js" />\r
+               <File path="editor/_source/commandclasses/fckpastewordcommand.js" />\r
+               <File path="editor/_source/commandclasses/fcktablecommand.js" />\r
+               <File path="editor/_source/commandclasses/fckstylecommand.js" />\r
+               <File path="editor/_source/commandclasses/fckfitwindow.js" />\r
+               <File path="editor/_source/internals/fckcommands.js" />\r
+\r
+               <File path="editor/_source/classes/fckpanel.js" />\r
+               <File path="editor/_source/classes/fckicon.js" />\r
+               <File path="editor/_source/classes/fcktoolbarbuttonui.js" />\r
+               <File path="editor/_source/classes/fcktoolbarbutton.js" />\r
+               <File path="editor/_source/classes/fckspecialcombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarspecialcombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarfontscombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarfontsizecombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarfontformatcombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarstylecombo.js" />\r
+               <File path="editor/_source/classes/fcktoolbarpanelbutton.js" />\r
+               <File path="editor/_source/internals/fcktoolbaritems.js" />\r
+               <File path="editor/_source/classes/fcktoolbar.js" />\r
+               <File path="editor/_source/classes/fcktoolbarbreak_gecko.js" />\r
+               <File path="editor/_source/internals/fcktoolbarset.js" />\r
+               <File path="editor/_source/internals/fckdialog.js" />\r
+               <File path="editor/_source/internals/fckdialog_gecko.js" />\r
+\r
+               <File path="editor/_source/classes/fckmenuitem.js" />\r
+               <File path="editor/_source/classes/fckmenublock.js" />\r
+               <File path="editor/_source/classes/fckmenublockpanel.js" />\r
+               <File path="editor/_source/classes/fckcontextmenu.js" />\r
+               <File path="editor/_source/internals/fck_contextmenu.js" />\r
+\r
+               <File path="editor/_source/classes/fckplugin.js" />\r
+               <File path="editor/_source/internals/fckplugins.js" />\r
+       </PackageFile>\r
+\r
+</Package>
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/fckstyles.xml b/httemplate/elements/fckeditor/fckstyles.xml
new file mode 100644 (file)
index 0000000..bfd80e7
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8" ?>\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This is the sample style definitions file. It makes the styles combo\r
+ * completely customizable.\r
+ *\r
+ * See FCKConfig.StylesXmlPath in the configuration file.\r
+-->\r
+<Styles>\r
+       <Style name="Image on Left" element="img">\r
+               <Attribute name="style" value="padding: 5px; margin-right: 5px" />\r
+               <Attribute name="border" value="2" />\r
+               <Attribute name="align" value="left" />\r
+       </Style>\r
+       <Style name="Image on Right" element="img">\r
+               <Attribute name="style" value="padding: 5px; margin-left: 5px" />\r
+               <Attribute name="border" value="2" />\r
+               <Attribute name="align" value="right" />\r
+       </Style>\r
+       <Style name="Custom Bold" element="span">\r
+               <Attribute name="style" value="font-weight: bold;" />\r
+       </Style>\r
+       <Style name="Custom Italic" element="em" />\r
+       <Style name="Title" element="span">\r
+               <Attribute name="class" value="Title" />\r
+       </Style>\r
+       <Style name="Code" element="span">\r
+               <Attribute name="class" value="Code" />\r
+       </Style>\r
+       <Style name="Title H3" element="h3" />\r
+       <Style name="Custom Ruler" element="hr">\r
+               <Attribute name="size" value="1" />\r
+               <Attribute name="color" value="#ff0000" />\r
+       </Style>\r
+</Styles>
\ No newline at end of file
diff --git a/httemplate/elements/fckeditor/fcktemplates.xml b/httemplate/elements/fckeditor/fcktemplates.xml
new file mode 100644 (file)
index 0000000..8761062
--- /dev/null
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8" ?>\r
+<!--\r
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben\r
+ *\r
+ * == BEGIN LICENSE ==\r
+ *\r
+ * Licensed under the terms of any of the following licenses at your\r
+ * choice:\r
+ *\r
+ *  - GNU General Public License Version 2 or later (the "GPL")\r
+ *    http://www.gnu.org/licenses/gpl.html\r
+ *\r
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
+ *    http://www.gnu.org/licenses/lgpl.html\r
+ *\r
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
+ *    http://www.mozilla.org/MPL/MPL-1.1.html\r
+ *\r
+ * == END LICENSE ==\r
+ *\r
+ * This is the sample templates definitions file. It makes the "templates"\r
+ * command completely customizable.\r
+ *\r
+ * See FCKConfig.TemplatesXmlPath in the configuration file.\r
+-->\r
+<Templates imagesBasePath="fck_template/images/">\r
+       <Template title="Image and Title" image="template1.gif">\r
+               <Description>One main image with a title and text that surround the image.</Description>\r
+               <Html>\r
+                       <![CDATA[\r
+                               <img style="MARGIN-RIGHT: 10px" height="100" alt="" width="100" align="left"/>\r
+                               <h3>Type the title here</h3>\r
+                               Type the text here\r
+                       ]]>\r
+               </Html>\r
+       </Template>\r
+       <Template title="Strange Template" image="template2.gif">\r
+               <Description>A template that defines two colums, each one with a title, and some text.</Description>\r
+               <Html>\r
+                       <![CDATA[\r
+                               <table cellspacing="0" cellpadding="0" width="100%" border="0">\r
+                                       <tbody>\r
+                                               <tr>\r
+                                                       <td width="50%">\r
+                                                       <h3>Title 1</h3>\r
+                                                       </td>\r
+                                                       <td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </td>\r
+                                                       <td width="50%">\r
+                                                       <h3>Title 2</h3>\r
+                                                       </td>\r
+                                               </tr>\r
+                                               <tr>\r
+                                                       <td>Text 1</td>\r
+                                                       <td>&nbsp;</td>\r
+                                                       <td>Text 2</td>\r
+                                               </tr>\r
+                                       </tbody>\r
+                               </table>\r
+                               More text goes here.\r
+                       ]]>\r
+               </Html>\r
+       </Template>\r
+       <Template title="Text and Table" image="template3.gif">\r
+               <Description>A title with some text and a table.</Description>\r
+               <Html>\r
+                       <![CDATA[\r
+                               <table align="left" width="80%" border="0" cellspacing="0" cellpadding="0"><tr><td>\r
+                                       <h3>Title goes here</h3>\r
+                                       <p>\r
+                                       <table style="FLOAT: right" cellspacing="0" cellpadding="0" width="150" border="1">\r
+                                               <tbody>\r
+                                                       <tr>\r
+                                                               <td align="center" colspan="3"><strong>Table title</strong></td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                       </tr>\r
+                                                       <tr>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                               <td>&nbsp;</td>\r
+                                                       </tr>\r
+                                               </tbody>\r
+                                       </table>\r
+                                       Type the text here</p>\r
+                               </td></tr></table>\r
+                       ]]>\r
+               </Html>\r
+       </Template>\r
+</Templates>\r
diff --git a/httemplate/elements/footer.html b/httemplate/elements/footer.html
new file mode 100644 (file)
index 0000000..32d1219
--- /dev/null
@@ -0,0 +1,5 @@
+        </TD>
+      </TR>
+    </TABLE>
+  </BODY>
+</HTML>
diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css
new file mode 100644 (file)
index 0000000..c310e2f
--- /dev/null
@@ -0,0 +1,16 @@
+* {
+  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:hover {  text-decoration: underline } */
+
+/* a:focus { background-color: #ccccee } */
+
diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html
new file mode 100644 (file)
index 0000000..68be108
--- /dev/null
@@ -0,0 +1,24 @@
+%
+%  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="#e8e8e8" <% $etc %>>
+    <link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
+    <FONT SIZE=6>
+      <CENTER><% $title %></CENTER>
+    </FONT>
+    <BR><!--<BR>-->
diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html
new file mode 100644 (file)
index 0000000..7bf5670
--- /dev/null
@@ -0,0 +1,261 @@
+<!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"> 
+
+    <% include('menu.html', 'freeside_baseurl' => $fsurl,
+                            'position'         => $menu_position,
+              )
+    %>
+
+    <SCRIPT TYPE="text/javascript">
+      function clearhint_search_cust (what) {
+        if ( what.value == '(cust #, name, company or phone)' )
+          what.value = '';
+      }
+
+      function clearhint_search_address2 (what) {
+        if ( what.value == '(Unit #)' )
+          what.value = '';
+      }
+
+      function clearhint_search_invoice (what) {
+        if ( what.value == '(inv #)' )
+          what.value = '';
+      }
+
+      function clearhint_search_svc (what) {
+        if ( what.value == '(user, email, ip, mac, or domain)' )
+          what.value = '';
+      }
+
+      function clearhint_search_ticket (what) {
+        if ( what.value == '(ticket # or subject string)' )
+          what.value = '';
+      }
+    </SCRIPT>
+
+    <% $head %>
+
+  </HEAD>
+  <BODY <% $menu_position eq 'left' ? qq( BACKGROUND="${fsurl}images/background-cheat.png" ) : ' BGCOLOR="#e8e8e8" ' %> <% $etc %> 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 rowspan=2 BGCOLOR="#ffffff"><IMG BORDER=0 ALT="freeside" SRC="<%$fsurl%>images/small-logo.png"></td>
+        <td align=left rowspan=2 BGCOLOR="#ffffff"> <!-- valign="top" -->
+          <font size=6><% $conf->config('company_name') || 'ExampleCo' %></font>
+        </td>
+        <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% getotaker %>&nbsp;</b><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html">Preferences</a>&nbsp;<BR></FONT>
+        </td>
+      </tr>
+      <tr>
+        <td align=right valign=bottom BGCOLOR="#ffffff">
+  
+          <table>
+            <tr>
+              <td align=right BGCOLOR="#ffffff">
+                <FONT SIZE="-2">
+                 <A HREF="http://www.sisd.com/freeside">Freeside</A>&nbsp;v<% $FS::VERSION %><BR>
+                 <A HREF="<% $conf->config('support-key') ? "http://www.sisd.com/mediawiki/index.php/Supported:Documentation" : "http://www.sisd.com/mediawiki/index.php/Freeside:1.9:Documentation" %>">Documentation</A><BR>
+                </FONT>
+              </td>
+% if ( $conf->config('ticket_system') eq 'RT_Internal' ) { 
+% eval "use RT;"; 
+
+                <td bgcolor=#000000></td>
+                <td align=left>
+                  <FONT SIZE="-2">
+                   <A HREF="http://www.bestpractical.com/rt">RT<A>&nbsp;v<% $RT::VERSION %><BR>
+                   <A HREF="http://wiki.bestpractical.com/">Documentation</A><BR>
+                  </FONT>
+                </td>
+% } 
+
+  
+            </tr>
+          </table>
+  
+        </td>
+      </tr>
+    </table>
+
+<style type="text/css">
+input.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;
+        overflow:visible;
+        filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff333333',EndColorStr='#ff666666')
+}
+
+input.fsblackbuttonselected {
+        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;
+        overflow:visible;
+        filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff330033',EndColorStr='#ff7e0079')
+}
+</style>
+
+    <TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0>
+      <TR>
+        <TD COLSPAN=6 WIDTH="100%" STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gradient.png" HEIGHT="13" WIDTH="100%"></TD>
+      </TR>
+
+% if ( $menu_position eq 'top' ) {
+
+      <TR>
+
+        <TD COLSPAN="6" WIDTH="100%" STYLE="padding:0">
+          <SCRIPT TYPE="text/javascript">
+            document.write(myBar);
+          </SCRIPT>
+        </TD>
+
+      </TR>
+
+      <TR>
+        <TD COLSPAN="6" WIDTH="100%" HEIGHT="2px" STYLE="padding:0" BGCOLOR="#000000">
+        </TD>
+      </TR>
+      
+      <TR>
+        <TD COLSPAN="6" WIDTH="100%" HEIGHT="4px" STYLE="padding:0" BGCOLOR="#000000">
+        </TD>
+      </TR>
+
+% }
+
+      <TR>
+
+        <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right">
+          <FORM ACTION="<%$fsurl%>edit/cust_main.cgi" METHOD="GET" STYLE="margin:0">
+            <INPUT TYPE="submit" VALUE="New customer" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="vertical-align:bottom; font-size:100%">
+          </FORM>
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right">
+          <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0">
+            <INPUT NAME="search_cust" TYPE="text" VALUE="(cust #, name, company or phone)" SIZE="28" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" STYLE="vertical-align:bottom;text-align:right"><BR>
+            <A HREF="<%$fsurl%>search/report_cust_main.html" STYLE="color: #ffffff; font-size: 70%">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:70%">
+          </FORM>
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="center">
+% 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="(Unit #)" SIZE="4" onFocus="clearhint_search_address2(this);" onClick="clearhint_search_address2(this);" STYLE="vertical-align:bottom;text-align:right;margin-bottom:1px">
+              <BR>
+              <INPUT TYPE="submit" VALUE="Search units" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%;padding-left:2px;padding-right:2px">
+            </FORM>
+% } 
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right">
+% if ( $FS::CurrentUser::CurrentUser->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 #)" SIZE="4" onFocus="clearhint_search_invoice(this);" onClick="clearhint_search_invoice(this);" STYLE="vertical-align:bottom;text-align:right;margin-bottom:1px">
+%   if ( $FS::CurrentUser::CurrentUser->access_right('List invoices') ) { 
+
+                <A HREF="<%$fsurl%>search/report_cust_bill.html" STYLE="color: #ffffff; font-size: 70%">Advanced</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:70%">
+            </FORM>
+% } 
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right">
+          <FORM ACTION="<%$fsurl%>search/cust_svc.html" METHOD="GET" STYLE="margin:0">
+            <INPUT NAME="search_svc" TYPE="text" VALUE="(user, email, ip, mac, or domain)" SIZE="26" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" STYLE="vertical-align:bottom;text-align:right"><BR>
+            <A NOTYET="<%$fsurl%>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%">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:70%">
+          </FORM>
+        </TD>
+
+        <TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right" STYLE="padding-right:4px">
+% if ( $conf->config("ticket_system") ) { 
+          <FORM ACTION="<% FS::TicketSystem->baseurl %>index.html" METHOD="GET" STYLE="margin:0">
+            <INPUT NAME="q" TYPE="text" VALUE="(ticket # or subject string)" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" STYLE="vertical-align:bottom;text-align:right"><BR>
+            <A HREF="<% FS::TicketSystem->baseurl %>Search/Build.html" STYLE="color: #ffffff; font-size: 70%">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:70%;padding-left:2px;padding-right:2px">
+          </FORM>
+% }
+        </TD>
+
+      </TR>
+    </TABLE>
+
+    <TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4>
+
+      <TR>
+
+% if ( $menu_position eq 'left' ) {
+
+        <TD BGCOLOR="#000000" STYLE="padding:0" WIDTH="154"></TD>
+        <TD STYLE="padding:0" WIDTH="13"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-corner.png"></TD>
+
+% }
+
+        <TD STYLE="padding:0" WIDTH="100%"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-top.png" HEIGHT="13" WIDTH="100%"></TD>
+
+      </TR>
+
+      <TR HEIGHT="100%">
+
+% if ( $menu_position eq 'left' ) {
+
+        <TD BGCOLOR="#000000" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right">
+          <SCRIPT TYPE="text/javascript">
+            document.write(myBar);
+          </SCRIPT>
+          <BR>
+          <IMG SRC="<%$fsurl%>images/32clear.gif" HEIGHT="1" WIDTH="154">
+
+        </TD>
+        <TD STYLE="padding:0" HEIGHT="100%" WIDTH=13 VALIGN="top"><IMG WIDTH="13" HEIGHT="100%" BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-side.png"></TD>
+
+% }
+
+        <TD BGCOLOR="#e8e8e8" HEIGHT="100%" VALIGN="top"> <!-- WIDTH="100%"> -->
+
+          <FONT SIZE=6>
+            <% $title %>
+          </FONT>
+
+          <BR><BR>
+          <% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %>
+<%init>
+
+my($title, $menubar) = ( shift, shift );
+my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
+my $conf = new FS::Conf;
+
+my $menu_position = $FS::CurrentUser::CurrentUser->option('menu_position')
+                    || 'left';
+
+</%init>
diff --git a/httemplate/elements/hidden.html b/httemplate/elements/hidden.html
new file mode 100644 (file)
index 0000000..8311081
--- /dev/null
@@ -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/iframecontentmws.js b/httemplate/elements/iframecontentmws.js
new file mode 100644 (file)
index 0000000..c809989
--- /dev/null
@@ -0,0 +1,20 @@
+/*\r
+ iframecontentmws.js - Foteos Macrides\r
+   Initial: October 10, 2004 - Last Revised: May 9, 2005\r
+ Simple script for using an HTML file as iframe content in overlibmws popups.\r
+ Include WRAP and TEXTPADDING,0 in the overlib call to ensure that the width\r
+ arg is respected (unless the CAPTION plus CLOSETEXT widths add up to more than\r
+ the width arg, in which case you should increase the width arg).  The name arg\r
+ should be a unique string for each popup with iframe content in the document.\r
+ The frameborder arg should be 1 (browser default if omitted) or 0.\r
+\r
+ See http://www.macridesweb.com/oltest/IFRAME.html for demonstration.\r
+*/\r
+\r
+function OLiframeContent(src, width, height, name, frameborder) {\r
+ return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'\r
+ +(name!=null?' name="'+name+'" id="'+name+'"':'')\r
+ +(frameborder!=null?' frameborder="'+frameborder+'"':'')\r
+ +' scrolling="auto">'\r
+ +'<div>[iframe not supported]</div></iframe>');\r
+}\r
diff --git a/httemplate/elements/jsrsClient.js b/httemplate/elements/jsrsClient.js
new file mode 100644 (file)
index 0000000..3a2572c
--- /dev/null
@@ -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 (file)
index 0000000..f37b0aa
--- /dev/null
@@ -0,0 +1,4 @@
+%
+%  my $server = new FS::UI::Web::JSRPC '', $cgi;
+%
+<% $server->process %>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
new file mode 100644 (file)
index 0000000..f43fca1
--- /dev/null
@@ -0,0 +1,410 @@
+<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">
+
+% }
+
+<link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
+
+<SCRIPT TYPE="text/javascript">
+
+  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 %>
+          myBar.add(new WebFXMenuButton("<% $item %>", null, "<% $tooltip %>", <% $submenuname %> ));
+
+%     } 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;
+
+#Active tickets not assigned to a customer
+
+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',
+  'List customers' => [ \%report_customers_lists, '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_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" ),
+        '',
+      ];
+  }
+
+  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{'Advanced package reports'} =  [ $fsurl.'search/report_cust_pkg.html', 'by agent, date range, status, package definition' ];
+
+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_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_financial, 'Tie::IxHash', 
+  '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)' ],
+  'Credit Report' => [ $fsurl.'search/report_cust_credit.html', 'Credit report (by employee and/or date range)' ],
+  'Payment Report' => [ $fsurl.'search/report_cust_pay.html', 'Payment report (by type and/or date range)' ],
+;
+$report_financial{'Payment Batch Report'} = [ $fsurl.'search/pay_batch.html', 'Payment batches (by status and/or date range)' ]
+  if $conf->exists('batch-enable');
+$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' ];
+;
+
+tie my %report_menu, 'Tie::IxHash';
+$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{'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{'Usage'} =  [ \%report_rating,    'Usage reports'  ]
+  if $curuser->access_right('List rating data');
+$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');
+
+tie my %tools_importing, 'Tie::IxHash',
+  'Import customers from CSV file' => [ $fsurl.'misc/cust_main-import.cgi', '' ],
+  'Import payments from CSV file' => [ $fsurl.'misc/cust_pay-import.cgi', '' ],
+
+  'Import customer comments from CSV file' => [ $fsurl.'misc/cust_main_note-import.html', '' ],
+  'Import one-time charges from CSV file' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ],
+  'Import Call Detail Records (CDRs) from CSV file' => [ $fsurl.'misc/cdr-import.html', '' ],
+;
+
+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_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') && $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{'Time Queue'} =  [ $fsurl.'search/timeworked.html', 'View pending support time' ]
+  if $curuser->access_right('Time queue');
+$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',
+  'View/Edit employees' => [ $fsurl.'browse/access_user.html', 'Setup internal users' ],
+  'View/Edit employee groups' => [ $fsurl.'browse/access_group.html', 'Employee groups allow you to control access to the backend' ],
+;
+
+tie my %config_export_svc_pkg, 'Tie::IxHash', ();
+if ( $curuser->access_right('Configuration') ) {
+  $config_export_svc_pkg{'View/Edit exports'} = [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ];
+  $config_export_svc_pkg{'View/Edit service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ];
+}
+$config_export_svc_pkg{'View/Edit 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') ) {
+  $config_export_svc_pkg{'View/Edit package classes'} =  [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ];
+  $config_export_svc_pkg{'View/Edit cancel reason types'} = [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons, for reporting and convenience purposes.' ];
+  $config_export_svc_pkg{'View/Edit cancel reasons'} = [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ];
+  $config_export_svc_pkg{'View/Edit suspend reason types'} = [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons, for reporting and convenience purposes.' ];
+  $config_export_svc_pkg{'View/Edit suspend reasons'} = [ $fsurl.'browse/reason.html?class=S', 'Suspend reasons explain why a service was suspended.' ];
+}
+
+tie my %config_agent, 'Tie::IxHash',
+  'View/Edit agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ],
+  'View/Edit 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)' ],
+  'View/Edit agent payment gateways'         => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors for agent overrides' ];
+;
+
+tie my %config_billing_rates, 'Tie::IxHash',
+  'View/Edit rate plans' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans' ],
+  'View/Edit regions and prefixes' => [ $fsurl.'browse/rate_region.html', 'Manage regions and prefixes' ],
+;
+
+tie my %config_billing, 'Tie::IxHash';
+#  'View/Edit payment gateways'         => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ];
+$config_billing{'View/Edit 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{'View/Edit invoice events'}         = [ $fsurl.'browse/part_bill_event.cgi', 'Deprecated, old-style actions for overdue invoices' ];
+  $config_billing{'View/Edit invoice templates'}      = [ $fsurl.'browse/invoice_template.html', 'Edit templates for HTML, plaintext and typeset invoices' ];
+  $config_billing{'View/Edit prepaid cards'}          = [ $fsurl.'search/prepay_credit.html', 'View outstanding cards, generate new cards' ];
+  $config_billing{'View/Edit call rates and regions'} = [ \%config_billing_rates, 'Manage rate plans, regions and prefixes for VoIP and call billing' ];
+  $config_billing{'View/Edit locales and tax rates'}  = [ $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{'View/Edit credit reason types'}  = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons, for reporting and convenience purposes.' ];
+  $config_billing{'View/Edit credit reasons'}  = [ $fsurl.'browse/reason.html?class=R', 'Credit reasons explain why a credit was issued.' ];
+}
+
+tie my %config_dialup, 'Tie::IxHash',
+  'View/Edit access numbers' => [ $fsurl.'browse/svc_acct_pop.cgi', 'Points of Presence' ],
+;
+
+tie my %config_broadband, 'Tie::IxHash',
+  'View/Edit routers'        => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ],
+  'View/Edit address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ],
+;
+
+tie my %config_misc, 'Tie::IxHash';
+$config_misc{'View/Edit advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service.  Tracked for informational purposes' ]
+  if $curuser->access_right('Edit advertising sources')
+  || $curuser->access_right('Edit global advertising sources');
+if ( $curuser->access_right('Configuration') ) {
+  $config_misc{'View/Edit virtual fields'} = [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ];
+  $config_misc{'View/Edit message catalog'} = [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ];
+  $config_misc{'View/Edit inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ];
+}
+
+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, services and packages'} =
+    [ \%config_export_svc_pkg, ''    ]
+  if    $curuser->access_right('Configuration' )
+     || $curuser->access_right('Edit package definitions')
+     || $curuser->access_right('Edit global package definitions');
+$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');
+if ( $curuser->access_right('Configuration') ) {
+  $config_menu{'Dialup'}  = [ \%config_dialup, ''    ];
+  $config_menu{'Fixed (username-less) broadband'} = 
+                            [ \%config_broadband, ''    ];
+}
+$config_menu{'Miscellaneous'} = [ \%config_misc, ''    ]
+  if $curuser->access_right('Edit advertising sources')
+  || $curuser->access_right('Edit global advertising sources');
+
+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{'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('Edit advertising sources')
+  || $curuser->access_right('Edit global advertising sources');
+
+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".
+
+  (
+    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 = 280;\n",
+
+  "myMenu$menunum";
+
+}
+
+</%init>
+
diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html
new file mode 100644 (file)
index 0000000..ec6c13f
--- /dev/null
@@ -0,0 +1,10 @@
+%
+%  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) %>
diff --git a/httemplate/elements/overlibmws.js b/httemplate/elements/overlibmws.js
new file mode 100644 (file)
index 0000000..5ef50d6
--- /dev/null
@@ -0,0 +1,697 @@
+/*\r
+ Do not remove or change this notice.\r
+ overlibmws.js core module - Copyright Foteos Macrides 2002-2005. All rights reserved.\r
+   Initial: August 18, 2002 - Last Revised: May 30, 2006\r
+ This module is subject to the same terms of usage as for Erik Bosrup's overLIB,\r
+ though only a minority of the code and API now correspond with Erik's version.\r
+ See the overlibmws Change History and Command Reference via:\r
+\r
+       http://www.macridesweb.com/oltest/\r
+\r
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html\r
+ Give credit on sites that use overlibmws and submit changes so others can use them as well.\r
+ You can get Erik's version via: http://www.bosrup.com/web/overlib/\r
+*/\r
+\r
+// PRE-INIT -- Ignore these lines, configuration is below.\r
+var OLloaded=0,pmCnt=1,pMtr=new Array(),OLcmdLine=new Array(),OLrunTime=new Array(),OLv,OLudf,\r
+OLpct=new Array("83%","67%","83%","100%","117%","150%","200%","267%"),OLrefXY,\r
+OLbubblePI=0,OLcrossframePI=0,OLdebugPI=0,OLdraggablePI=0,OLexclusivePI=0,OLfilterPI=0,\r
+OLfunctionPI=0,OLhidePI=0,OLiframePI=0,OLovertwoPI=0,OLscrollPI=0,OLshadowPI=0,OLprintPI=0;\r
+if(typeof OLgateOK=='undefined')var OLgateOK=1;\r
+var OLp1or2c='inarray,caparray,caption,closetext,right,left,center,autostatuscap,padx,pady,'\r
++'below,above,vcenter,donothing',OLp1or2co='nofollow,background,offsetx,offsety,fgcolor,'\r
++'bgcolor,cgcolor,textcolor,capcolor,width,wrap,wrapmax,height,border,base,status,autostatus,'\r
++'snapx,snapy,fixx,fixy,relx,rely,midx,midy,ref,refc,refp,refx,refy,fgbackground,bgbackground,'\r
++'cgbackground,fullhtml,capicon,textfont,captionfont,textsize,captionsize,timeout,delay,hauto,'\r
++'vauto,nojustx,nojusty,fgclass,bgclass,cgclass,capbelow,textpadding,textfontclass,'\r
++'captionpadding,captionfontclass,sticky,noclose,mouseoff,offdelay,closecolor,closefont,'\r
++'closesize,closeclick,closetitle,closefontclass,decode',OLp1or2o='text,cap,close,hpos,vpos,'\r
++'padxl,padxr,padyt,padyb',OLp1co='label',OLp1or2=OLp1or2co+','+OLp1or2o,OLp1=OLp1co+','+'frame';\r
+OLregCmds(OLp1or2c+','+OLp1or2co+','+OLp1co);\r
+function OLud(v){return eval('typeof ol_'+v+'=="undefined"')?1:0;}\r
+\r
+// DEFAULT CONFIGURATION -- See overlibConfig.txt for descriptions\r
+if(OLud('fgcolor'))var ol_fgcolor="#ccccff";\r
+if(OLud('bgcolor'))var ol_bgcolor="#333399";\r
+if(OLud('cgcolor'))var ol_cgcolor="#333399";\r
+if(OLud('textcolor'))var ol_textcolor="#000000";\r
+if(OLud('capcolor'))var ol_capcolor="#ffffff";\r
+if(OLud('closecolor'))var ol_closecolor="#eeeeff";\r
+if(OLud('textfont'))var ol_textfont="Verdana,Arial,Helvetica";\r
+if(OLud('captionfont'))var ol_captionfont="Verdana,Arial,Helvetica";\r
+if(OLud('closefont'))var ol_closefont="Verdana,Arial,Helvetica";\r
+if(OLud('textsize'))var ol_textsize=1;\r
+if(OLud('captionsize'))var ol_captionsize=1;\r
+if(OLud('closesize'))var ol_closesize=1;\r
+if(OLud('fgclass'))var ol_fgclass="";\r
+if(OLud('bgclass'))var ol_bgclass="";\r
+if(OLud('cgclass'))var ol_cgclass="";\r
+if(OLud('textpadding'))var ol_textpadding=2;\r
+if(OLud('textfontclass'))var ol_textfontclass="";\r
+if(OLud('captionpadding'))var ol_captionpadding=2;\r
+if(OLud('captionfontclass'))var ol_captionfontclass="";\r
+if(OLud('closefontclass'))var ol_closefontclass="";\r
+if(OLud('close'))var ol_close="Close";\r
+if(OLud('closeclick'))var ol_closeclick=0;\r
+if(OLud('closetitle'))var ol_closetitle="Click to Close";\r
+if(OLud('text'))var ol_text="Default Text";\r
+if(OLud('cap'))var ol_cap="";\r
+if(OLud('capbelow'))var ol_capbelow=0;\r
+if(OLud('background'))var ol_background="";\r
+if(OLud('width'))var ol_width=200;\r
+if(OLud('wrap'))var ol_wrap=0;\r
+if(OLud('wrapmax'))var ol_wrapmax=0;\r
+if(OLud('height'))var ol_height= -1;\r
+if(OLud('border'))var ol_border=1;\r
+if(OLud('base'))var ol_base=0;\r
+if(OLud('offsetx'))var ol_offsetx=10;\r
+if(OLud('offsety'))var ol_offsety=10;\r
+if(OLud('sticky'))var ol_sticky=0;\r
+if(OLud('nofollow'))var ol_nofollow=0;\r
+if(OLud('noclose'))var ol_noclose=0;\r
+if(OLud('mouseoff'))var ol_mouseoff=0;\r
+if(OLud('offdelay'))var ol_offdelay=300;\r
+if(OLud('hpos'))var ol_hpos=RIGHT;\r
+if(OLud('vpos'))var ol_vpos=BELOW;\r
+if(OLud('status'))var ol_status="";\r
+if(OLud('autostatus'))var ol_autostatus=0;\r
+if(OLud('snapx'))var ol_snapx=0;\r
+if(OLud('snapy'))var ol_snapy=0;\r
+if(OLud('fixx'))var ol_fixx= -1;\r
+if(OLud('fixy'))var ol_fixy= -1;\r
+if(OLud('relx'))var ol_relx=null;\r
+if(OLud('rely'))var ol_rely=null;\r
+if(OLud('midx'))var ol_midx=null;\r
+if(OLud('midy'))var ol_midy=null;\r
+if(OLud('ref'))var ol_ref="";\r
+if(OLud('refc'))var ol_refc='UL';\r
+if(OLud('refp'))var ol_refp='UL';\r
+if(OLud('refx'))var ol_refx=0;\r
+if(OLud('refy'))var ol_refy=0;\r
+if(OLud('fgbackground'))var ol_fgbackground="";\r
+if(OLud('bgbackground'))var ol_bgbackground="";\r
+if(OLud('cgbackground'))var ol_cgbackground="";\r
+if(OLud('padxl'))var ol_padxl=1;\r
+if(OLud('padxr'))var ol_padxr=1;\r
+if(OLud('padyt'))var ol_padyt=1;\r
+if(OLud('padyb'))var ol_padyb=1;\r
+if(OLud('fullhtml'))var ol_fullhtml=0;\r
+if(OLud('capicon'))var ol_capicon="";\r
+if(OLud('frame'))var ol_frame=self;\r
+if(OLud('timeout'))var ol_timeout=0;\r
+if(OLud('delay'))var ol_delay=0;\r
+if(OLud('hauto'))var ol_hauto=0;\r
+if(OLud('vauto'))var ol_vauto=0;\r
+if(OLud('nojustx'))var ol_nojustx=0;\r
+if(OLud('nojusty'))var ol_nojusty=0;\r
+if(OLud('label'))var ol_label="";\r
+if(OLud('decode'))var ol_decode=0;\r
+// ARRAY CONFIGURATION - See overlibConfig.txt for descriptions.\r
+if(OLud('texts'))var ol_texts=new Array("Text 0","Text 1");\r
+if(OLud('caps'))var ol_caps=new Array("Caption 0","Caption 1");\r
+// END CONFIGURATION -- Don't change anything below, all configuration is above.\r
+\r
+// INIT -- Runtime variables.\r
+var o3_text="",o3_cap="",o3_sticky=0,o3_nofollow=0,o3_background="",o3_noclose=0,o3_mouseoff=0,\r
+o3_offdelay=300,o3_hpos=RIGHT,o3_offsetx=10,o3_offsety=10,o3_fgcolor="",o3_bgcolor="",\r
+o3_cgcolor="",o3_textcolor="",o3_capcolor="",o3_closecolor="",o3_width=200,o3_wrap=0,\r
+o3_wrapmax=0,o3_height= -1,o3_border=1,o3_base=0,o3_status="",o3_autostatus=0,o3_snapx=0,\r
+o3_snapy=0,o3_fixx= -1,o3_fixy= -1,o3_relx=null,o3_rely=null,o3_midx=null,o3_midy=null,o3_ref="",\r
+o3_refc='UL',o3_refp='UL',o3_refx=0,o3_refy=0,o3_fgbackground="",o3_bgbackground="",\r
+o3_cgbackground="",o3_padxl=0,o3_padxr=0,o3_padyt=0,o3_padyb=0,o3_fullhtml=0,o3_vpos=BELOW,\r
+o3_capicon="",o3_textfont="Verdana,Arial,Helvetica",o3_captionfont="",o3_closefont="",\r
+o3_textsize=1,o3_captionsize=1,o3_closesize=1,o3_frame=self,o3_timeout=0,o3_delay=0,o3_hauto=0,\r
+o3_vauto=0,o3_nojustx=0,o3_nojusty=0,o3_close="",o3_closeclick=0,o3_closetitle="",o3_fgclass="",\r
+o3_bgclass="",o3_cgclass="",o3_textpadding=2,o3_textfontclass="",o3_captionpadding=2,\r
+o3_captionfontclass="",o3_closefontclass="",o3_capbelow=0,o3_label="",o3_decode=0,\r
+CSSOFF=DONOTHING,CSSCLASS=DONOTHING,OLdelayid=0,OLtimerid=0,OLshowid=0,OLndt=0,over=null,\r
+OLfnRef="",OLhover=0,OLx=0,OLy=0,OLshowingsticky=0,OLallowmove=0,OLcC=null,\r
+OLua=navigator.userAgent.toLowerCase(),\r
+OLns4=(navigator.appName=='Netscape'&&parseInt(navigator.appVersion)==4),\r
+OLns6=(document.getElementById)?1:0,\r
+OLie4=(document.all)?1:0,\r
+OLgek=(OLv=OLua.match(/gecko\/(\d{8})/i))?parseInt(OLv[1]):0,\r
+OLmac=(OLua.indexOf('mac')>=0)?1:0,\r
+OLsaf=(OLua.indexOf('safari')>=0)?1:0,\r
+OLkon=(OLua.indexOf('konqueror')>=0)?1:0,\r
+OLkht=(OLsaf||OLkon)?1:0,\r
+OLopr=(OLua.indexOf('opera')>=0)?1:0,\r
+OLop7=(OLopr&&document.createTextNode)?1:0;\r
+if(OLopr){OLns4=OLns6=0;if(!OLop7)OLie4=0;}\r
+var OLieM=((OLie4&&OLmac)&&!(OLkht||OLopr))?1:0,\r
+OLie5=0,OLie55=0;if(OLie4&&!OLop7){\r
+if((OLv=OLua.match(/msie (\d\.\d+)\.*/i))&&(OLv=parseFloat(OLv[1]))>=5.0){\r
+OLie5=1;OLns6=0;if(OLv>=5.5)OLie55=1;}if(OLns6)OLie4=0;}\r
+if(OLns4)window.onresize=function(){location.reload();}\r
+var OLchkMh=1,OLdw;\r
+if(OLns4||OLie4||OLns6)OLmh();\r
+else{overlib=nd=cClick=OLpageDefaults=no_overlib;}\r
+\r
+/*\r
+ PUBLIC FUNCTIONS\r
+*/\r
+// Loads defaults then args into runtime variables.\r
+function overlib(){\r
+if(!(OLloaded&&OLgateOK))return;\r
+if((OLexclusivePI)&&OLisExclusive(arguments))return true;\r
+if(OLchkMh)OLmh();\r
+if(OLndt&&!OLtimerid)OLndt=0;if(over)cClick();\r
+OLload(OLp1or2);OLload(OLp1);\r
+OLfnRef="";OLhover=0;\r
+OLsetRunTimeVar();\r
+OLparseTokens('o3_',arguments);\r
+if(!(over=OLmkLyr()))return false;\r
+if(o3_decode)OLdecode();\r
+if(OLprintPI)OLchkPrint();\r
+if(OLbubblePI)OLchkForBubbleEffect();\r
+if(OLdebugPI)OLsetDebugCanShow();\r
+if(OLshadowPI)OLinitShadow();\r
+if(OLiframePI)OLinitIfs();\r
+if(OLfilterPI)OLinitFilterLyr();\r
+if(OLexclusivePI&&o3_exclusive&&o3_exclusivestatus!="")o3_status=o3_exclusivestatus;\r
+else if(o3_autostatus==2&&o3_cap!="")o3_status=o3_cap;\r
+else if(o3_autostatus==1&&o3_text!="")o3_status=o3_text;\r
+if(!o3_delay){return OLmain();\r
+}else{OLdelayid=setTimeout("OLmain()",o3_delay);\r
+if(o3_status!=""){self.status=o3_status;return true;}\r
+else if(!(OLop7&&event&&event.type=='mouseover'))return false;}\r
+}\r
+\r
+// Clears popups if appropriate\r
+function nd(time){\r
+if(OLloaded&&OLgateOK){if(!((OLexclusivePI)&&OLisExclusive())){\r
+if(time&&over&&!o3_delay){if(OLtimerid>0)clearTimeout(OLtimerid);\r
+OLtimerid=(OLhover&&o3_frame==self&&!OLcursorOff())?0:\r
+setTimeout("cClick()",(o3_timeout=OLndt=time));}else{\r
+if(!OLshowingsticky){OLallowmove=0;if(over)OLhideObject(over);}}}}\r
+return false;\r
+}\r
+\r
+// Close function for stickies\r
+function cClick(){\r
+if(OLloaded&&OLgateOK){OLhover=0;if(over){\r
+if(OLovertwoPI&&over==over2)cClick2();OLhideObject(over);OLshowingsticky=0;}}\r
+return false;\r
+}\r
+\r
+// Sets page-specific defaults.\r
+function OLpageDefaults(){\r
+OLparseTokens('ol_',arguments);\r
+}\r
+\r
+// For unsupported browsers.\r
+function no_overlib(){return false;}\r
+\r
+/*\r
+ OVERLIB MAIN FUNCTION SET\r
+*/\r
+function OLmain(){\r
+o3_delay=0;\r
+if(o3_frame==self){if(o3_noclose)OLoptMOUSEOFF(0);else if(o3_mouseoff)OLoptMOUSEOFF(1);}\r
+if(o3_sticky)OLshowingsticky=1;OLdoLyr();OLallowmove=0;if(o3_timeout>0){\r
+if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("cClick()",o3_timeout);}\r
+if(o3_ref){OLrefXY=OLgetRefXY(o3_ref);if(OLrefXY[0]==null){o3_ref="";o3_midx=0;o3_midy=0;}}\r
+OLdisp(o3_status);if(OLdraggablePI)OLcheckDrag();\r
+if(o3_status!="")return true;else if(!(OLop7&&event&&event.type=='mouseover'))return false;\r
+}\r
+\r
+// Loads o3_ variables\r
+function OLload(c){var i,m=c.split(',');for(i=0;i<m.length;i++)eval('o3_'+m[i]+'=ol_'+m[i]);}\r
+\r
+// Chooses LGF \r
+function OLdoLGF(){\r
+return (o3_background!=''||o3_fullhtml)?OLcontentBackground(o3_text,o3_background,o3_fullhtml):\r
+(o3_cap=="")?OLcontentSimple(o3_text):\r
+(o3_sticky)?OLcontentCaption(o3_text,o3_cap,o3_close):OLcontentCaption(o3_text,o3_cap,'');\r
+}\r
+\r
+// Makes Layer\r
+function OLmkLyr(id,f,z){\r
+id=(id||'overDiv');f=(f||o3_frame);z=(z||1000);var fd=f.document,d=OLgetRefById(id,fd);\r
+if(!d){if(OLns4)d=fd.layers[id]=new Layer(1024,f);else if(OLie4&&!document.getElementById){\r
+fd.body.insertAdjacentHTML('BeforeEnd','<div id="'+id+'"></div>');d=fd.all[id];\r
+}else{d=fd.createElement('div');if(d){d.id=id;fd.body.appendChild(d);}}if(!d)return null;\r
+if(OLns4)d.zIndex=z;else{var o=d.style;o.position='absolute';o.visibility='hidden';o.zIndex=z;}}\r
+return d;\r
+}\r
+\r
+// Creates and writes layer content\r
+function OLdoLyr(){\r
+if(o3_background==''&&!o3_fullhtml){\r
+if(o3_fgbackground!='')o3_fgbackground=' background="'+o3_fgbackground+'"';\r
+if(o3_bgbackground!='')o3_bgbackground=' background="'+o3_bgbackground+'"';\r
+if(o3_cgbackground!='')o3_cgbackground=' background="'+o3_cgbackground+'"';\r
+if(o3_fgcolor!='')o3_fgcolor=' bgcolor="'+o3_fgcolor+'"';\r
+if(o3_bgcolor!='')o3_bgcolor=' bgcolor="'+o3_bgcolor+'"';\r
+if(o3_cgcolor!='')o3_cgcolor=' bgcolor="'+o3_cgcolor+'"';\r
+if(o3_height>0)o3_height=' height="'+o3_height+'"';else o3_height='';}\r
+if(!OLns4)OLrepositionTo(over,(OLns6?20:0),0);var lyrHtml=OLdoLGF();\r
+if(o3_sticky&&OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}\r
+if(o3_wrap&&!o3_fullhtml){OLlayerWrite(lyrHtml);\r
+o3_width=(OLns4?over.clip.width:over.offsetWidth);\r
+if(OLns4&&o3_wrapmax<1)o3_wrapmax=o3_frame.innerWidth-40;\r
+o3_wrap=0;if(o3_wrapmax>0&&o3_width>o3_wrapmax)o3_width=o3_wrapmax;lyrHtml=OLdoLGF();}\r
+OLlayerWrite(lyrHtml);o3_width=(OLns4?over.clip.width:over.offsetWidth);\r
+if(OLbubblePI)OLgenerateBubble(lyrHtml);\r
+}\r
+\r
+/*\r
+ LAYER GENERATION FUNCTIONS\r
+*/\r
+// Makes simple table without caption\r
+function OLcontentSimple(txt){\r
+var t=OLbgLGF()+OLfgLGF(txt)+OLbaseLGF();\r
+OLsetBackground('');return t;\r
+}\r
+\r
+// Makes table with caption and optional close link\r
+function OLcontentCaption(txt,title,close){\r
+var closing=(OLprintPI?OLprintCapLGF():''),closeevent='onmouseover',caption,t,\r
+cC='javascript:return '+OLfnRef+(OLovertwoPI&&over==over2?'cClick2();':'cClick();');\r
+if(o3_closeclick)closeevent=(o3_closetitle?'title="'+o3_closetitle+'" ':'')+'onclick';\r
+if(o3_capicon!='')o3_capicon='<img src="'+o3_capicon+'" /> ';\r
+if(close){closing+='<td align="right"><a href="'+cC+'" '\r
++closeevent+'="'+cC+'"'+(o3_closefontclass?' class="'+o3_closefontclass\r
++'">':'>'+OLlgfUtil(0,0,'','span',o3_closecolor,o3_closefont,o3_closesize))+close\r
++(o3_closefontclass?'':OLlgfUtil(1,0,'','span'))+'</a></td>';}\r
+caption='<table'+OLwd(0)+' border="0" cellpadding="'+o3_captionpadding+'" cellspacing="0"'\r
++(o3_cgclass?' class="'+o3_cgclass+'"':o3_cgcolor+o3_cgbackground)+'><tr><td'+OLwd(0)\r
++(o3_cgclass?' class="'+o3_cgclass+'">':'>')+(o3_captionfontclass?'<div class="'\r
++o3_captionfontclass+'">':OLlgfUtil(0,1,'','div',o3_capcolor,o3_captionfont,\r
+o3_captionsize))+o3_capicon+title+OLlgfUtil(1,1,'','div')+'</td>'+closing+'</tr></table>';\r
+t=OLbgLGF()+(o3_capbelow?OLfgLGF(txt)+caption:caption+OLfgLGF(txt))+OLbaseLGF();\r
+OLsetBackground('');return t;\r
+}\r
+\r
+// For BACKGROUND and FULLHTML commands\r
+function OLcontentBackground(txt, image, hasfullhtml){\r
+var t;if(hasfullhtml){t=txt;}else{t='<table'+OLwd(1)\r
++' border="0" cellpadding="0" cellspacing="0" '+'height="'+o3_height\r
++'"><tr><td colspan="3" height="'+o3_padyt+'"></td></tr><tr><td width="'\r
++o3_padxl+'"></td><td valign="top"'+OLwd(2)+'>'\r
++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+txt+\r
+OLlgfUtil(1,0,'','div')+'</td><td width="'+o3_padxr+'"></td></tr><tr><td colspan="3" height="'\r
++o3_padyb+'"></td></tr></table>';}\r
+OLsetBackground(image);return t;\r
+}\r
+\r
+// LGF utilities\r
+function OLbgLGF(){\r
+return '<table'+OLwd(1)+o3_height+' border="0" cellpadding="'+o3_border+'" cellspacing="0"'\r
++(o3_bgclass?' class="'+o3_bgclass+'"':o3_bgcolor+o3_bgbackground)+'><tr><td>';\r
+}\r
+function OLfgLGF(t){\r
+return '<table'+OLwd(0)+o3_height+' border="0" cellpadding="'+o3_textpadding\r
++'" cellspacing="0"'+(o3_fgclass?' class="'+o3_fgclass+'"':o3_fgcolor+o3_fgbackground)\r
++'><tr><td valign="top"'+(o3_fgclass?' class="'+o3_fgclass+'"':'')+'>'\r
++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+t\r
++(OLprintPI?OLprintFgLGF():'')+OLlgfUtil(1,0,'','div')+'</td></tr></table>';\r
+}\r
+function OLlgfUtil(end,stg,tfc,ele,col,fac,siz){\r
+if(end)return ('</'+(OLns4?'font'+(stg?'></strong':''):ele)+'>');else return (tfc?'<div '\r
++'class="' +tfc +'">':('<'+(OLns4?(stg?'strong><':'')+'font color="'+col+'" face="'\r
++OLquoteMultiNameFonts(fac)+'" size="'+siz:ele+' style="color:'+col\r
++(stg?';font-weight:bold':'')+';font-family:'+OLquoteMultiNameFonts(fac)+';font-size:'\r
++siz+';'+(ele=='span'?'text-decoration:underline;':''))+'">'));\r
+}\r
+function OLquoteMultiNameFonts(f){\r
+var i,v,pM=f.split(',');\r
+for(i=0;i<pM.length;i++){v=pM[i];v=v.replace(/^\s+/,'').replace(/\s+$/,'');\r
+if(/\s/.test(v) && !/['"]/.test(v)){v="\'"+v+"\'";pM[i]=v;}}\r
+return pM.join();\r
+}\r
+function OLbaseLGF(){\r
+return ((o3_base>0&&!o3_wrap)?('<table width="100%" border="0" cellpadding="0" cellspacing="0"'\r
++(o3_bgclass?' class="'+o3_bgclass+'"':'')+'><tr><td height="'+o3_base\r
++'"></td></tr></table>'):'')+'</td></tr></table>';\r
+}\r
+function OLwd(a){\r
+return(o3_wrap?'':' width="'+(!a?'100%':(a==1?o3_width:(o3_width-o3_padxl-o3_padxr)))+'"');\r
+}\r
+\r
+// Loads image into the div.\r
+function OLsetBackground(i){\r
+if(i==''){if(OLns4)over.background.src=null;\r
+else{if(OLns6)over.style.width='';over.style.backgroundImage='none';}\r
+}else{if(OLns4)over.background.src=i;\r
+else{if(OLns6)over.style.width=o3_width+'px';over.style.backgroundImage='url('+i+')';}}\r
+}\r
+\r
+/*\r
+ HANDLING FUNCTIONS\r
+*/\r
+// Displays layer\r
+function OLdisp(s){\r
+if(!OLallowmove){if(OLshadowPI)OLdispShadow();if(OLiframePI)OLdispIfs();OLplaceLayer();\r
+if(OLndt)OLshowObject(over);else OLshowid=setTimeout("OLshowObject(over)",1);\r
+OLallowmove=(o3_sticky||o3_nofollow)?0:1;}OLndt=0;if(s!="")self.status=s;\r
+}\r
+\r
+// Decides placement of layer.\r
+function OLplaceLayer(){\r
+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,\r
+o=OLfd(),nsb=(OLgek>=20010505&&!o3_frame.scrollbars.visible)?1:0;\r
+if(!OLkht&&o&&o.clientWidth)iWd=o.clientWidth;\r
+else if(o3_frame.innerWidth){SB=Math.ceil(1.4*(o3_frame.outerWidth-o3_frame.innerWidth));\r
+if(SB>20)SB=20;iWd=o3_frame.innerWidth;}\r
+pgLeft=(OLie4)?o.scrollLeft:o3_frame.pageXOffset;\r
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)SB=CX=5;else\r
+if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){SB+=((o3_shadowx>0)?o3_shadowx:0);\r
+LM=((o3_shadowx<0)?Math.abs(o3_shadowx):0);CX=Math.abs(o3_shadowx);}\r
+if(o3_ref!=""||o3_fixx> -1||o3_relx!=null||o3_midx!=null){\r
+if(o3_ref!=""){X=OLrefXY[0];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){\r
+if(o3_refp=='UR'||o3_refp=='LR')X-=5;}\r
+else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){\r
+if(o3_shadowx<0&&(o3_refp=='UL'||o3_refp=='LL'))X-=o3_shadowx;else\r
+if(o3_shadowx>0&&(o3_refp=='UR'||o3_refp=='LR'))X-=o3_shadowx;}\r
+}else{if(o3_midx!=null){\r
+X=parseInt(pgLeft+((iWd-pWd-SB-LM)/2)+o3_midx);\r
+}else{if(o3_relx!=null){\r
+if(o3_relx>=0)X=pgLeft+o3_relx+LM;else X=pgLeft+o3_relx+iWd-pWd-SB;\r
+}else{X=o3_fixx+LM;}}}\r
+}else{\r
+if(o3_hauto){\r
+if(o3_hpos==LEFT&&OLx-pgLeft<iWd/2&&OLx-pWd-o3_offsetx<pgLeft+LM)o3_hpos=RIGHT;else\r
+if(o3_hpos==RIGHT&&OLx-pgLeft>iWd/2&&OLx+pWd+o3_offsetx>pgLeft+iWd-SB)o3_hpos=LEFT;}\r
+X=(o3_hpos==CENTER)?parseInt(OLx-((pWd+CX)/2)+o3_offsetx):\r
+(o3_hpos==LEFT)?OLx-o3_offsetx-pWd:OLx+o3_offsetx;\r
+if(o3_snapx>1){\r
+snp=X % o3_snapx;\r
+if(o3_hpos==LEFT){X=X-(o3_snapx+snp);}else{X=X+(o3_snapx-snp);}}}\r
+if(!o3_nojustx&&X+pWd>pgLeft+iWd-SB)\r
+X=iWd+pgLeft-pWd-SB;if(!o3_nojustx&&X-LM<pgLeft)X=pgLeft+LM;\r
+pgTop=OLie4?o.scrollTop:o3_frame.pageYOffset;\r
+if(!OLkht&&!nsb&&o&&o.clientHeight)iHt=o.clientHeight;\r
+else if(o3_frame.innerHeight)iHt=o3_frame.innerHeight;\r
+if(OLbubblePI&&o3_bubble)pHt=OLbubbleHt;else pHt=OLns4?over.clip.height:over.offsetHeight;\r
+if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){TM=(o3_shadowy<0)?Math.abs(o3_shadowy):0;\r
+if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)BM=CY=5;else\r
+BM=(o3_shadowy>0)?o3_shadowy:0;CY=Math.abs(o3_shadowy);}\r
+if(o3_ref!=""||o3_fixy> -1||o3_rely!=null||o3_midy!=null){\r
+if(o3_ref!=""){Y=OLrefXY[1];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){\r
+if(o3_refp=='LL'||o3_refp=='LR')Y-=5;}else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){\r
+if(o3_shadowy<0&&(o3_refp=='UL'||o3_refp=='UR'))Y-=o3_shadowy;else\r
+if(o3_shadowy>0&&(o3_refp=='LL'||o3_refp=='LR'))Y-=o3_shadowy;}\r
+}else{if(o3_midy!=null){\r
+Y=parseInt(pgTop+((iHt-pHt-CY)/2)+o3_midy);\r
+}else{if(o3_rely!=null){\r
+if(o3_rely>=0)Y=pgTop+o3_rely+TM;else Y=pgTop+o3_rely+iHt-pHt-BM;}else{\r
+Y=o3_fixy+TM;}}}\r
+}else{\r
+if(o3_vauto){\r
+if(o3_vpos==ABOVE&&OLy-pgTop<iHt/2&&OLy-pHt-o3_offsety<pgTop)o3_vpos=BELOW;else\r
+if(o3_vpos==BELOW&&OLy-pgTop>iHt/2&&OLy+pHt+o3_offsety+((OLns4||OLkht)?17:0)>pgTop+iHt-BM)\r
+o3_vpos=ABOVE;}Y=(o3_vpos==VCENTER)?parseInt(OLy-((pHt+CY)/2)+o3_offsety):\r
+(o3_vpos==ABOVE)?OLy-(pHt+o3_offsety+BM):OLy+o3_offsety+TM;\r
+if(o3_snapy>1){\r
+snp=Y % o3_snapy;\r
+if(pHt>0&&o3_vpos==ABOVE){Y=Y-(o3_snapy+snp);}else{Y=Y+(o3_snapy-snp);}}}\r
+if(!o3_nojusty&&Y+pHt+BM>pgTop+iHt)Y=pgTop+iHt-pHt-BM;if(!o3_nojusty&&Y-TM<pgTop)Y=pgTop+TM;\r
+OLrepositionTo(over,X,Y);\r
+if(OLshadowPI)OLrepositionShadow(X,Y);if(OLiframePI)OLrepositionIfs(X,Y);\r
+if(OLns6&&o3_frame.innerHeight){iHt=o3_frame.innerHeight;OLrepositionTo(over,X,Y);}\r
+if(OLscrollPI)OLchkScroll(X-pgLeft,Y-pgTop);\r
+}\r
+\r
+// Chooses body or documentElement\r
+function OLfd(f){\r
+var fd=((f)?f:o3_frame).document,fdc=fd.compatMode,fdd=fd.documentElement;\r
+return (!OLop7&&fdc&&fdc!='BackCompat'&&fdd&&fdd.clientWidth)?fd.documentElement:fd.body;\r
+}\r
+\r
+// Gets location of REFerence object\r
+function OLgetRefXY(r,d){\r
+var o=OLgetRef(r,d),ob=o,rXY=[o3_refx,o3_refy],of;\r
+if(!o)return [null,null];\r
+if(OLns4){if(typeof o.length!='undefined'&&o.length>1){\r
+ob=o[0];rXY[0]+=o[0].x+o[1].pageX;rXY[1]+=o[0].y+o[1].pageY;\r
+}else{if((o.toString().indexOf('Image')!= -1)||(o.toString().indexOf('Anchor')!= -1)){\r
+rXY[0]+=o.x;rXY[1]+=o.y;}else{rXY[0]+=o.pageX;rXY[1]+=o.pageY;}}\r
+}else{rXY[0]+=OLpageLoc(o,'Left');rXY[1]+=OLpageLoc(o,'Top');}\r
+of=OLgetRefOffsets(ob);rXY[0]+=of[0];rXY[1]+=of[1];\r
+return rXY;\r
+}\r
+function OLgetRef(l,d){var r=OLgetRefById(l,d);return (r)?r:OLgetRefByName(l,d);}\r
+\r
+// Seeks REFerence by id\r
+function OLgetRefById(l,d){\r
+l=(l||'overDiv');d=(d||o3_frame.document);var j,r;\r
+if(OLie4&&d.all)return d.all[l];if(d.getElementById)return d.getElementById(l);\r
+if(d.layers&&d.layers.length>0){if(d.layers[l])return d.layers[l];\r
+for(j=0;j<d.layers.length;j++){r=OLgetRefById(l,d.layers[j].document);if(r)return r;}}\r
+return null;\r
+}\r
+\r
+// Seeks REFerence by name\r
+function OLgetRefByName(l,d){\r
+d=(d||o3_frame.document);var j,r,v=OLie4?d.all.tags('iframe'):\r
+OLns6?d.getElementsByTagName('iframe'):null;\r
+if(typeof d.images!='undefined'&&d.images[l])return d.images[l];\r
+if(typeof d.anchors!='undefined'&&d.anchors[l])return d.anchors[l];\r
+if(v)for(j=0;j<v.length;j++)if(v[j].name==l)return v[j];\r
+if(d.layers&&d.layers.length>0)for(j=0;j<d.layers.length;j++){\r
+r=OLgetRefByName(l,d.layers[j].document);\r
+if(r&&r.length>0)return r;else if(r)return [r,d.layers[j]];}\r
+return null;\r
+}\r
+\r
+// Gets layer vs REFerence offsets\r
+function OLgetRefOffsets(o){\r
+var c=o3_refc.toUpperCase(),p=o3_refp.toUpperCase(),W=0,H=0,pW=0,pH=0,of=[0,0];\r
+pW=(OLbubblePI&&o3_bubble)?o3_width:OLns4?over.clip.width:over.offsetWidth;\r
+pH=(OLbubblePI&&o3_bubble)?OLbubbleHt:OLns4?over.clip.height:over.offsetHeight;\r
+if((!OLop7)&&o.toString().indexOf('Image')!= -1){W=o.width;H=o.height;\r
+}else if((!OLop7)&&o.toString().indexOf('Anchor')!= -1){c=o3_refc='UL';}else{\r
+W=(OLns4)?o.clip.width:o.offsetWidth;H=(OLns4)?o.clip.height:o.offsetHeight;}\r
+if((OLns4||(OLns6&&OLgek))&&o.border){W+=2*parseInt(o.border);H+=2*parseInt(o.border);}\r
+if(c=='UL'){of=(p=='UR')?[-pW,0]:(p=='LL')?[0,-pH]:(p=='LR')?[-pW,-pH]:[0,0];\r
+}else if(c=='UR'){of=(p=='UR')?[W-pW,0]:(p=='LL')?[W,-pH]:(p=='LR')?[W-pW,-pH]:[W,0];\r
+}else if(c=='LL'){of=(p=='UR')?[-pW,H]:(p=='LL')?[0,H-pH]:(p=='LR')?[-pW,H-pH]:[0,H];\r
+}else if(c=='LR'){of=(p=='UR')?[W-pW,H]:(p=='LL')?[W,H-pH]:(p=='LR')?[W-pW,H-pH]:\r
+[W,H];}\r
+return of;\r
+}\r
+\r
+// Gets x or y location of object\r
+function OLpageLoc(o,t){\r
+var l=0;while(o.offsetParent&&o.offsetParent.tagName.toLowerCase()!='html'){\r
+l+=o['offset'+t];o=o.offsetParent;}l+=o['offset'+t];\r
+return l;\r
+} \r
+\r
+// Moves layer\r
+function OLmouseMove(e){\r
+var e=(e||event);\r
+OLcC=(OLovertwoPI&&over2&&over==over2?cClick2:cClick);\r
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);\r
+if((OLallowmove&&over)&&(o3_frame==self||over==OLgetRefById()\r
+||(OLovertwoPI&&over2==over&&over==OLgetRefById('overDiv2')))){\r
+OLplaceLayer();if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}\r
+if(OLhover&&over&&o3_frame==self&&OLcursorOff())if(o3_offdelay<1)OLcC();else\r
+{if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("OLcC()",o3_offdelay);}\r
+}\r
+\r
+// Capture mouse and chain other scripts.\r
+function OLmh(){\r
+var fN,f,j,k,s,mh=OLmouseMove,w=(OLns4&&window.onmousemove),re=/function[ ]*(\w*)\(/;\r
+OLdw=document;if(document.onmousemove||w){if(w)OLdw=window;f=OLdw.onmousemove.toString();\r
+fN=f.match(re);if(!fN||fN[1]=='anonymous'||fN[1]=='OLmouseMove'){OLchkMh=0;return;}\r
+if(fN[1])s=fN[1]+'(e)';else{j=f.indexOf('{');k=f.lastIndexOf('}')+1;s=f.substring(j,k);}\r
+s+=';OLmouseMove(e);';mh=new Function('e',s);}\r
+OLdw.onmousemove=mh;if(OLns4)OLdw.captureEvents(Event.MOUSEMOVE);\r
+}\r
+\r
+/*\r
+ PARSING\r
+*/\r
+function OLparseTokens(pf,ar){\r
+var i,v,md= -1,par=(pf!='ol_'),p=OLpar,q=OLparQuo,t=OLtoggle;OLudf=(par&&!ar.length?1:0);\r
+for(i=0;i< ar.length;i++){if(md<0){if(typeof ar[i]=='number'){OLudf=(par?1:0);i--;}\r
+else{switch(pf){case 'ol_':ol_text=ar[i];break;default:o3_text=ar[i];}}md=0;\r
+}else{\r
+if(ar[i]==INARRAY){OLudf=0;eval(pf+'text=ol_texts['+ar[++i]+']');continue;}\r
+if(ar[i]==CAPARRAY){eval(pf+'cap=ol_caps['+ar[++i]+']');continue;}\r
+if(ar[i]==CAPTION){q(ar[++i],pf+'cap');continue;}\r
+if(Math.abs(ar[i])==STICKY){t(ar[i],pf+'sticky');continue;}\r
+if(Math.abs(ar[i])==NOFOLLOW){t(ar[i],pf+'nofollow');continue;}\r
+if(ar[i]==BACKGROUND){q(ar[++i],pf+'background');continue;}\r
+if(Math.abs(ar[i])==NOCLOSE){t(ar[i],pf+'noclose');continue;}\r
+if(Math.abs(ar[i])==MOUSEOFF){t(ar[i],pf+'mouseoff');continue;}\r
+if(ar[i]==OFFDELAY){p(ar[++i],pf+'offdelay');continue;}\r
+if(ar[i]==RIGHT||ar[i]==LEFT||ar[i]==CENTER){p(ar[i],pf+'hpos');continue;}\r
+if(ar[i]==OFFSETX){p(ar[++i],pf+'offsetx');continue;}\r
+if(ar[i]==OFFSETY){p(ar[++i],pf+'offsety');continue;}\r
+if(ar[i]==FGCOLOR){q(ar[++i],pf+'fgcolor');continue;}\r
+if(ar[i]==BGCOLOR){q(ar[++i],pf+'bgcolor');continue;}\r
+if(ar[i]==CGCOLOR){q(ar[++i],pf+'cgcolor');continue;}\r
+if(ar[i]==TEXTCOLOR){q(ar[++i],pf+'textcolor');continue;}\r
+if(ar[i]==CAPCOLOR){q(ar[++i],pf+'capcolor');continue;}\r
+if(ar[i]==CLOSECOLOR){q(ar[++i],pf+'closecolor');continue;}\r
+if(ar[i]==WIDTH){p(ar[++i],pf+'width');continue;}\r
+if(Math.abs(ar[i])==WRAP){t(ar[i],pf+'wrap');continue;}\r
+if(ar[i]==WRAPMAX){p(ar[++i],pf+'wrapmax');continue;}\r
+if(ar[i]==HEIGHT){p(ar[++i],pf+'height');continue;}\r
+if(ar[i]==BORDER){p(ar[++i],pf+'border');continue;}\r
+if(ar[i]==BASE){p(ar[++i],pf+'base');continue;}\r
+if(ar[i]==STATUS){q(ar[++i],pf+'status');continue;}\r
+if(Math.abs(ar[i])==AUTOSTATUS){v=pf+'autostatus';\r
+eval(v+'=('+ar[i]+'<0)?('+v+'==2?2:0):('+v+'==1?0:1)');continue;}\r
+if(Math.abs(ar[i])==AUTOSTATUSCAP){v=pf+'autostatus';\r
+eval(v+'=('+ar[i]+'<0)?('+v+'==1?1:0):('+v+'==2?0:2)');continue;}\r
+if(ar[i]==CLOSETEXT){q(ar[++i],pf+'close');continue;}\r
+if(ar[i]==SNAPX){p(ar[++i],pf+'snapx');continue;}\r
+if(ar[i]==SNAPY){p(ar[++i],pf+'snapy');continue;}\r
+if(ar[i]==FIXX){p(ar[++i],pf+'fixx');continue;}\r
+if(ar[i]==FIXY){p(ar[++i],pf+'fixy');continue;}\r
+if(ar[i]==RELX){p(ar[++i],pf+'relx');continue;}\r
+if(ar[i]==RELY){p(ar[++i],pf+'rely');continue;}\r
+if(ar[i]==MIDX){p(ar[++i],pf+'midx');continue;}\r
+if(ar[i]==MIDY){p(ar[++i],pf+'midy');continue;}\r
+if(ar[i]==REF){q(ar[++i],pf+'ref');continue;}\r
+if(ar[i]==REFC){q(ar[++i],pf+'refc');continue;}\r
+if(ar[i]==REFP){q(ar[++i],pf+'refp');continue;}\r
+if(ar[i]==REFX){p(ar[++i],pf+'refx');continue;}\r
+if(ar[i]==REFY){p(ar[++i],pf+'refy');continue;}\r
+if(ar[i]==FGBACKGROUND){q(ar[++i],pf+'fgbackground');continue;}\r
+if(ar[i]==BGBACKGROUND){q(ar[++i],pf+'bgbackground');continue;}\r
+if(ar[i]==CGBACKGROUND){q(ar[++i],pf+'cgbackground');continue;}\r
+if(ar[i]==PADX){p(ar[++i],pf+'padxl');p(ar[++i],pf+'padxr');continue;}\r
+if(ar[i]==PADY){p(ar[++i],pf+'padyt');p(ar[++i],pf+'padyb');continue;}\r
+if(Math.abs(ar[i])==FULLHTML){t(ar[i],pf+'fullhtml');continue;}\r
+if(ar[i]==BELOW||ar[i]==ABOVE||ar[i]==VCENTER){p(ar[i],pf+'vpos');continue;}\r
+if(ar[i]==CAPICON){q(ar[++i],pf+'capicon');continue;}\r
+if(ar[i]==TEXTFONT){q(ar[++i],pf+'textfont');continue;}\r
+if(ar[i]==CAPTIONFONT){q(ar[++i],pf+'captionfont');continue;}\r
+if(ar[i]==CLOSEFONT){q(ar[++i],pf+'closefont');continue;}\r
+if(ar[i]==TEXTSIZE){q(ar[++i],pf+'textsize');continue;}\r
+if(ar[i]==CAPTIONSIZE){q(ar[++i],pf+'captionsize');continue;}\r
+if(ar[i]==CLOSESIZE){q(ar[++i],pf+'closesize');continue;}\r
+if(ar[i]==TIMEOUT){p(ar[++i],pf+'timeout');continue;}\r
+if(ar[i]==DELAY){p(ar[++i],pf+'delay');continue;}\r
+if(Math.abs(ar[i])==HAUTO){t(ar[i],pf+'hauto');continue;}\r
+if(Math.abs(ar[i])==VAUTO){t(ar[i],pf+'vauto');continue;}\r
+if(Math.abs(ar[i])==NOJUSTX){t(ar[i],pf+'nojustx');continue;}\r
+if(Math.abs(ar[i])==NOJUSTY){t(ar[i],pf+'nojusty');continue;}\r
+if(Math.abs(ar[i])==CLOSECLICK){t(ar[i],pf+'closeclick');continue;}\r
+if(ar[i]==CLOSETITLE){q(ar[++i],pf+'closetitle');continue;}\r
+if(ar[i]==FGCLASS){q(ar[++i],pf+'fgclass');continue;}\r
+if(ar[i]==BGCLASS){q(ar[++i],pf+'bgclass');continue;}\r
+if(ar[i]==CGCLASS){q(ar[++i],pf+'cgclass');continue;}\r
+if(ar[i]==TEXTPADDING){p(ar[++i],pf+'textpadding');continue;}\r
+if(ar[i]==TEXTFONTCLASS){q(ar[++i],pf+'textfontclass');continue;}\r
+if(ar[i]==CAPTIONPADDING){p(ar[++i],pf+'captionpadding');continue;}\r
+if(ar[i]==CAPTIONFONTCLASS){q(ar[++i],pf+'captionfontclass');continue;}\r
+if(ar[i]==CLOSEFONTCLASS){q(ar[++i],pf+'closefontclass');continue;}\r
+if(Math.abs(ar[i])==CAPBELOW){t(ar[i],pf+'capbelow');continue;}\r
+if(ar[i]==LABEL){q(ar[++i],pf+'label');continue;}\r
+if(Math.abs(ar[i])==DECODE){t(ar[i],pf+'decode');continue;}\r
+if(ar[i]==DONOTHING){continue;}\r
+i=OLparseCmdLine(pf,i,ar);}}\r
+if((OLfunctionPI)&&OLudf&&o3_function)o3_text=o3_function();\r
+if(pf=='o3_')OLfontSize();\r
+}\r
+function OLpar(a,v){eval(v+'='+a);}\r
+function OLparQuo(a,v){eval(v+"='"+OLescSglQt(a)+"'");}\r
+function OLescSglQt(s){return s.toString().replace(/'/g,"\\'");}\r
+function OLtoggle(a,v){eval(v+'=('+v+'==0&&'+a+'>=0)?1:0');}\r
+function OLhasDims(s){return /[%\-a-z]+$/.test(s);}\r
+function OLfontSize(){\r
+var i;if(OLhasDims(o3_textsize)){if(OLns4)o3_textsize="2";}else\r
+if(!OLns4){i=parseInt(o3_textsize);o3_textsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}\r
+if(OLhasDims(o3_captionsize)){if(OLns4)o3_captionsize="2";}else\r
+if(!OLns4){i=parseInt(o3_captionsize);o3_captionsize=(i>0&&i<8)?OLpct[i]:OLpct[0];}\r
+if(OLhasDims(o3_closesize)){if(OLns4)o3_closesize="2";}else\r
+if(!OLns4){i=parseInt(o3_closesize);o3_closesize=(i>0&&i<8)?OLpct[i]:OLpct[0];}\r
+if(OLprintPI)OLprintDims();\r
+}\r
+function OLdecode(){\r
+var re=/%[0-9A-Fa-f]{2,}/,t=o3_text,c=o3_cap,u=unescape,d=!OLns4&&(!OLgek||OLgek>=20020826)\r
+&&typeof decodeURIComponent?decodeURIComponent:u;if(typeof(window.TypeError)=='function'){\r
+if(re.test(t)){eval(new Array('try{','o3_text=d(t);','}catch(e){','o3_text=u(t);',\r
+'}').join('\n'))};if(c&&re.test(c)){eval(new Array('try{','o3_cap=d(c);','}catch(e){',\r
+'o3_cap=u(c);','}').join('\n'))}}else{if(re.test(t))o3_text=u(t);if(c&&re.test(c))o3_cap=u(c);}\r
+}\r
+\r
+/*\r
+ LAYER FUNCTIONS\r
+*/\r
+// Writes to layer\r
+function OLlayerWrite(t){\r
+t+="\n";\r
+if(OLns4){over.document.write(t);over.document.close();\r
+}else if(typeof over.innerHTML!='undefined'){if(OLieM)over.innerHTML='';over.innerHTML=t;\r
+}else{range=o3_frame.document.createRange();range.setStartAfter(over);\r
+domfrag=range.createContextualFragment(t);\r
+while(over.hasChildNodes()){over.removeChild(over.lastChild);}\r
+over.appendChild(domfrag);}\r
+if(OLprintPI)over.print=o3_print?t:null;\r
+}\r
+\r
+// Makes object visible\r
+function OLshowObject(o){\r
+OLshowid=0;o=(OLns4)?o:o.style;\r
+if(((OLfilterPI)&&!OLchkFilter(o))||!OLfilterPI)o.visibility="visible";\r
+if(OLshadowPI)OLshowShadow();if(OLiframePI)OLshowIfs();if(OLhidePI)OLhideUtil(1,1,0);\r
+}\r
+\r
+// Hides object\r
+function OLhideObject(o){\r
+if(OLshowid>0){clearTimeout(OLshowid);OLshowid=0;}\r
+if(OLtimerid>0)clearTimeout(OLtimerid);if(OLdelayid>0)clearTimeout(OLdelayid);\r
+OLtimerid=0;OLdelayid=0;self.status="";o3_label=ol_label;\r
+if(o3_frame!=self)o=OLgetRefById();\r
+if(o){if(o.onmouseover)o.onmouseover=null;\r
+if(OLscrollPI&&o==over)OLclearScroll();\r
+if(OLdraggablePI)OLclearDrag();\r
+if(OLfilterPI)OLcleanupFilter(o);if(OLshadowPI)OLhideShadow();\r
+var os=(OLns4)?o:o.style;os.visibility="hidden";\r
+if(OLhidePI&&o==over)OLhideUtil(0,0,1);if(OLiframePI)OLhideIfs(o);}\r
+}\r
+\r
+// Moves layer\r
+function OLrepositionTo(o,xL,yL){\r
+o=(OLns4)?o:o.style;\r
+o.left=(OLns4?xL:xL+'px');\r
+o.top=(OLns4?yL:yL+'px');\r
+}\r
+\r
+// Handle NOCLOSE-MOUSEOFF\r
+function OLoptMOUSEOFF(c){\r
+if(!c)o3_close="";\r
+over.onmouseover=function(){OLhover=1;if(OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}}\r
+}\r
+function OLcursorOff(){\r
+var o=(OLns4?over:over.style),pHt=OLns4?over.clip.height:over.offsetHeight,\r
+left=parseInt(o.left),top=parseInt(o.top),\r
+right=left+o3_width,bottom=top+((OLbubblePI&&o3_bubble)?OLbubbleHt:pHt);\r
+if(OLx<left||OLx>right||OLy<top||OLy>bottom)return true;\r
+return false;\r
+}\r
+\r
+/*\r
+ REGISTRATION\r
+*/\r
+function OLsetRunTimeVar(){\r
+if(OLrunTime.length)for(var k=0;k<OLrunTime.length;k++)OLrunTime[k]();\r
+}\r
+function OLparseCmdLine(pf,i,ar){\r
+if(OLcmdLine.length){for(var k=0;k<OLcmdLine.length;k++){\r
+var j=OLcmdLine[k](pf,i,ar);if(j>-1){i=j;break;}}}\r
+return i;\r
+}\r
+function OLregCmds(c){\r
+if(typeof c!='string')return;\r
+var pM=c.split(',');pMtr=pMtr.concat(pM);\r
+for(var i=0;i<pM.length;i++)eval(pM[i].toUpperCase()+'='+pmCnt++);\r
+}\r
+function OLregRunTimeFunc(f){\r
+if(typeof f=='object')OLrunTime=OLrunTime.concat(f);\r
+else OLrunTime[OLrunTime.length++]=f;\r
+}\r
+function OLregCmdLineFunc(f){\r
+if(typeof f=='object')OLcmdLine=OLcmdLine.concat(f);\r
+else OLcmdLine[OLcmdLine.length++]=f;\r
+}\r
+\r
+OLloaded=1;\r
diff --git a/httemplate/elements/overlibmws_crossframe.js b/httemplate/elements/overlibmws_crossframe.js
new file mode 100644 (file)
index 0000000..6b21c42
--- /dev/null
@@ -0,0 +1,44 @@
+/*\r
+ overlibmws_crossframe.js plug-in module - Copyright Foteos Macrides 2003-2006\r
+   For support of FRAME.\r
+   Initial: August 3, 2003 - Last Revised: November 2, 2004\r
+ See the Change History and Command Reference for overlibmws via:\r
+\r
+       http://www.macridesweb.com/oltest/\r
+\r
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html\r
+*/\r
+\r
+OLloaded=0;\r
+OLregCmds('frame');\r
+\r
+function OLparseCrossframe(pf,i,ar){\r
+var k=i,v;\r
+if(k<ar.length){\r
+if(ar[k]==FRAME){v=ar[++k];if(pf=='ol_')ol_frame=v;else OLoptFRAME(v);return k;}}\r
+return -1;\r
+}\r
+\r
+function OLgetFrameRef(thisFrame,ofrm){\r
+var i,v,retVal='';for(i=0;i<thisFrame.length;i++){if((((thisFrame[i].length>0)))&&(((OLns4))||\r
+((OLie4)&&(v=thisFrame[i].document.all.tags('iframe'))!=null&&v.length==0)||\r
+((OLns6)&&(v=thisFrame[i].document.getElementsByTagName('iframe'))!=null&&v.length==0))){\r
+retVal=OLgetFrameRef(thisFrame[i],ofrm);if(retVal=='')continue;}\r
+else if(thisFrame[i]!=ofrm)continue;retVal='['+i+']'+retVal;break;}\r
+return retVal;\r
+}\r
+\r
+function OLoptFRAME(frm){\r
+o3_frame=OLmkLyr('overDiv',frm)?frm:self;if(o3_frame!=self){\r
+var l,tFrm=OLgetFrameRef(top.frames,o3_frame),sFrm=OLgetFrameRef(top.frames,ol_frame);\r
+if(sFrm.length==tFrm.length) {l=tFrm.lastIndexOf('[');if(l){\r
+while(sFrm.substring(0,l)!=tFrm.substring(0,l))l=tFrm.lastIndexOf('[',l-1);\r
+tFrm=tFrm.substr(l);sFrm=sFrm.substr(l);}}var i,k,cnt=0,p='',str=tFrm;\r
+while((k=str.lastIndexOf('['))!= -1){cnt++;str=str.substring(0,k);}\r
+for(i=0;i<cnt;i++)p=p+'parent.';OLfnRef=p+'frames'+sFrm+'.';}\r
+}\r
+\r
+OLregCmdLineFunc(OLparseCrossframe);\r
+\r
+OLcrossframePI=1;\r
+OLloaded=1;\r
diff --git a/httemplate/elements/overlibmws_draggable.js b/httemplate/elements/overlibmws_draggable.js
new file mode 100644 (file)
index 0000000..0d25f84
--- /dev/null
@@ -0,0 +1,78 @@
+/*\r
+ overlibmws_draggable.js plug-in module - Copyright Foteos Macrides 2002=2005\r
+   For support of the DRAGGABLE feature.\r
+   Initial: August 24, 2002 - Last Revised: March 2, 2006\r
+ See the Change History and Command Reference for overlibmws via:\r
+\r
+       http://www.macridesweb.com/oltest/\r
+\r
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html\r
+*/\r
+\r
+OLloaded=0;\r
+OLregCmds('draggable');\r
+\r
+// DEFAULT CONFIGURATION\r
+if(OLud('draggable'))var ol_draggable=0;\r
+// END CONFIGURATION\r
+\r
+var o3_draggable=0,o3_dragging=0,OLmMv,OLcX,OLcY,OLcbX,OLcbY;\r
+function OLloadDraggable(){OLload('draggable');}\r
+function OLparseDraggable(pf,i,ar){\r
+var k=i;\r
+if(k<ar.length){if(Math.abs(ar[k])==DRAGGABLE){OLtoggle(ar[k],pf+'draggable');return k;}}\r
+return -1;\r
+}\r
+\r
+function OLcheckDrag(){\r
+if(o3_draggable){if(o3_sticky&&(o3_frame==self))initDrag();else o3_draggable=0;}\r
+}\r
+function initDrag(){\r
+OLmMv=OLdw.onmousemove;o3_dragging=0;\r
+if(OLns4){document.captureEvents(Event.MOUSEDOWN|Event.CLICK);\r
+document.onmousedown=OLgrabEl;;document.onclick=function(e){return routeEvent(e);}}\r
+else{over.onmousedown=OLgrabEl;OLsetDrgCur(1);}\r
+}\r
+function OLsetDrgCur(d){if(!OLns4)over.style.cursor=(d?'move':'auto');}\r
+\r
+function OLgrabEl(e){\r
+var e=(e||event);\r
+var cKy=(OLns4?e.modifiers&Event.ALT_MASK:(e.altKey||(OLop7&&e.ctrlKey)));o3_dragging=1;\r
+if(cKy){OLsetDrgCur(0);document.onmouseup=function(){OLsetDrgCur(1);o3_dragging=0;}\r
+return(OLns4?routeEvent(e):true);}\r
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);\r
+if(OLie4)over.onselectstart=function(){return false;}\r
+if(OLns4){OLcX=OLx;OLcY=OLy;document.captureEvents(Event.MOUSEUP)}else{\r
+OLcX=OLx-(OLns4?over.left:parseInt(over.style.left));\r
+OLcY=OLy-(OLns4?over.top:parseInt(over.style.top));\r
+if((OLshadowPI)&&bkdrop&&o3_shadow){OLcbX=OLx-(parseInt(bkdrop.style.left));\r
+OLcbY=OLy-(parseInt(bkdrop.style.top));}}OLdw.onmousemove=OLmoveEl;\r
+document.onmouseup=function(){\r
+if(OLie4)over.onselectstart=null;o3_dragging=0;OLdw.onmousemove=OLmMv;}\r
+return(OLns4?routeEvent(e):false);\r
+}\r
+\r
+function OLmoveEl(e){\r
+var e=(e||event);\r
+OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop);\r
+if(o3_dragging){if(OLns4){over.moveBy(OLx-OLcX,OLy-OLcY);\r
+if(OLshadowPI&&bkdrop&&o3_shadow)bkdrop.moveBy(OLx-OLcX,OLy-OLcY);}\r
+else{OLrepositionTo(over,OLx-OLcX,OLy-OLcY);\r
+if((OLiframePI)&&OLie55&&OLifsP1)OLrepositionTo(OLifsP1,OLx-OLcX,OLy-OLcY);\r
+if((OLshadowPI)&&bkdrop&&o3_shadow){OLrepositionTo(bkdrop,OLx-OLcbX,OLy-OLcbY);\r
+if((OLiframePI)&&OLie55&&OLifsSh)OLrepositionTo(OLifsSh,OLx-OLcbX,OLy-OLcbY);}}\r
+if(OLhidePI)OLhideUtil(0,1,1,0,0,0);}if(OLns4){OLcX=OLx;OLcY=OLy;}\r
+return false;\r
+}\r
+\r
+function OLclearDrag(){\r
+if(OLns4){document.releaseEvents(Event.MOUSEDOWN|Event.MOUSEUP|Event.CLICK);\r
+document.onmousedown=document.onclick=null;}else{over.onmousedown=null;OLsetDrgCur(0);}\r
+document.onmouseup=null;o3_dragging=0;\r
+}\r
+\r
+OLregRunTimeFunc(OLloadDraggable);\r
+OLregCmdLineFunc(OLparseDraggable);\r
+\r
+OLdraggablePI=1;\r
+OLloaded=1;\r
diff --git a/httemplate/elements/overlibmws_iframe.js b/httemplate/elements/overlibmws_iframe.js
new file mode 100644 (file)
index 0000000..e3032f2
--- /dev/null
@@ -0,0 +1,93 @@
+/*\r
+ overlibmws_iframe.js plug-in module - Copyright Foteos Macrides 2003-2005\r
+   Masks system controls to prevent obscuring of popops for IE v5.5 or higher.\r
+   Initial: October 19, 2003 - Last Revised: May 15, 2005\r
+ See the Change History and Command Reference for overlibmws via:\r
+\r
+       http://www.macridesweb.com/oltest/\r
+\r
+ Published under an open source license: http://www.macridesweb.com/oltest/license.html\r
+*/\r
+\r
+OLloaded=0;\r
+\r
+var OLifsP1=null,OLifsSh=null,OLifsP2=null;\r
+\r
+// IFRAME SHIM SUPPORT FUNCTIONS\r
+function OLinitIfs(){\r
+if(!OLie55)return;\r
+if((OLovertwoPI)&&over2&&over==over2){\r
+var o=o3_frame.document.all['overIframeOvertwo'];\r
+if(!o||OLifsP2!=o){OLifsP2=null;OLgetIfsP2Ref();}return;}\r
+o=o3_frame.document.all['overIframe'];\r
+if(!o||OLifsP1!=o){OLifsP1=null;OLgetIfsRef();}\r
+if((OLshadowPI)&&o3_shadow){o=o3_frame.document.all['overIframeShadow'];\r
+if(!o||OLifsSh!=o){OLifsSh=null;OLgetIfsShRef();}}\r
+}\r
+\r
+function OLsetIfsRef(o,i,z){\r
+o.id=i;o.src='javascript:false;';o.scrolling='no';var os=o.style;\r
+os.position='absolute';os.top=0;os.left=0;os.width=1;os.height=1;os.visibility='hidden';\r
+os.zIndex=over.style.zIndex-z;os.filter='Alpha(style=0,opacity=0)';\r
+}\r
+\r
+function OLgetIfsRef(){\r
+if(OLifsP1||!OLie55)return;\r
+OLifsP1=o3_frame.document.createElement('iframe');\r
+OLsetIfsRef(OLifsP1,'overIframe',2);\r
+o3_frame.document.body.appendChild(OLifsP1);\r
+}\r
+\r
+function OLgetIfsShRef(){\r
+if(OLifsSh||!OLie55)return;\r
+OLifsSh=o3_frame.document.createElement('iframe');\r
+OLsetIfsRef(OLifsSh,'overIframeShadow',3);\r
+o3_frame.document.body.appendChild(OLifsSh);\r
+}\r
+\r
+function OLgetIfsP2Ref(){\r
+if(OLifsP2||!OLie55)return;\r
+OLifsP2=o3_frame.document.createElement('iframe');\r
+OLsetIfsRef(OLifsP2,'overIframeOvertwo',1);\r
+o3_frame.document.body.appendChild(OLifsP2);\r
+}\r
+\r
+function OLsetDispIfs(o,w,h){\r
+var os=o.style;\r
+os.width=w+'px';os.height=h+'px';os.clip='rect(0px '+w+'px '+h+'px 0px)';\r
+o.filters.alpha.enabled=true;\r
+}\r
+\r
+function OLdispIfs(){\r
+if(!OLie55)return;\r
+var wd=over.offsetWidth,ht=over.offsetHeight;\r
+if(OLfilterPI&&o3_filter&&o3_filtershadow){wd+=5;ht+=5;}\r
+if((OLovertwoPI)&&over2&&over==over2){\r
+if(!OLifsP2)return;\r
+OLsetDispIfs(OLifsP2,wd,ht);return;}\r
+if(!OLifsP1)return;\r
+OLsetDispIfs(OLifsP1,wd,ht);\r
+if((!OLshadowPI)||!o3_shadow||!OLifsSh)return;\r
+OLsetDispIfs(OLifsSh,wd,ht);\r
+}\r
+\r
+function OLshowIfs(){\r
+if(OLifsP1){OLifsP1.style.visibility="visible";\r
+if((OLshadowPI)&&o3_shadow&&OLifsSh)OLifsSh.style.visibility="visible";}\r
+}\r
+\r
+function OLhideIfs(o){\r
+if(!OLie55||o!=over)return;\r
+if(OLifsP1)OLifsP1.style.visibility="hidden";\r
+if((OLshadowPI)&&o3_shadow&&OLifsSh)OLifsSh.style.visibility="hidden";\r
+}\r
+\r
+function OLrepositionIfs(X,Y){\r
+if(OLie55){if((OLovertwoPI)&&over2&&over==over2){\r
+if(OLifsP2)OLrepositionTo(OLifsP2,X,Y);}\r
+else{if(OLifsP1){OLrepositionTo(OLifsP1,X,Y);if((OLshadowPI)&&o3_shadow&&OLifsSh)\r
+OLrepositionTo(OLifsSh,X+o3_shadowx,Y+o3_shadowy);}}}\r
+}\r
+\r
+OLiframePI=1;\r
+OLloaded=1;\r
diff --git a/httemplate/elements/pager.html b/httemplate/elements/pager.html
new file mode 100644 (file)
index 0000000..a53300f
--- /dev/null
@@ -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 (file)
index 0000000..b1ae2aa
--- /dev/null
@@ -0,0 +1,31 @@
+<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>
+
+% if ( length($number) ) { 
+
+  <% $number %>
+
+%   if ( $opt{'callable'} && $curuser->option('vonage-username') ) { 
+
+      <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('https://secure.click2callu.com/tpcc/makecall?username=<% uri_escape($curuser->option('vonage-username')) %>&password=<% uri_escape($curuser->option('vonage-password')) %>&fromnumber=<% uri_escape($curuser->option('vonage-fromnumber')) %>&tonumber=1<% $snumber %>', 240, 64, 'call_popup'), CAPTION, 'Initiating call', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE, WIDTH, 240, HEIGHT, 64 ); return false;" TITLE="Call this number"><IMG SRC="<%$fsurl%>images/red_telephone_mimooh_01.png" BORDER=0 ALT="Call this number"></A>
+
+%   } 
+%
+% } else { 
+
+  &nbsp;
+
+% } 
+<%init>
+
+my( $number, %opt ) = @_;
+( my $snumber = $number ) =~ s/\D//g;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+( my $vonage_number = $curuser->option('vonage-fromnumber') ) =~ s/\D//g;
+$vonage_number =~ /^1/ or $vonage_number = "1$vonage_number";
+
+</%init>
diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html
new file mode 100644 (file)
index 0000000..1c96a54
--- /dev/null
@@ -0,0 +1,85 @@
+%
+%  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'} )
+%  } else {
+%    $url_or_message_link = "url=$url_or_message";
+%  }
+%
+
+
+<% include('/elements/xmlhttp.html',
+              'method' => 'POST',
+              'url'    => $action,
+              'subs'   => [ 'start_job' ],
+              'key'    => $key,
+           )
+%>
+<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">
+function OLiframeContent(src, width, height, name) {
+  return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'
+   +(name?' name="'+name+'" id="'+name+'"':'')+' scrolling="auto">'
+   +'<div>[iframe not supported]</div></iframe>');
+}
+
+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, 'progress_popup'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
+
+}
+
+</SCRIPT>
diff --git a/httemplate/elements/progress-popup.html b/httemplate/elements/progress-popup.html
new file mode 100644 (file)
index 0000000..cda704a
--- /dev/null
@@ -0,0 +1,105 @@
+%
+%  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];
+
+  //if ( status == 'progress' ) {
+  //IE workaround, no i have no idea why
+  if ( status.indexOf('progress') > -1 ) {
+    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 ) { 
+
+    document.getElementById("progress_message").innerHTML = "<% $message %>";
+    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;
+    }
+% } 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 (file)
index 0000000..537aac4
--- /dev/null
@@ -0,0 +1,29 @@
+/**\r
+ * QLIB 1.0 Box Control\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QBox(parent, name, res, x, y, width, height, body, visible, effects, opacity, zindex) {\r
+    this.init(parent, name);\r
+    if (this.res = res) {\r
+        this.x = x - 0;\r
+        this.y = y - 0;\r
+        this.width = width - 0;\r
+        this.height = (typeof(height) == "number") ? height : null;\r
+        this.body = body || "&nbsp;";\r
+        var j = QBox.arguments.length;\r
+        this.visible = (j > 8) ? visible : true;\r
+        this.effects = (j > 9) ? effects : (res.effects || 0);\r
+        this.opacity = (j > 10) ? opacity : (res.opacity != null ? res.opacity : 100);\r
+        this.zindex  = (j > 11) ? zindex : null;\r
+        this.create();\r
+    } else {\r
+        this.document.write("invalid resource");\r
+    }\r
+}\r
+QBox.prototype = new QBoxCtrl();\r
diff --git a/httemplate/elements/qlib/boxctrl.js b/httemplate/elements/qlib/boxctrl.js
new file mode 100644 (file)
index 0000000..417b204
--- /dev/null
@@ -0,0 +1,48 @@
+/**\r
+ * QLIB 1.0 Box Abstraction\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QBoxCtrl_content() {\r
+    with (this) {\r
+        if (res) {\r
+            this.cwidth = width - res.L - res.R - 8;\r
+            this.cheight = height && (height - res.T - res.B - 8);\r
+            var ec = '"><table border="0" cellspacing="0" cellpadding="0"><tr><td></td></tr></table></td>';\r
+            document.write('<table class="qbox" border="0" cellspacing="0" cellpadding="0" width="' +\r
+                (width - 8) + (height != null ? '" height="' + (height - 8) : '') + '"><tr><td width="' +\r
+                res.L + '" height="' + res.T + '"><img src="' + res.TL.src + '" border="0" width="' +\r
+                res.L + '" height="' + res.T + '"></td><td width="' + cwidth + '" height="' + res.T +\r
+                '" background="' + res.TC.src + ec + '<td width="' + res.R + '" height="' + res.T +\r
+                '"><img src="' + res.TR.src + '" border="0" width="' + res.R + '" height="' + res.T +\r
+                '"></td></tr><tr><td width="' + res.L + (cheight != null ? '" height="' + cheight : '') +\r
+                '" background="' + res.ML.src + ec + '<td width="' + cwidth + '" bgcolor="' + res.bgcolor +\r
+                (cheight != null ? '" height="' + cheight : '') + (res.bgtile ? '" background="' +\r
+                res.bgtile.src : '') + '" align="left" valign="top" class="body" unselectable="on">');\r
+                if (typeof(body) == "function") {\r
+                    this.body();\r
+                } else {\r
+                    document.write(body);\r
+                }\r
+            document.write('</td><td width="' + res.R + (cheight != null ? '" height="' + cheight : '') +\r
+                '" background="' + res.MR.src + ec + '</tr><tr><td width="' + res.L + '" height="' + res.B +\r
+                '"><img src="' + res.BL.src + '" border="0" width="' + res.L + '" height="' + res.B +\r
+                '"></td><td width="' + cwidth + '" height="' + res.B + '" background="' + res.BC.src + ec +\r
+                '<td width="' + res.R + '" height="' + res.B + '"><img src="' + res.BR.src +\r
+                '" border="0" width="' + res.R + '" height="' + res.B + '"></td></tr></table><br>');\r
+        }\r
+    }\r
+}\r
+\r
+function QBoxCtrl() {\r
+    this.res = false;\r
+    this.body = "&nbsp;";\r
+    this.cwidth = this.cheight = 0;\r
+    this.content = QBoxCtrl_content;\r
+}\r
+QBoxCtrl.prototype = new QWndCtrl();\r
diff --git a/httemplate/elements/qlib/boxres.js b/httemplate/elements/qlib/boxres.js
new file mode 100644 (file)
index 0000000..0878172
--- /dev/null
@@ -0,0 +1,42 @@
+/**\r
+ * QLIB 1.0 Box Resource\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QBoxRes(t, r, b, l, tc, tr, mr, br, bc, bl, ml, tl, bgcolor, bgtile, effects, opacity) { \r
+    var args = QBoxRes.arguments.length;\r
+    this.T = t;\r
+    this.R = r;\r
+    this.B = b;\r
+    this.L = l;\r
+    this.TC = new Image();\r
+    this.TC.src = tc;\r
+    this.TR = new Image(r, t);\r
+    this.TR.src = tr;\r
+    this.MR = new Image();\r
+    this.MR.src = mr;\r
+    this.BR = new Image(r, b);\r
+    this.BR.src = br;\r
+    this.BC = new Image();\r
+    this.BC.src = bc;\r
+    this.BL = new Image(l, b);\r
+    this.BL.src = bl;\r
+    this.ML = new Image();\r
+    this.ML.src = ml;\r
+    this.TL = new Image(l, t);\r
+    this.TL.src = tl;\r
+    this.bgcolor = bgcolor || "#FFFFFF";\r
+    if (bgtile) {\r
+        this.bgtile = new Image();\r
+        this.bgtile.src = bgtile;\r
+    } else {\r
+        this.bgtile = false;\r
+    }\r
+    this.effects = (args > 13) ? effects : null;\r
+    this.opacity = (args > 14) ? opacity : null;\r
+}\r
diff --git a/httemplate/elements/qlib/button.js b/httemplate/elements/qlib/button.js
new file mode 100644 (file)
index 0000000..05247d5
--- /dev/null
@@ -0,0 +1,74 @@
+/**\r
+ * QLIB 1.0 Button Control\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QButton_update() {\r
+    with (this) {\r
+        image.src = ((!enabled && res.imgD) || (value ? res.imgP : res.imgN)).src;\r
+    }\r
+}\r
+\r
+function QButton_doEvent() {\r
+    with (this) {\r
+        if (enabled) {\r
+            if (res.style == 1) {\r
+                this.value = value ? 0 : 1;\r
+                update();\r
+            }\r
+            onClick(value, tag);\r
+        }\r
+    }\r
+    return false;\r
+}\r
+\r
+function QButton_enable(state) {\r
+    this.enabled = state;\r
+    this.update();\r
+}\r
+\r
+function QButton_set(value) {\r
+    if (this.enabled) {\r
+        this.value = value ? 1 : 0;\r
+        this.update();\r
+    }\r
+    return true;\r
+}\r
+\r
+function QButton(parent, name, res, tooltip) {\r
+    this.init(parent, name);\r
+    if (res) {\r
+        this.res = res;\r
+        this.tip = tooltip || "";\r
+        this.enabled = true;\r
+        this.value = 0;\r
+        this.set = QButton_set;\r
+        this.enable = QButton_enable;\r
+        this.update = QButton_update;\r
+        this.doEvent = QButton_doEvent;\r
+        this.onClick = QControl.event;\r
+        with (this) {\r
+            document.write('<a href="#" hidefocus="true" unselectable="on"' +\r
+                (tip ? ' title="' + tip + '"' : '') + ' onClick="return ' + name +\r
+                '.doEvent()" onMouseOver="' + (res.style == 2 ? name + '.set(1);' : '') +\r
+                'window.top.status=' + name + '.tip;return true" onMouseOut="' +\r
+                (!res.style || (res.style == 2) ? name + '.set();' : '') + 'window.top.status=\'\'"' +\r
+                (!res.style ? ' onMouseDown="return ' + name + '.set(1)" onMouseUp="return ' + name + '.set()"' : '') +\r
+                '><img class="qbutton" name="' + id + '" src="' + res.imgN.src + '" border="0" width="' +\r
+                res.width + '" height="' + res.height + '"></a>');\r
+            this.image = document.images[id] || new Image(1, 1);\r
+        }\r
+    } else {\r
+        this.document.write("invalid resource");\r
+    }\r
+}\r
+QButton.prototype = new QControl();\r
+QButton.NORMAL    = 0;\r
+QButton.CHECKBOX  = 1;\r
+QButton.WEB       = 2;\r
+QButton.SIGNAL    = 3;\r
diff --git a/httemplate/elements/qlib/buttonres.js b/httemplate/elements/qlib/buttonres.js
new file mode 100644 (file)
index 0000000..97f6dfc
--- /dev/null
@@ -0,0 +1,23 @@
+/**\r
+ * QLIB 1.0 Button Resource\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QButtonRes(style, width, height, normal, pressed, disabled) {\r
+    this.style = style;\r
+    this.width = width;\r
+    this.height = height;\r
+    this.imgN = new Image(width, height);\r
+    this.imgN.src = normal;\r
+    this.imgP = new Image(width, height);\r
+    this.imgP.src = pressed;\r
+    if (disabled) {\r
+        this.imgD = new Image(width, height);\r
+        this.imgD.src = disabled;\r
+    }\r
+}\r
diff --git a/httemplate/elements/qlib/control.js b/httemplate/elements/qlib/control.js
new file mode 100644 (file)
index 0000000..f50206e
--- /dev/null
@@ -0,0 +1,51 @@
+/**\r
+ * QLIB 1.0 Base Abstract Control\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QControl_init(parent, name) {\r
+    this.parent = parent || self;\r
+    this.window = (parent && parent.window) || self;\r
+    this.document = (parent && parent.document) || self.document;\r
+    this.name = (parent && parent.name) ? (parent.name + "." + name) : ("self." + name);\r
+    this.id = "Q";\r
+    var h = this.hash(this.name);\r
+    for (var j=0; j<8; j++) {\r
+        this.id += QControl.HEXTABLE.charAt(h & 15);\r
+        h >>>= 4;\r
+    }\r
+}\r
+\r
+function QControl_hash(str) {\r
+    var h = 0;\r
+    if (str) {\r
+        for (var j=str.length-1; j>=0; j--) {\r
+            h ^= QControl.ANTABLE.indexOf(str.charAt(j)) + 1;\r
+            for (var i=0; i<3; i++) {\r
+                var m = (h = h<<7 | h>>>25) & 150994944;\r
+                h ^= m ? (m == 150994944 ? 1 : 0) : 1;\r
+            }\r
+        }\r
+    }\r
+    return h;\r
+}\r
+\r
+function QControl_nop() {\r
+}\r
+\r
+function QControl() {\r
+    this.init = QControl_init;\r
+    this.hash = QControl_hash;\r
+    this.window = self;\r
+    this.document = self.document;\r
+    this.tag = null;\r
+}\r
+QControl.ANTABLE  = "w5Q2KkFts3deLIPg8Nynu_JAUBZ9YxmH1XW47oDpa6lcjMRfi0CrhbGSOTvqzEV";\r
+QControl.HEXTABLE = "0123456789ABCDEF";\r
+QControl.nop = QControl_nop;\r
+QControl.event = QControl_nop;\r
diff --git a/httemplate/elements/qlib/counter.js b/httemplate/elements/qlib/counter.js
new file mode 100644 (file)
index 0000000..72aeddb
--- /dev/null
@@ -0,0 +1,81 @@
+/**\r
+ * QLIB 1.0 Animated Digital Counter\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QCounter_update() {\r
+    with (this) {\r
+        var v = Math.max(value, 0);\r
+        var mod;\r
+        for (var j=0; j<size; j++) {\r
+            mod = Math.floor(v % 10);\r
+            images[j].src = (v >= 1) || (!j) ? res.list[mod].src : res.list[10].src;\r
+            v /= 10;\r
+        }\r
+    }\r
+}\r
+\r
+function QCounter_count(value, step) {\r
+    this._cntt = false;\r
+    this.value += step; \r
+    if ((step * (this.value - value)) >= 0) {\r
+        this.value = value - 0;  // convert to number\r
+    } else {\r
+        this._cntt = setTimeout(this.name + ".count(" + value + "," + step + ")", 50);\r
+    }\r
+    this.update();\r
+}\r
+         \r
+function QCounter_set(value) {\r
+    this.setval = value;\r
+    if (value != this.value) {\r
+        if (this._cntt) {\r
+            clearTimeout(this._cntt);\r
+            this._cntt = false;\r
+        }\r
+        var dv = value - this.value;\r
+        if (this.effect == 2) {\r
+            dv = dv / Math.min(10, Math.abs(dv));\r
+        } else if (this.effect == 3) {\r
+            dv = dv / Math.abs(dv);\r
+        }\r
+        this.count(value, dv);\r
+    }\r
+}\r
+\r
+function QCounter(parent, name, res, size, effect) {\r
+    this.init(parent, name);\r
+    if (res) {\r
+        this.res = res;\r
+        this.setval = this.value = 0;\r
+        this.size = size || 4;\r
+        this.effect = effect || 2;\r
+        this._cntt = false;\r
+        this.images = new Array(this.size);\r
+        this.set = QCounter_set;\r
+        this.update = QCounter_update;\r
+        this.count = QCounter_count;\r
+        with (this) {\r
+            document.write('<table class="qcounter" width="' + (res.width * size) + '" height="' + res.height +\r
+                '" border="0" cellspacing="0" cellpadding="0" unselectable="on"><tr>');\r
+            for (var j=(size - 1); j>=0; j--) {\r
+                document.write('<td width="' + res.width + '" height="' + res.height +\r
+                    '" unselectable="on"><img name="' + id + j + '" src="' + (j ? res.list[10].src : res.list[0].src) +\r
+                    '" border="0" width="' + res.width + '" height="' + res.height + '"></td>');\r
+                images[j] = document.images[id + j] || new Image(1, 1);\r
+            }\r
+            document.write('</tr></table>');\r
+        }\r
+    } else {\r
+        this.document.write("invalid resource");\r
+    }\r
+}\r
+QCounter.prototype = new QControl();\r
+QCounter.INSTANT   = 1;\r
+QCounter.FAST      = 2;\r
+QCounter.SLOW      = 3;\r
diff --git a/httemplate/elements/qlib/imagelist.js b/httemplate/elements/qlib/imagelist.js
new file mode 100644 (file)
index 0000000..9f12de0
--- /dev/null
@@ -0,0 +1,25 @@
+/**\r
+ * QLIB 1.0 ImageList Resource\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QImageList(width, height) {\r
+    var len = QImageList.arguments.length - 2;\r
+    if (len > 0) {\r
+        this.list = new Array(len);\r
+        this.length = len;\r
+        this.width = width;\r
+        this.height = height;\r
+        var im;\r
+        for (var j=0; j<len; j++) {\r
+            im = new Image(width, height);\r
+            im.src = QImageList.arguments[j + 2];\r
+            this.list[j] = im;\r
+        }\r
+    }\r
+}
\ No newline at end of file
diff --git a/httemplate/elements/qlib/label.js b/httemplate/elements/qlib/label.js
new file mode 100644 (file)
index 0000000..2d8b1e7
--- /dev/null
@@ -0,0 +1,72 @@
+/**\r
+ * QLIB 1.0 Text Label\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QLabel_set_ie(value) {\r
+    this.label.innerText = (this.value = value) || "\xA0";\r
+}\r
+\r
+function QLabel_set_dom2(value) {\r
+    with (this.label) {\r
+        replaceChild(this.document.createTextNode((this.value = value) || "\xA0"), firstChild);\r
+    }\r
+}\r
+\r
+function QLabel_set_ns4(value) {\r
+    this.value = value || "";\r
+    with (this) {\r
+        document.open();\r
+        document.write('<div class="qlabel">' + (clickable ? '<a href="#" title="' + tooltip + '" onClick="return ' +\r
+            name + '.doEvent()" onMouseOut="window.top.status=\'\'" onMouseOver="window.top.status=' + name +\r
+            '.tooltip;return true">' + value + '</a>' : value) + '</div>');\r
+        document.close();\r
+    }\r
+}\r
+\r
+function QLabel_doEvent() {\r
+    this.onClick(this.value, this.tag);\r
+    return false;\r
+}\r
+\r
+function QLabel(parent, name, value, clickable, tooltip) {\r
+    this.init(parent, name);\r
+    this.value = value || "";\r
+    this.clickable = clickable || false;\r
+    this.tooltip = tooltip || "";\r
+    this.doEvent = QLabel_doEvent;\r
+    this.onClick = QControl.event;\r
+    with (this) {\r
+        if (document.getElementById || document.all) {\r
+            document.write(clickable ? '<div class="qlabel" unselectable="on"><a id="' + id + '" href="#" title="' +\r
+                tooltip + '" onClick="return ' + name + '.doEvent()" onMouseOver="window.top.status=' + name +\r
+                '.tooltip;return true" onMouseOut="window.top.status=\'\'" hidefocus="true" unselectable="on">' +\r
+                (value || '&nbsp;') + '</a></div>' : '<div id="' + id + '" class="qlabel" unselectable="on">' +\r
+                (value || '&nbsp;') + '</div>');\r
+            this.label = document.getElementById ? document.getElementById(id) :\r
+                (document.all.item ? document.all.item(id) : document.all[id]);\r
+            this.set = (label && (label.innerText ? QLabel_set_ie :\r
+                (label.replaceChild && QLabel_set_dom2))) || QControl.nop;\r
+        } else if (document.layers) {\r
+            var suffix = "";\r
+            for (var j=value.length; j<QLabel.TEXTQUOTA; j++) suffix += " &nbsp;";\r
+            document.write('<div><ilayer id="i' + id + '"><layer id="' + id + '"><div class="qlabel">' +\r
+                (clickable ? '<a href="#" title="' + tooltip + '" onClick="return ' + name +\r
+                '.doEvent()" onMouseOver="window.top.status=' + name +\r
+                '.tooltip;return true" onMouseOut="window.top.status=\'\'">' + value + suffix + '</a>' :\r
+                value + suffix) + '</div></layer></ilayer></div>');\r
+            this.label = (this.label = document.layers["i" + id]) && label.document.layers[id];\r
+            this.document = label && label.document;\r
+            this.set = (label && document) ? QLabel_set_ns4 : QControl.nop;\r
+        } else {\r
+            document.write("Object is not supported");\r
+        }\r
+    }\r
+}\r
+QLabel.prototype = new QControl();\r
+QLabel.TEXTQUOTA = 50;\r
diff --git a/httemplate/elements/qlib/messagebox.js b/httemplate/elements/qlib/messagebox.js
new file mode 100644 (file)
index 0000000..2e45839
--- /dev/null
@@ -0,0 +1,57 @@
+/**\r
+ * QLIB 1.0 Message Box Control\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QMessageBox_alert(msg) {\r
+    if (typeof(msg) == "string") {\r
+        this.label.set(this.value = msg);\r
+    }\r
+    this.center();\r
+    this.focus();\r
+    this.show(true);\r
+}\r
+\r
+function QMessageBox_close() {\r
+    with (this.parent) {\r
+        if (!onClose(tag)) show(false);\r
+    }\r
+}\r
+\r
+function QMessageBox_body() {\r
+    with (this) {\r
+        document.write('<table border="0" width="' + cwidth + '"><tr><td align="left" valign="top" unselectable="on">');\r
+        this.label = new QLabel(this, "label", value);\r
+        document.write('</td></tr><tr><td height="' + (bres.height + 14) + '" align="center" valign="bottom" unselectable="on">');\r
+        this.button = new QButton(this, "button", bres, "Close");\r
+        document.write('</td></tr></table>');\r
+        button.onClick = QMessageBox_close;\r
+    }\r
+}\r
+\r
+function QMessageBox(parent, name, box, btn, msg, effects, opacity) {\r
+    this.init(parent, name);\r
+    if ((this.res = box) && (this.bres = btn)) {\r
+        this.value = typeof(msg) == "string" ? msg : "";\r
+        this.width = Math.max(200, Math.floor(Math.sqrt(555 * this.value.length)));\r
+        this.height = null;\r
+        this.x = this.y = 0;\r
+        this.visible = false;\r
+        this.zindex = null;\r
+        this.body = QMessageBox_body;\r
+        var j = QMessageBox.arguments.length;\r
+        this.effects = j > 5 ? effects : (box.effects != null ? box.effects : 0);\r
+        this.opacity = j > 6 ? opacity : (box.opacity != null ? box.opacity : 100);\r
+        this.create();\r
+        this.alert = QMessageBox_alert;\r
+        this.onClose = QControl.event;\r
+    } else {\r
+        this.document.write("invalid resource");\r
+    }\r
+}\r
+QMessageBox.prototype = new QBoxCtrl();\r
diff --git a/httemplate/elements/qlib/progress.js b/httemplate/elements/qlib/progress.js
new file mode 100644 (file)
index 0000000..2de077e
--- /dev/null
@@ -0,0 +1,73 @@
+/**\r
+ * QLIB 1.0 Progress Control\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QProgress_update() {\r
+    with (this) {\r
+        var i = low;\r
+        for (var j=0; j<size; j++) {\r
+            images[j].src = i < value ? imgsrc1 : imgsrc0;\r
+            i += delta;\r
+        }\r
+    }\r
+}\r
+\r
+function QProgress_set(value) {\r
+    this.value = value - 0;\r
+    this.update();\r
+}\r
+\r
+function QProgress_setBounds(low, high) {\r
+    this.low = Math.min(low, high);\r
+    this.high = Math.max(low, high);\r
+    this.delta = (this.high - this.low) / this.size;\r
+    this.update();\r
+}\r
\r
+function QProgress(parent, name, res, size, style) {\r
+    this.init(parent, name);\r
+    if (res) {\r
+        this.res = res;\r
+        this.value = 0;\r
+        this.low = 0;\r
+        this.high = 100;\r
+        this.size = size || 10;\r
+        this.delta = 100 / this.size;\r
+        this.style = style || 0;\r
+        this.images = new Array(this.size);\r
+        this.imgsrc0 = res.list[0] && res.list[0].src;\r
+        this.imgsrc1 = res.list[1] && res.list[1].src;\r
+        this.set = QProgress_set;\r
+        this.update = QProgress_update;\r
+        this.setBounds = QProgress_setBounds;\r
+        with (this) {\r
+            var hor = this.style < 2;\r
+            var rev = this.style % 2;\r
+            document.write('<table class="qprogress" border="0"  cellspacing="0" cellpadding="0" unselectable="on" ' +\r
+                (hor ? 'width="' + (size * res.width) + '" height="' + res.height + '"><tr>' : 'width="' + res.width +\r
+                '" height="' + (size * res.height) + '">'));\r
+            for (var j=0; j<size; j++) {\r
+                document.write((hor ? '' : '<tr>') + '<td width="' + res.width + '" height="' + res.height +\r
+                    '" unselectable="on"><img name="' + id + (rev ? size - j - 1 : j) + '" src="' + res.list[0].src +\r
+                    '" border="0" width="' + res.width + '" height="' + res.height + '"></td>' + (hor ? '' : '</tr>'));\r
+            }\r
+            document.write((hor ? '</tr>' : '') + '</table>');\r
+            for (var j=0; j<size; j++) {\r
+                images[j] = document.images[id + j] || new Image(1, 1);\r
+            }\r
+        }\r
+    } else {\r
+        this.document.write("invalid resource");\r
+    }\r
+}\r
+QProgress.prototype = new QControl();\r
+QProgress.NORMAL    = 0;\r
+QProgress.REVERSE   = 1;\r
+QProgress.FALL      = 2;\r
+QProgress.RISE      = 3;\r
diff --git a/httemplate/elements/qlib/sound.js b/httemplate/elements/qlib/sound.js
new file mode 100644 (file)
index 0000000..3d1aaf6
--- /dev/null
@@ -0,0 +1,47 @@
+/**\r
+ * QLIB 1.0 Preloaded Sound\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QSound_play(loop) {\r
+    this._out.loop = loop || 0;\r
+    this._out.src = this._buf.src;\r
+}\r
+\r
+function QSound_stop() {\r
+    this._out.loop = 0;\r
+    this._out.src = "";\r
+}\r
+\r
+function QSound_setVolume(volume) {\r
+    this._out.volume = this.volume = volume;\r
+}\r
\r
+function QSound(parent, name, src, volume) {\r
+    this.init(parent, name);\r
+    this.volume = volume || 0;\r
+    this.play = this.stop = this.setVolume = QControl.nop;\r
+    with (this) {\r
+        document.write('<bgsound id="' + id + '" src="" volume="' + volume + '">');\r
+        if (document.all && document.all.item) {\r
+            this._out = document.all.item(id);\r
+            if (_out && (typeof _out.src != "undefined") && (_out.volume === volume)) {\r
+                document.write('<bgsound id="b' + id + '" src="' + src + '" volume="-10000">');\r
+                this._buf = document.all.item("b" + id);\r
+                if (_buf) {\r
+                    this.play = QSound_play;\r
+                    this.stop = QSound_stop;\r
+                    this.setVolume = QSound_setVolume;\r
+\r
+                    _out.onreadystatechange = new Function("alert(0)");\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
+QSound.prototype = new QControl();\r
diff --git a/httemplate/elements/qlib/sprite.js b/httemplate/elements/qlib/sprite.js
new file mode 100644 (file)
index 0000000..72a68fb
--- /dev/null
@@ -0,0 +1,125 @@
+/**\r
+ * QLIB 1.0 Sprite Object\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QSprite_load(src) {\r
+    if (src) {\r
+        this.face = new Image(this.cwidth, this.cheight);\r
+        this.face.src = src;\r
+        this.valid = false;\r
+    }\r
+}\r
\r
+function QSprite_show(show) {\r
+    if (show && !this.valid && this.face.complete) {\r
+        this._img.src = this.face.src;\r
+        this.valid = true;\r
+    }\r
+    this._show(show);\r
+}\r
+\r
+function QSprite_moveTo(x, y) {\r
+    this.stop();\r
+    this._move(x, y);\r
+}\r
+\r
+function QSprite_slideTo(x, y) {\r
+    this.stop();\r
+    if (this.visible) {\r
+        this.doSlide(++this._spro, x, y);\r
+    } else {\r
+        this.moveTo(x, y);\r
+    }\r
+}\r
+\r
+function QSprite_shake() {\r
+    this.stop();\r
+    if (this.visible) {\r
+        this.doShake(++this._spro, 0, this.x, this.y);\r
+    }\r
+}\r
+\r
+function QSprite_stop() {\r
+    this._spro++;\r
+    if (this._sprt) {\r
+        clearTimeout(this._sprt);\r
+        this._sprt = false;\r
+    }\r
+}\r
+\r
+function QSprite_doSlide(id, x, y) {\r
+    if (this._spro == id) {\r
+        this._sprt = false;\r
+        var dx = Math.round(x - this.x);\r
+        var dy = Math.round(y - this.y);\r
+        if (dx || dy) {\r
+            if (dx) dx = dx > 0 ? Math.ceil(dx/4) : Math.floor(dx/4);\r
+            if (dy) dy = dy > 0 ? Math.ceil(dy/4) : Math.floor(dy/4);\r
+            this._move(this.x + dx, this.y + dy);\r
+            this._sprt = setTimeout(this.name + ".doSlide(" + id + "," + x + "," + y + ")", 30);\r
+        } else {\r
+            this._move(x, y);\r
+        }\r
+    }\r
+}\r
+\r
+function QSprite_doShake(id, phase, x, y) {\r
+    if (this._spro == id) {\r
+        this._sprt = false;\r
+        if (phase < 20) {\r
+            var m = 3 * Math.sin(.16 * phase);\r
+            this._move(x + m * Math.sin(phase), y + m * Math.cos(phase));\r
+            this._sprt = setTimeout(this.name + ".doShake(" + id + "," + (++phase) + "," + x + "," + y + ")", 20);\r
+        } else {\r
+            this._move(x, y);\r
+        }\r
+    }\r
+}\r
+\r
+function QSprite_doClick() {\r
+    if (!this._sprt) {\r
+        this.onClick(this.tag);\r
+    }\r
+    return false;\r
+}\r
+\r
+function QSprite(parent, name, x, y, width, height, src, visible, effects, opacity, zindex) {\r
+    this.init(parent, name);\r
+    this.x = x - 0;\r
+    this.y = y - 0;\r
+    this.width = (this.cwidth = width - 0) + 8;\r
+    this.height = (this.cheight = height - 0) + 8;\r
+    var j = QSprite.arguments.length;\r
+    this.visible = (j > 7) ? visible : true;\r
+    this.effects = (j > 8) ? effects : 0;\r
+    this.opacity = (j > 9) ? opacity : 100;\r
+    this.zindex  = (j > 10) ? zindex : null;\r
+    this.valid = !!src;\r
+    this.content = '<a href="#" title="" onclick="return false" onmousedown="return ' + this.name +\r
+        '.doClick()" onmouseover="window.top.status=\'\';return true" hidefocus="true" unselectable="on"><img name="' +\r
+        this.id + '" src="' + (src || '') + '" border="0" width="' + this.cwidth + '" height="' + this.cheight +\r
+        '" alt="" unselectable="on"></a>';\r
+    this.doClick = QSprite_doClick;\r
+    this.doSlide = QSprite_doSlide;\r
+    this.doShake = QSprite_doShake;\r
+    this.onClick = QControl.event;\r
+    this.create();\r
+    this.face = this._img = this.document.images[this.id] || new Image(1, 1);\r
+    this._spro = 0;\r
+    this._sprt = false;\r
+    this._show = this.show;\r
+    this._move = this.moveTo;\r
+    this.load = QSprite_load;\r
+    this.show = QSprite_show;\r
+    this.moveTo = QSprite_moveTo;\r
+    this.slideTo = QSprite_slideTo;\r
+    this.shake = QSprite_shake;\r
+    this.stop = QSprite_stop;\r
+}\r
+QSprite.prototype = new QWndCtrl();\r
diff --git a/httemplate/elements/qlib/window.js b/httemplate/elements/qlib/window.js
new file mode 100644 (file)
index 0000000..6056fda
--- /dev/null
@@ -0,0 +1,25 @@
+/**\r
+ * QLIB 1.0 Window Control\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QWindow(parent, name, x, y, width, height, content, visible, effects, opacity, zindex) {\r
+    this.init(parent, name);\r
+    this.x = x - 0;\r
+    this.y = y - 0;\r
+    this.width = width - 0;\r
+    this.height = (typeof(height) == "number") ? height : null;\r
+    this.content = content;\r
+    var j = QWindow.arguments.length;\r
+    this.visible = (j > 7) ? visible : true;\r
+    this.effects = (j > 8) ? effects : 0;\r
+    this.opacity = (j > 9) ? opacity : 100;\r
+    this.zindex  = (j > 10) ? zindex : null;\r
+    this.create();\r
+}\r
+QWindow.prototype = new QWndCtrl();\r
diff --git a/httemplate/elements/qlib/wndctrl.js b/httemplate/elements/qlib/wndctrl.js
new file mode 100644 (file)
index 0000000..b3bde4e
--- /dev/null
@@ -0,0 +1,322 @@
+/**\r
+ * QLIB 1.0 Window Abstraction\r
+ * Copyright (C) 2002 2003, Quazzle.com Serge Dolgov\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ * http://qlib.quazzle.com\r
+ */\r
+\r
+function QWndCtrl_center_ie4() {\r
+    var b = this.document.body;\r
+    this.moveTo(b.scrollLeft + Math.max(0, Math.floor((b.clientWidth -\r
+        this.width) / 2)), b.scrollTop + 100);\r
+}\r
+\r
+function QWndCtrl_center_moz() {\r
+    this.moveTo(self.pageXOffset + Math.max(0, Math.floor((self.innerWidth -\r
+        this.width) / 2)), self.pageYOffset + 100);\r
+}\r
+\r
+function QWndCtrl_setEffects_ie4(fx) {\r
+    this.effects = fx;\r
+    with (this.wnd) {\r
+        filters[0].enabled = (fx & 256) != 0;\r
+        filters[1].enabled = (fx & 512) != 0;\r
+        filters[2].enabled = (fx & 1024) != 0;\r
+        filters[4].enabled = (fx & 2048) != 0;\r
+    }\r
+}\r
+\r
+function QWndCtrl_setEffects_moz(fx) {\r
+    this.effects = fx;\r
+}\r
+\r
+function QWndCtrl_setOpacity_ie4(op) {\r
+    this.opacity = Math.max(0, Math.min(100, Math.floor(op - 0)));\r
+    this.wnd.filters[3].opacity = this.opacity;\r
+    this.wnd.filters[3].enabled = (this.opacity < 100);\r
+}\r
+\r
+function QWndCtrl_setOpacity_moz(op) {\r
+    this.opacity = Math.max(0, Math.min(100, Math.floor(op - 0)));\r
+    this.wnd.style.MozOpacity = this.opacity + "%";\r
+}\r
+\r
+function QWndCtrl_setSize_css(w, h) {\r
+    this.wnd.style.width = (this.width = Math.floor(w - 0)) + "px";\r
+    this.wnd.style.height = typeof(h) == "number" ? (this.height = Math.floor(h)) + "px" : "auto";\r
+}\r
+\r
+function QWndCtrl_setSize_ns4(w, h) {\r
+    this.wnd.clip.width = this.width = Math.floor(w - 0);\r
+    if (typeof(h) == "number") {\r
+        this.wnd.clip.height = this.height = Math.floor(h);\r
+    }\r
+}\r
+\r
+function QWndCtrl_focus() {\r
+    this.setZIndex(QWndCtrl.TOPZINDEX++);\r
+}\r
+\r
+function QWndCtrl_setZIndex_css(z) {\r
+    this.wnd.style.zIndex = this.zindex = z || 0;\r
+}\r
+\r
+function QWndCtrl_setZIndex_ns4(z) {\r
+    this.wnd.zIndex = this.zindex = z || 0;\r
+}\r
+\r
+function QWndCtrl_moveTo_css(x, y) {\r
+    this.wnd.style.left = (this.x = Math.floor(x - 0)) + "px";\r
+    this.wnd.style.top = (this.y = Math.floor(y - 0)) + "px";\r
+}\r
+\r
+function QWndCtrl_moveTo_ns4(x, y) {\r
+    this.wnd.moveTo(this.x = Math.floor(x - 0), this.y = Math.floor(y - 0));\r
+}\r
+\r
+function QWndCtrl_fxhandler() {\r
+    this.fxhandler = QControl.nop;\r
+    this.onShow(this.visible, this.tag);\r
+}\r
+\r
+function QWndCtrl_show_ie4(show) {\r
+    if (this.visible != show) {\r
+        var fx = false;\r
+        switch (show ? this.effects & 15 : (this.effects & 240) >>> 4) {\r
+            case 1:\r
+                fx = this.wnd.filters[5];\r
+                break;\r
+            case 2:\r
+                (fx = this.wnd.filters[6]).transition = show ? 1 : 0;\r
+                break;\r
+            case 3:\r
+                (fx = this.wnd.filters[6]).transition = show ? 3 : 2;\r
+                break;\r
+            case 4:\r
+                (fx = this.wnd.filters[6]).transition = show ? 5 : 4;\r
+                break;\r
+            case 5:\r
+                (fx = this.wnd.filters[6]).transition = show ? 14 : 13;\r
+                break;\r
+            case 6:\r
+                (fx = this.wnd.filters[6]).transition = show ? 16 : 15;\r
+                break;\r
+            case 7:\r
+                (fx = this.wnd.filters[6]).transition = 12;\r
+                break;\r
+            case 8:\r
+                (fx = this.wnd.filters[6]).transition = 8;\r
+                break;\r
+            case 9:\r
+                (fx = this.wnd.filters[6]).transition = 9;\r
+        }\r
+        if (fx) {\r
+            fx.apply();\r
+            this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";\r
+            this.fxhandler = QWndCtrl_fxhandler;\r
+            fx.play(0.3);\r
+        } else {\r
+            this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";\r
+            this.onShow(show, this.tag);\r
+        }\r
+    }\r
+}\r
+\r
+function QWndCtrl_fade_moz(op, step) {\r
+    this._wndt = false;\r
+    if (step) {\r
+        op += step;\r
+        if ((op > 0) && (op < this.opacity)) {\r
+            this.wnd.style.MozOpacity = op + "%";\r
+            this._wndt = setTimeout(this.name + ".fade(" + op + "," + step + ")", 50);\r
+        } else {\r
+            if (op <= 0) {\r
+                this.wnd.style.visibility = "hidden";\r
+                this.visible = false;\r
+            }\r
+            this.wnd.style.MozOpacity = this.opacity + "%";\r
+            this.onShow(this.visible, this.tag);\r
+        }\r
+    }\r
+}\r
+\r
+function QWndCtrl_show_moz(show) {\r
+    if (this.visible != show) {\r
+        if (this._wndt) {\r
+            clearTimeout(this._wndt);\r
+            this._wndt = false;\r
+        }\r
+        var step = show ? ((this.effects & 15) == 1) && Math.floor(this.opacity / 5) :\r
+            ((this.effects & 240) == 16) && -Math.floor(this.opacity / 5);\r
+        if (step) {\r
+            if (this.visible) {\r
+                this.fade(this.opacity - 0, step);\r
+            } else {\r
+                this.wnd.style.MozOpacity = "0%";\r
+                this.wnd.style.visibility = "visible";\r
+                this.visible = true;\r
+                this.fade(0, step);\r
+            }\r
+        } else {\r
+            this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";\r
+            this.onShow(show, this.tag);\r
+        }\r
+    }\r
+}\r
+\r
+function QWndCtrl_show_css(show) {\r
+    if (this.visible != show) {\r
+        this.wnd.style.visibility = (this.visible = show) ? "visible" : "hidden";\r
+        this.onShow(show, this.tag);\r
+    }\r
+}\r
+\r
+function QWndCtrl_show_ns4(show) {\r
+    if (this.visible != show) {\r
+        this.wnd.visibility = (this.visible = show) ? "show" : "hidden";\r
+        this.onShow(show, this.tag);\r
+    }\r
+}\r
+\r
+function QWndCtrl_create_dom2() {\r
+    with (this) {\r
+        this.fxhandler = QControl.nop;\r
+        var ie4 = document.body && document.body.filters;\r
+        var moz = document.body && document.body.style &&\r
+            typeof(document.body.style.MozOpacity) == "string";\r
+        document.write('<div unselectable="on" id="' + id +\r
+            (ie4 ? '" onfilterchange="' + name + '.fxhandler()': '') +\r
+            '" style="position:absolute;left:' + x + 'px;top:' + y +\r
+            'px;width:' + width + (height != null ? 'px;height:' + height : '') +\r
+            'px;visibility:' + (visible ? 'visible' : 'hidden') +\r
+            ';overflow:hidden' + (zindex ? ';z-index:' + zindex : '') +\r
+            (ie4 ? ';filter:Gray(enabled=' + (effects & 256 ? '1' : '0') +\r
+            ') Xray(enabled=' + (effects & 512 ? '1' : '0') +\r
+            ') Invert(enabled=' + (effects & 1024 ? '1' : '0') +\r
+            ') alpha(enabled=' + (opacity < 100 ? '1' : '0') + ',opacity=' + opacity +\r
+            ') shadow(enabled=' + (effects & 2048 ? '1' : '0') +\r
+            ',direction=135) BlendTrans(enabled=0) RevealTrans(enabled=0)' : '') +\r
+            (moz && (opacity < 100) ? ';-moz-opacity:' + opacity + '%' : '') +\r
+            '"><div unselectable="on" class="qwindow">');\r
+        if (typeof(content) == "function") {\r
+            this.content();\r
+        } else {\r
+            document.write(content);\r
+        }\r
+        document.write('</div></div>');\r
+        if (this.wnd = document.getElementById ? document.getElementById(id) :\r
+            (document.all.item ? document.all.item(id) : document.all[id])) {\r
+            if (wnd.style) {\r
+                ie4 = ie4 && wnd.filters;\r
+                moz = moz && typeof(wnd.style.MozOpacity) == "string";\r
+                this.moveTo = QWndCtrl_moveTo_css;\r
+                this.setZIndex = QWndCtrl_setZIndex_css;\r
+                this.focus = QWndCtrl_focus;\r
+                this.setSize = QWndCtrl_setSize_css;\r
+                this.show = ie4 ? QWndCtrl_show_ie4 : (moz ? QWndCtrl_show_moz : QWndCtrl_show_css);\r
+                this.fade = moz ? QWndCtrl_fade_moz : QControl.nop;\r
+                this.setOpacity = ie4 ? QWndCtrl_setOpacity_ie4 : (moz ? QWndCtrl_setOpacity_moz : QControl.nop);\r
+                this.setEffects = ie4 ? QWndCtrl_setEffects_ie4 : (moz ? QWndCtrl_setEffects_moz : QControl.nop);\r
+                this.center = self.innerWidth ? QWndCtrl_center_moz :\r
+                    (document.body && document.body.clientWidth ? QWndCtrl_center_ie4 : QControl.nop);\r
+            }\r
+        }\r
+    }\r
+}\r
+\r
+function QWndCtrl_create_ns4(finalize) {\r
+    with (this) {\r
+        if (finalize) {\r
+            if (_wnde) {\r
+                parent.window.onload = _wnde;\r
+                parent.window.onload();\r
+            }\r
+            document.open();\r
+            document.write('<div class="qwindow">');\r
+            this.content();\r
+            document.write('</div>');\r
+            document.close();\r
+        } else {\r
+            document.write('<layer id="' + id + '" left="' + x + '" top="' + y +\r
+                '" width="' + width + '" visibility="' + (visible ? 'show' : 'hidden') +\r
+                (height != null ? '" height="' + height + '" clip="' + width + ',' + height : '') +\r
+                (zindex ? '" z-index="' + zindex : '') + (typeof(content) != "function" ?\r
+                '"><div class="qwindow">' + content + '</div></layer>' : '">&nbsp;</layer>'));\r
+            if (this.window = this.wnd = document.layers[id]) {\r
+                if (this.document = wnd.document) {\r
+                    this.show = QWndCtrl_show_ns4;\r
+                    this.moveTo = QWndCtrl_moveTo_ns4;\r
+                    this.setZIndex = QWndCtrl_setZIndex_ns4;\r
+                    this.focus = QWndCtrl_focus;\r
+                    this.center = QWndCtrl_center_moz;\r
+                    this.setSize = QWndCtrl_setSize_ns4;\r
+                    if (typeof(content) == "function") {\r
+                        this._wnde = parent.window.onload;\r
+                        parent.window.onload = new Function(name + ".create(true)");\r
+                    }\r
+                }\r
+            }\r
+        }\r
+    }\r
+}\r
+\r
+function QWndCtrl_create_na() {\r
+    this.document.write('Object is not supported.');\r
+    this.wnd = null;\r
+}\r
+\r
+function QWndCtrl_create() {\r
+    with (this) {\r
+        this.create = (document.getElementById || document.all) ? QWndCtrl_create_dom2 :\r
+            (document.layers ? QWndCtrl_create_ns4 : QWndCtrl_create_na);\r
+        create();\r
+    }\r
+}\r
+\r
+function QWndCtrl() {\r
+    this.x = this.y = 0;\r
+    this.width = this.height = 0;\r
+    this.content = "";\r
+    this.visible = true;\r
+    this.effects = 0;\r
+    this.opacity = 100;\r
+    this.zindex = null;\r
+    this._wndt = this._wnde = false;\r
+    this.create = QWndCtrl_create;\r
+    this.show = QControl.nop;\r
+    this.focus = QControl.nop;\r
+    this.center = QControl.nop;\r
+    this.moveTo = QControl.nop;\r
+    this.setSize = QControl.nop;\r
+    this.setOpacity = QControl.nop;\r
+    this.setEffects = QControl.nop;\r
+    this.setZIndex  = QControl.nop;\r
+    this.onShow = QControl.event;\r
+}\r
+QWndCtrl.prototype = new QControl();\r
+QWndCtrl.TOPZINDEX = 1000;\r
+QWndCtrl.GRAY      = 256;\r
+QWndCtrl.XRAY      = 512;\r
+QWndCtrl.INVERT    = 1024;\r
+QWndCtrl.SHADOW    = 2048;\r
+QWndCtrl.FADEIN    = 1;\r
+QWndCtrl.FADEOUT   = 16;\r
+QWndCtrl.BOXIN     = 2;\r
+QWndCtrl.BOXOUT    = 32;\r
+QWndCtrl.CIRCLEIN  = 3;\r
+QWndCtrl.CIRCLEOUT = 48;\r
+QWndCtrl.WIPEIN    = 4;\r
+QWndCtrl.WIPEOUT   = 64;\r
+QWndCtrl.HBARNIN   = 5;\r
+QWndCtrl.HBARNOUT  = 80;\r
+QWndCtrl.VBARNIN   = 6;\r
+QWndCtrl.VBARNOUT  = 96;\r
+QWndCtrl.DISSOLVEIN  = 7;\r
+QWndCtrl.DISSOLVEOUT = 112;\r
+QWndCtrl.HBLINDSIN   = 8;\r
+QWndCtrl.HBLINDSOUT  = 128;\r
+QWndCtrl.VBLINDSIN   = 9;\r
+QWndCtrl.VBLINDSOUT  = 144;\r
diff --git a/httemplate/elements/search-cust_main.html b/httemplate/elements/search-cust_main.html
new file mode 100644 (file)
index 0000000..f2b17ea
--- /dev/null
@@ -0,0 +1,164 @@
+%
+%  my( %opt ) = @_;
+%  $opt{'field_name'} ||= 'custnum';
+%
+%  my $cust_main = '';
+%  if ( $opt{'value'} ) {
+%    $cust_main = qsearchs(
+%      'table'     => 'cust_main',
+%      'hashref'   => { 'custnum' => $opt{'value'} },
+%      'extra_sql' => " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql,
+%    );
+%  }
+%
+
+
+<INPUT TYPE="hidden" NAME="<% $opt{'field_name'} %>" VALUE="<% $opt{'value'} %>">
+
+<!-- some false laziness w/ misc/batch-cust_pay.html, though not as bad as i'd thought at first... -->
+
+<INPUT TYPE="text" NAME="<% $opt{'field_name'} %>_search" ID="<% $opt{'field_name'} %>_search" SIZE="32" VALUE="<% $cust_main ? $cust_main->name : '(cust #, name or company)' %>" onFocus="clearhint_<% $opt{'field_name'} %>_search(this);" onClick="clearhint_<% $opt{'field_name'} %>_search(this);" onChange="smart_<% $opt{'field_name'} %>_search(this);">
+
+<SELECT NAME="<% $opt{'field_name'} %>_select" ID="<% $opt{'field_name'} %>_select" STYLE="color:#ff0000; display:none" onChange="select_<% $opt{'field_name'} %>(this);">
+</SELECT>
+
+<% include('/elements/xmlhttp.html',
+              'url'  => $p. 'misc/xmlhttp-cust_main-search.cgi',
+              'subs' => [ 'smart_search' ],
+           )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+  function clearhint_<% $opt{'field_name'} %>_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);
+
+  }
+
+  function smart_<% $opt{'field_name'} %>_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('<% $opt{'field_name'} %>_select');
+
+    //alert("search for customer " + customer);
+
+    function <% $opt{'field_name'} %>_search_update(customers) {
+
+      //alert('customers returned: ' + customers);
+
+      var customerArray = eval('(' + customers + ')');
+
+      what.disabled = false;
+      what.style.backgroundColor = '#ffffff';
+
+      if ( customerArray.length == 0 ) {
+
+        what.form.<% $opt{'field_name'} %>.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.<% $opt{'field_name'} %>.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 = '';
+
+      }
+
+    }
+
+    smart_search( customer, <% $opt{'field_name'} %>_search_update );
+
+
+  }
+
+  function select_<% $opt{'field_name'} %> (what) {
+
+    var custnum = what.options[what.selectedIndex].value;
+    var customer = what.options[what.selectedIndex].text;
+
+    var customer_obj = document.getElementById('<% $opt{'field_name'} %>_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.<% $opt{'field_name'} %>.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>
+
diff --git a/httemplate/elements/select-access_group.html b/httemplate/elements/select-access_group.html
new file mode 100644 (file)
index 0000000..299a66a
--- /dev/null
@@ -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 (file)
index 0000000..54069a5
--- /dev/null
@@ -0,0 +1,21 @@
+<% include( '/elements/select-table.html',
+                 'table'       => 'agent',
+                 'name_col'    => 'agent',
+                 'value'       => $agentnum || '',
+                 'empty_label' => 'all',
+                 'hashref'     => { 'disabled' => '' },
+                 'extra_sql'   => ' AND '.
+                                  $FS::CurrentUser::CurrentUser->agentnums_sql.
+                                  ' ORDER BY agent',
+                 %opt,
+             )
+%>
+<%init>
+
+my %opt = @_;
+my $agentnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'records'} = delete $opt{'agents'}
+  if $opt{'agents'};
+
+</%init>
diff --git a/httemplate/elements/select-agent_type.html b/httemplate/elements/select-agent_type.html
new file mode 100644 (file)
index 0000000..ba59cd3
--- /dev/null
@@ -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-cust-fields.html b/httemplate/elements/select-cust-fields.html
new file mode 100644 (file)
index 0000000..98feaf8
--- /dev/null
@@ -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 (file)
index 0000000..8b446b9
--- /dev/null
@@ -0,0 +1,56 @@
+<%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-table.html',
+              'table'          => 'part_pkg',
+              'name_col'       => 'pkg',
+              'empty_label'    => 'Select package',
+              'label_callback' => sub { $_[0]->pkgpart. ': '.
+                                        $_[0]->pkg.     ' - '.
+                                        $_[0]->comment;
+                                      },
+              %opt,
+          )
+%>
+<%init>
+
+my( %opt ) = @_;
+
+my $cust_main = $opt{'cust_main'}
+  or die "cust_main not specified";
+
+$opt{'records'} = delete $opt{'part_pkg'}
+  if $opt{'part_pkg'};
+
+my $extra_sql = $opt{'extra_sql'}.
+  ' AND 0 < ( SELECT COUNT(*) FROM type_pkgs '.
+  '             WHERE typenum = '. $cust_main->agent->typenum.
+  '             AND type_pkgs.pkgpart = part_pkg.pkgpart )';
+
+$opt{'records'} ||= [ qsearch({ 
+                                'table'     => 'part_pkg',
+                                'hashref'   => { 'disabled' => '', },
+                                'extra_sql' => "$extra_sql ORDER BY pkg",
+                                #'extra_sql' => $extra_sql,
+                                #'order_by'  => 'ORDER BY pkg',
+                             })
+                    ];
+
+</%init>
diff --git a/httemplate/elements/select-cust_main-status.html b/httemplate/elements/select-cust_main-status.html
new file mode 100644 (file)
index 0000000..2e0b6cb
--- /dev/null
@@ -0,0 +1,30 @@
+<SELECT NAME="<% $opt{'field'} || 'status' %>"
+        <% $opt{'multiple'} ? 'MULTIPLE' : '' %>
+        <% $onchange %>
+>
+
+  <OPTION VALUE="">all
+
+% foreach my $option ( @{ $opt{'statuses'} } ) { 
+
+    <OPTION VALUE="<% $option %>"
+            <% $option eq $curr_value ? 'SELECTED' : '' %>
+    ><% $option %>
+
+% } 
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+$opt{'statuses'} ||= [ FS::cust_main->statuses() ]; # { disabled=>'' } )
+
+my $onchange = $opt{'onchange'}
+                 ? 'onChange="'. $opt{'onchange'}. '(this)"'
+                 : '';
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+</%init>
diff --git a/httemplate/elements/select-cust_pkg-status.html b/httemplate/elements/select-cust_pkg-status.html
new file mode 100644 (file)
index 0000000..2d545c0
--- /dev/null
@@ -0,0 +1,30 @@
+<SELECT NAME="<% $opt{'field'} || 'status' %>"
+        <% $opt{'multiple'} ? 'MULTIPLE' : '' %>
+        <% $onchange %>
+>
+
+  <OPTION VALUE="">all
+
+% foreach my $option ( @{ $opt{'statuses'} } ) { 
+
+    <OPTION VALUE="<% $option %>"
+            <% $option eq $curr_value ? 'SELECTED' : '' %>
+    ><% $option %>
+
+% } 
+
+</SELECT>
+
+<%init>
+
+my %opt = @_;
+
+$opt{'statuses'} ||= [ FS::cust_pkg->statuses() ]; # { disabled=>'' } )
+
+my $onchange = $opt{'onchange'}
+                 ? 'onChange="'. $opt{'onchange'}. '(this)"'
+                 : '';
+
+my $curr_value = $opt{'curr_value'} || $opt{'value'};
+
+</%init>
diff --git a/httemplate/elements/select-month_year.html b/httemplate/elements/select-month_year.html
new file mode 100644 (file)
index 0000000..34476bc
--- /dev/null
@@ -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 (file)
index 0000000..2a689f3
--- /dev/null
@@ -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_referral.html b/httemplate/elements/select-part_referral.html
new file mode 100644 (file)
index 0000000..c4b8829
--- /dev/null
@@ -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-payby.html b/httemplate/elements/select-payby.html
new file mode 100644 (file)
index 0000000..3f19cb9
--- /dev/null
@@ -0,0 +1,40 @@
+<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 = ( 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-pkg_class.html b/httemplate/elements/select-pkg_class.html
new file mode 100644 (file)
index 0000000..f30259e
--- /dev/null
@@ -0,0 +1,18 @@
+<% include( '/elements/select-table.html',
+                 'table'       => 'pkg_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{'pkg_class'}
+  if $opt{'pkg_class'};
+
+</%init>
diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html
new file mode 100644 (file)
index 0000000..8bcbf25
--- /dev/null
@@ -0,0 +1,114 @@
+<%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',
+   
+    #opt
+    'empty_label'    => '', #better specify it though, the default might change
+    'hashref'        => {},
+    'extra_sql'      => '',
+    'records'        => \@records, #instead of hashref
+    'pre_options'    => [ 'value' => 'option' ], #before normal options
+    '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
+    'multiple'       => 0, # bool
+    'disable_empty'  => 0, # bool (implied by multiple)
+    'debug'          => 0, #set true to enable
+    'label_callback' => sub { my $record = shift; return "label"; },
+  )
+
+</%doc>
+
+<SELECT <% $opt{'multiple'} ? 'MULTIPLE' : '' %>
+        NAME = "<% $opt{'element_name'} || $opt{'field'} || $key %>"
+        <% $onchange %>
+        <% $opt{'element_etc'} %>
+>
+
+% while ( @pre_options ) { 
+    <OPTION VALUE="<% shift(@pre_options) %>"><% shift(@pre_options) %>
+
+% } 
+
+% unless ( $opt{'multiple'} || $opt{'disable_empty'} ) {
+    <OPTION VALUE=""><% $opt{'empty_label'} || 'all' %>
+% }
+
+% foreach my $record ( sort { $a->$name_col() cmp $b->$name_col() } @records ) {
+%   my $recvalue = $record->$key();
+    <OPTION VALUE="<% $recvalue %>"
+            <% ref($value) && $value->{$recvalue} || $value == $recvalue
+               ? ' SELECTED' : ''
+            %>
+    ><% $opt{'label_callback'}
+          ? &{ $opt{'label_callback'} }( $record )
+          : $record->$name_col()
+     %>
+% } 
+
+</SELECT>
+
+<%init>
+
+my( %opt ) = @_;
+
+warn "elements/select-table.html: \n". Dumper(%opt)
+  if exists $opt{debug} && $opt{debug};
+
+my $onchange = $opt{'onchange'}
+                 ? 'onChange="'. $opt{'onchange'}. '(this)"'
+                 : '';
+
+my $key = dbdef->table($opt{'table'})->primary_key; #? $opt{'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 @records = ();
+if ( $opt{'records'} ) {
+  @records = @{ $opt{'records'} };
+} else {
+  @records = qsearch( {
+    'table'     => $opt{'table'},
+    'hashref'   => ( $opt{'hashref'} || {} ),
+    'extra_sql' => ( $opt{'extra_sql'} || '' ),
+  });
+}
+
+unless (    ! $value
+         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},
+    'hashref'   => $opt{hashref},
+    'extra_sql' => ( $opt{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} } : ();
+
+</%init>
diff --git a/httemplate/elements/select-taxclass.html b/httemplate/elements/select-taxclass.html
new file mode 100644 (file)
index 0000000..6fe35d3
--- /dev/null
@@ -0,0 +1,38 @@
+% if ( $conf->exists('enable_taxclasses') ) { 
+
+    <SELECT NAME="<% $opt{'name'} || '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="taxclass" VALUE="<% $selected_taxclass %>">
+
+% } 
+
+<%init>
+
+my( $selected_taxclass, %opt ) = @_;
+my $conf = new FS::Conf;
+
+unless ( $opt{'taxclasses'} ) {
+
+  my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
+     or die dbh->errstr;
+  $sth->execute or die $sth->errstr;
+  my %taxclasses = map { $_->[0] => 1 } @{$sth->fetchall_arrayref};
+  @{ $opt{'taxclasses'} } = grep $_, keys %taxclasses;
+
+}
+
+</%init>
diff --git a/httemplate/elements/selectlayers.html b/httemplate/elements/selectlayers.html
new file mode 100644 (file)
index 0000000..4496892
--- /dev/null
@@ -0,0 +1,201 @@
+<%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-table,
+                                                         #XXX tbd:
+                                                         # more?
+                                      },
+                                      ...
+                                    ],
+                        'layer2' => [ 'l2fieldname',
+                                      ...
+                                    ],
+
+    #current values for layer fields above
+    'layer_values' => { 'layer'  => { 'fieldname'  => 'current_value',
+                                      'fieldname2' => 'field2value',
+                                      ...
+                                    },
+                        'layer2' => { 'l2fieldname' => 'l2value',
+                                      ...
+                                    },
+                        ...
+                      },
+
+    '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
+  )
+
+</%doc>
+% unless ( $opt{html_only} || $opt{js_only} ) {
+    <SCRIPT TYPE="text/javascript">
+% }
+% unless ( $opt{html_only} ) {
+      //alert('start function define');
+      function <% $key %>changed(what) {
+
+        <% $opt{'onchange'} %>
+
+        var <% $key %>layer = what.options[what.selectedIndex].value;
+
+%       foreach my $layer ( keys %$options ) {
+
+          if (<% $key %>layer == "<% $layer %>" ) {
+
+%           foreach my $not ( grep { $_ ne $layer } keys %$options ) {
+%             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'} %>
+
+      }
+      //alert('end function define');
+% }
+% unless ( $opt{html_only} || $opt{js_only} ) {
+    </SCRIPT>
+% }
+%
+% unless ( $opt{js_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>
+
+<% ref($between) ? &{$between}($key) : $between %>
+
+%   foreach my $layer ( keys %$options ) {
+
+      <DIV ID="<% $key %>d<% $layer %>"
+           STYLE="<% $layer eq $selected
+                       ? '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 $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};
+
+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 (file)
index 0000000..9060d89
--- /dev/null
@@ -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 (file)
index 0000000..e1e6c36
--- /dev/null
@@ -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 (file)
index 0000000..8152b65
--- /dev/null
@@ -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 (file)
index 0000000..35dc05d
--- /dev/null
@@ -0,0 +1,5 @@
+</TABLE>
+
+<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<% include('tr-title.html', @_ ) %>
diff --git a/httemplate/elements/tr-checkbox-multiple.html b/httemplate/elements/tr-checkbox-multiple.html
new file mode 100644 (file)
index 0000000..bb90a82
--- /dev/null
@@ -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 (file)
index 0000000..2e6d1f1
--- /dev/null
@@ -0,0 +1,25 @@
+<% include('tr-td-label.html', @_ ) %>
+
+  <TD <% $style %>>
+    <INPUT TYPE  = "checkbox"
+           NAME  = "<% $opt{field} %>"
+           ID    = "<% $opt{id} %>"
+           VALUE = "<% $opt{value} %>"
+           <% $opt{curr_value} eq $opt{value} ? ' CHECKED' : '' %>
+           <% $onchange %>
+    >
+  </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-fixed-country.html b/httemplate/elements/tr-fixed-country.html
new file mode 100644 (file)
index 0000000..806d92c
--- /dev/null
@@ -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 (file)
index 0000000..eea30dd
--- /dev/null
@@ -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 (file)
index 0000000..095e1bc
--- /dev/null
@@ -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 (file)
index 0000000..cb58bf6
--- /dev/null
@@ -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 (file)
index 0000000..8a1dd62
--- /dev/null
@@ -0,0 +1,75 @@
+% 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:   "%m/%d/%Y<% $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:   "%m/%d/%Y<% $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 = @_;
+
+$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 (file)
index 0000000..11581d5
--- /dev/null
@@ -0,0 +1,40 @@
+
+<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) = @_;
+
+$format = "%m/%d/%Y" unless $format;
+$label = $name unless $label;
+
+if ($usedatetime) {
+  my $dt = DateTime->from_epoch(epoch => $value, time_zone => 'floating');
+  $value = $dt->strftime($format)
+    unless $value eq '';
+}else{
+  $value = time2str($format, $value);
+}
+
+</%init>
+
diff --git a/httemplate/elements/tr-input-lessthan_greaterthan.html b/httemplate/elements/tr-input-lessthan_greaterthan.html
new file mode 100644 (file)
index 0000000..16c2ed9
--- /dev/null
@@ -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 (file)
index 0000000..8801419
--- /dev/null
@@ -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 (file)
index 0000000..ae553a9
--- /dev/null
@@ -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 (file)
index 0000000..c8bec5e
--- /dev/null
@@ -0,0 +1,44 @@
+<% include('tr-td-label.html', @_ ) %>
+
+  <TD <% $style %>>
+
+    <% $opt{'prefix'} %><INPUT TYPE  = "<% $opt{type} || 'text' %>"
+                               NAME  = "<% $opt{field} %>"
+                               ID    = "<% $opt{id} %>"
+                               VALUE = "<% $value |h %>"
+                               <% $size %>
+                               <% $maxlength %>
+                               <% $align %>
+                               <% $onchange %>
+                        ><% $opt{'postfix'} %>
+
+  </TD>
+
+</TR>
+
+<%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'}. '"'
+                : '';
+
+my $align = $opt{'text-align'}
+                ? 'STYLE="text-align: '. $opt{'text-align'}. '"'
+                : '';
+
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+</%init>
diff --git a/httemplate/elements/tr-password.html b/httemplate/elements/tr-password.html
new file mode 100644 (file)
index 0000000..bbc624d
--- /dev/null
@@ -0,0 +1,4 @@
+<% include('tr-input-text.html', @_,
+             'type'   => 'password',
+          )
+%>
diff --git a/httemplate/elements/tr-select-access_group.html b/httemplate/elements/tr-select-access_group.html
new file mode 100644 (file)
index 0000000..e443ad2
--- /dev/null
@@ -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 (file)
index 0000000..69cd95c
--- /dev/null
@@ -0,0 +1,37 @@
+% if ( scalar(@agents) == 1 ) { 
+
+  <INPUT TYPE="hidden" NAME="<% $opt{'field'} || 'agentnum' %>" VALUE="<% $agents[0]->agentnum %>">
+
+% } 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;
+if ( $opt{'agents'} ) {
+  #@agents = @{ $opt{'agents'} };
+  #here is the agent virtualization
+  my $agentnums_href = $FS::CurrentUser::CurrentUser->agentnums_href;
+  @agents = grep $agentnums_href->{$_->agentnum}, @{ $opt{'agents'} };
+  delete $opt{'agents'};
+} else {
+  @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 (file)
index 0000000..1b0dfd4
--- /dev/null
@@ -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-cust-fields.html b/httemplate/elements/tr-select-cust-fields.html
new file mode 100644 (file)
index 0000000..80562fe
--- /dev/null
@@ -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_main-status.html b/httemplate/elements/tr-select-cust_main-status.html
new file mode 100644 (file)
index 0000000..6700d7e
--- /dev/null
@@ -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-status.html b/httemplate/elements/tr-select-cust_pkg-status.html
new file mode 100644 (file)
index 0000000..6cc29d0
--- /dev/null
@@ -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-from_to.html b/httemplate/elements/tr-select-from_to.html
new file mode 100644 (file)
index 0000000..083243d
--- /dev/null
@@ -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 (file)
index 0000000..7ba8d99
--- /dev/null
@@ -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 (file)
index 0000000..edf62dc
--- /dev/null
@@ -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_pkg.html b/httemplate/elements/tr-select-part_pkg.html
new file mode 100644 (file)
index 0000000..b6d4d4d
--- /dev/null
@@ -0,0 +1,29 @@
+% if ( scalar(@{ $opt{'part_pkg'} }) == 0 ) { 
+
+  <INPUT TYPE="hidden" NAME="<% $opt{'field'} || 'pkgpart' %>" VALUE="">
+
+% } else { 
+
+  <TR>
+    <TD ALIGN="right"><% $opt{'label'} || 'Package definition' %></TD>
+    <TD>
+      <% include( '/elements/select-table.html',
+                    'table'     => 'part_pkg',
+                    'name_col'  => 'pkg',
+                    'multiple'  => 1,
+                    #N/A 'empty_label' => '(none)',
+                    %opt,
+                )
+      %>
+    </TD>
+  </TR>
+
+% } 
+
+<%init>
+
+my( %opt ) = @_;
+
+$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 (file)
index 0000000..62795e9
--- /dev/null
@@ -0,0 +1,35 @@
+% 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>
+         <% 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 $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 (file)
index 0000000..0274ef1
--- /dev/null
@@ -0,0 +1,29 @@
+% 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-table.html',
+                    'table'     => 'part_svc',
+                    'name_col'  => 'svc',
+                    'multiple'  => 1,
+                    #N/A 'empty_label' => '(none)',
+                    %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 (file)
index 0000000..354eb55
--- /dev/null
@@ -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 (file)
index 0000000..aa27609
--- /dev/null
@@ -0,0 +1,27 @@
+% if ( scalar(@{ $opt{'pkg_class'} }) == 0 ) { 
+
+  <INPUT TYPE="hidden" 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-reason.html b/httemplate/elements/tr-select-reason.html
new file mode 100755 (executable)
index 0000000..4051e7d
--- /dev/null
@@ -0,0 +1,149 @@
+<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 = $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;
+  }
+
+}
+
+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-taxclass.html b/httemplate/elements/tr-select-taxclass.html
new file mode 100644 (file)
index 0000000..3bba683
--- /dev/null
@@ -0,0 +1,33 @@
+% if ( ! $conf->exists('enable_taxclasses')
+%      || scalar(@{ $opt{'taxclasses'} }) == 0
+% ) { 
+
+  <INPUT TYPE="hidden" NAME=""<% $opt{'element_name'} || $opt{'field'} || 'taxclass' %>" VALUE="<% $taxclass %>">
+
+% } else { 
+
+  <TR>
+    <TD ALIGN="right"><% $opt{'label'} || 'Tax class: ' %></TD>
+    <TD>
+      <% include( '/elements/select-taxclass.html', $taxclass, %opt ) %>
+    </TD>
+  </TR>
+
+% } 
+<%init>
+
+my( $taxclass, %opt ) = @_;
+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')
+    or die dbh->errstr;
+  $sth->execute 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.html b/httemplate/elements/tr-select.html
new file mode 100644 (file)
index 0000000..07b0a01
--- /dev/null
@@ -0,0 +1,61 @@
+<% include('tr-td-label.html', @_ ) %>
+
+  <TD <% $style %>>
+
+    <SELECT NAME          = "<% $opt{field} %>"
+            ID            = "<% $opt{id} %>"
+            previousValue = "<% $curr_value %>"
+            previousText  = "<% $labels->{$curr_value} || $curr_value %>"
+            <% $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>
+
+  </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+                 ? 'onChange="'. $opt{'onchange'}. '(this)"'
+                 : '';
+
+my $labels = $opt{'option_labels'} || $opt{'labels'};
+
+my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $curr_value = $opt{'curr_value'};
+
+</%init>
diff --git a/httemplate/elements/tr-selectlayers.html b/httemplate/elements/tr-selectlayers.html
new file mode 100644 (file)
index 0000000..865d822
--- /dev/null
@@ -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 (file)
index 0000000..455038d
--- /dev/null
@@ -0,0 +1,20 @@
+<TR>
+  <TD ALIGN="right"><% $opt{'label'} || 'Packages' %></TD>
+  <TD>
+    <% include( '/elements/select-table.html',
+                  'table'     => 'part_pkg',
+                 'name_col'  => 'pkg',
+                 'value'     => '',
+                 'empty_label' => '(none)',
+                 '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 (file)
index 0000000..77c0484
--- /dev/null
@@ -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-title.html b/httemplate/elements/tr-title.html
new file mode 100644 (file)
index 0000000..6e2f58f
--- /dev/null
@@ -0,0 +1,15 @@
+<TR>
+  <TD BGCOLOR="#e8e8e8" COLSPAN=2>&nbsp;</TD>
+</TR>
+
+<TR>
+  <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+    <FONT SIZE="+1"><% $opt{value} %></FONT>
+  </TH>
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+</%init>
diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css
new file mode 100644 (file)
index 0000000..97c7da8
--- /dev/null
@@ -0,0 +1,196 @@
+
+.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 black;
+       padding:                1px;
+       background:             white;
+       filter:                 progid:DXImageTransform.Microsoft.Shadow(color="#777777", Direction=135, Strength=4)
+                               alpha(Opacity=95);
+       -moz-opacity:           0.95;
+       /* a drop shadow would be nice in moz/others too... */
+}
+
+.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 {
+       color:                  black;
+       border:                 1px solid #7e0079;
+}
+
+.webfx-menu a:hover {
+       color:                  black;
+       /* background:          #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */
+       /* background:          #ffe6fe; */
+       /* background:          #ffc2fe; */
+       background:             #fff2fe;
+       border:                 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/
+}      
+
+.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:              #000000;
+       /* 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;
+
+       /* border:                              1px solid black; /*rgb(0,0,0);/*rgb(255,128,0);*/
+       /* border: 1px solid black; /* #ffffff; */
+       /* border-bottom: 1px solid black; */
+       border-bottom: 1px solid white;
+       /* border-bottom:       1px solid rgb(0,66,174);
+       /* border-bottom: 1px solid black;
+       border-bottom: 1px solid black;
+       border-bottom: 1px solid black; */
+
+       padding:                        1px 5px 1px 5px;
+
+       /* 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;
+}
+
+.webfx-menu-bar a:hover {
+       /* color:                       black; */
+       color:                  white;
+       /* background:          rgb(120,172,255);        */
+       /* background:          #BC79B8; */
+        background:            #7e0079;
+       /* border-left: 1px solid rgb(234,242,255);
+       border-right:   1px solid rgb(0,66,174);
+       border-top:             1px solid rgb(234,242,255);
+       border-bottom:  1px solid rgb(0,66,174); */
+}
+
+.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);
+
+/*     border-left:    1px solid rgb(0,66,174); */
+/*     border-right:   1px solid rgb(234,242,255); */
+/*     border-top:             1px solid rgb(0,66,174); */
+/*     border-bottom:  1px solid rgb(234,242,255); */
+}
+
+.webfx-menu-title  {
+       color:                  black;
+       /* background:          #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */
+       background:             #7e0079;
+/*     border:                 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/
+       /* padding:             3px 1px 3px 6px; */
+       padding:                3px 1px 3px 5px;
+       display:                block;
+       font-size:              16px;
+/*        font-family:            Verdana, Arial, Helvetica, sans-serif; */
+        font-weight:            bold;
+       text-decoration:        none;
+       color:                  white;
+/*     border:                 1px solid white; */
+       border-bottom:          1px solid white;
+        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 (file)
index 0000000..134265f
--- /dev/null
@@ -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.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 (file)
index 0000000..7591703
--- /dev/null
@@ -0,0 +1,211 @@
+
+.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 black;
+       padding:                1px;
+       background:             white;
+       filter:                 progid:DXImageTransform.Microsoft.Shadow(color="#777777", Direction=135, Strength=4)
+                               alpha(Opacity=95);
+       -moz-opacity:           0.95;
+       /* a drop shadow would be nice in moz/others too... */
+}
+
+.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 {
+       color:                  black;
+       border:                 1px solid #7e0079;
+}
+
+.webfx-menu a:hover {
+       color:                  black;
+       /* background:          #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */
+       /* background:          #ffe6fe; */
+       /* background:          #ffc2fe; */
+       background:             #fff2fe;
+       border:                 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/
+}      
+
+.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:              #000000;
+       /* 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; */
+
+       /* border:                              1px solid black; /*rgb(0,0,0);/*rgb(255,128,0);*/
+       /* border: 1px solid black; /* #ffffff; */
+       /* border-bottom: 1px solid black; */
+       /* border-bottom: 1px solid white; */
+       /* border-bottom:       1px solid rgb(0,66,174);
+       /* border-bottom: 1px solid black;
+       border-bottom: 1px solid black;
+       border-bottom: 1px solid black; */
+
+       padding:                        1px 5px 1px 5px;
+
+       /* 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"));
+
+         background-color:#333333;
+        border:1px solid;
+        border-top-color:#cccccc;
+        border-left-color:#cccccc;
+        border-right-color:#aaaaaa;
+        border-bottom-color:#aaaaaa;
+
+         margin-right: 4px
+
+}
+
+.webfx-menu-bar a:link {
+       color: white;
+}
+
+.webfx-menu-bar a:hover {
+       /* color:                       black; */
+       color:                  white;
+       /* background:          rgb(120,172,255);        */
+       /* background:          #BC79B8; */
+        background:            #7e0079;
+       /* border-left: 1px solid rgb(234,242,255);
+       border-right:   1px solid rgb(0,66,174);
+       border-top:             1px solid rgb(234,242,255);
+       border-bottom:  1px solid rgb(0,66,174); */
+
+        border:1px solid;
+        border-top-color:#cccccc;
+        border-left-color:#cccccc;
+        border-right-color:#aaaaaa;
+        border-bottom-color:#aaaaaa;
+
+}
+
+.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);
+
+/*     border-left:    1px solid rgb(0,66,174); */
+/*     border-right:   1px solid rgb(234,242,255); */
+/*     border-top:             1px solid rgb(0,66,174); */
+/*     border-bottom:  1px solid rgb(234,242,255); */
+}
+
+.webfx-menu-title  {
+       color:                  black;
+       /* background:          #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */
+       background:             #7e0079;
+/*     border:                 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/
+       /* padding:             3px 1px 3px 6px; */
+       padding:                3px 1px 3px 5px;
+       display:                block;
+       font-size:              16px;
+/*        font-family:            Verdana, Arial, Helvetica, sans-serif; */
+        font-weight:            bold;
+       text-decoration:        none;
+       color:                  white;
+/*     border:                 1px solid white; */
+       border-bottom:          1px solid white;
+        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 (file)
index 0000000..8d81035
--- /dev/null
@@ -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.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 (file)
index 0000000..3f4462b
--- /dev/null
@@ -0,0 +1,109 @@
+<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;
+
+  }
+% 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 (file)
index 0000000..d7cae80
--- /dev/null
@@ -0,0 +1,109 @@
+<% include('elements/monthly.html',
+                'title'        => $title. 'Sales Report (Gross)',
+                'graph_type'   => 'Mountain',
+                'items'        => \@items,
+                'params'       => \@params,
+                'labels'       => \@labels,
+                'graph_labels' => \@labels,
+                'colors'       => \@colors,
+                'links'        => \@links,
+                'remove_empty' => 1,
+                'bottom_total' => 1,
+                'bottom_link'  => "$link;",
+                '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/search/cust_pkg.cgi
+my $classnum = 0;
+my @pkg_class = ();
+if ( $cgi->param('classnum') =~ /^(\d*)$/ ) {
+  $classnum = $1;
+  if ( $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 '' ) {
+    $title .= 'Empty class ';
+    @pkg_class = ( '(empty class)' );
+  } elsif ( $classnum eq '0' ) {
+    @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  = ();
+
+my $link = "${p}search/cust_bill_pkg.cgi?nottax=1;include_comp_cust=1";
+
+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 package classes...
+  my $n = 0;
+
+  foreach my $pkg_class ( @pkg_class ) {
+
+    push @items, 'cust_bill_pkg';
+
+
+    push @labels,
+      ( $sel_agent ? '' : $agent->agent.' ' ).
+      ( $classnum eq '0'
+          ? ( ref($pkg_class) ? $pkg_class->classname : $pkg_class ) 
+          : ''
+      );
+
+    my $row_classnum = ref($pkg_class) ? $pkg_class->classnum : 0;
+    my $row_agentnum = $agent->agentnum;
+    push @params, [ 'classnum' => $row_classnum,
+                    'agentnum' => $row_agentnum,
+                  ];
+
+    push @links, "$link;agentnum=$row_agentnum;classnum=$row_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 (file)
index 0000000..21ce07d
--- /dev/null
@@ -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/elements/monthly.html b/httemplate/graph/elements/monthly.html
new file mode 100644 (file)
index 0000000..7039bfe
--- /dev/null
@@ -0,0 +1,351 @@
+<%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>
+% if ( $cgi->param('_type') =~ /^(csv)$/ ) {
+%
+%   #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+%   http_header('Content-Type' => 'text/plain' );
+%
+%   my $csv = new Text::CSV_XS { 'always_quote' => 1,
+%                                'eol'          => "\n", #"\015\012", #"\012"
+%                              };
+%
+%   $csv->combine(map { my $m=$_; $m =~ s/^(\d+)\//$mon[$1-1] /; $m; }
+%                     ('', @{$data->{label}}, $opt{'nototal'} ? () : 'Total')
+%                );
+%   
+<% $csv->string %>
+%
+%   my @bottom_total = ();
+%   foreach ( @{ $data->{'items'} } ) {
+%
+%     my $col = 0;
+%     my $total = 0;
+%     $csv->combine(
+%       shift( @{ $data->{'item_labels'} } ),
+%       map { $total += $_; $bottom_total[$col++] += $_; sprintf($sprintf, $_); }
+%         ( @{ shift( @{$data->{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
+%
+%   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 ('', @{$data->{label}}, ($opt{'nototal'} ? () : 'Total') ) {
+%     my $header = $_;
+%     $header =~ s/^(\d+)\//$mon[$1-1] /;
+%     $worksheet->write($r, $c++, $header)
+%   }
+%
+%   my @bottom_total = ();
+%   foreach ( @{ $data->{'items'} } ) {
+%     $r++;
+%     $c = 0;
+%     my $total = 0;
+%     $worksheet->write( $r, $c++, shift( @{ $data->{'item_labels'} } ) );
+%     foreach ( @{ shift( @{$data->{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') =~ /^(png)$/ ) {
+%
+%   #my $chart = Chart::LinesPoints->new(1024,480);
+%   #my $chart = Chart::LinesPoints->new(768,480);
+%
+%   my $graph_type = 'LinesPoints';
+%   if ( $opt{'graph_type'} =~ /^(LinesPoints|Mountain)$/ ) {
+%     $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'} }
+%                         @{ $data->{'colors'} }
+%                   ),
+%                   #'grey_background' => [ 211, 211, 211 ],
+%                   'grey_background' => 'white',
+%                   'background' => [ 0xe8, 0xe8, 0xe8 ], #grey
+%                 },
+%     #'grey_background' => 'false',
+%     'legend_labels' => $data->{'item_labels'},
+%     'brush_size' => 4,
+%     #'pt_size' => 12,
+%   );
+%
+%   #my @data = map { $data->{$_} } ( 'label', @items );
+%   my @data = @{ $data->{data} };
+%   unshift @data, $data->{'label'};
+%   
+%   http_header('Content-Type' => 'image/png' );
+%
+%   $chart->_set_colors();
+%   
+<% $chart->scalar_png(\@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', "monthly.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', 'e8e8e8') %>
+
+<TR>
+
+  <TD></TD>
+
+% foreach my $column ( @{$data->{label}} ) {
+%       #$column =~ s/^(\d+)\//$mon[$1-1]<BR>/e;
+%       $column =~ s/^(\d+)\//$mon[$1-1]<BR>/;
+    <TH><% $column %></TH>
+% } 
+
+% unless ( $opt{'nototal'} ) { 
+    <TH>Total</TH>
+% } 
+
+</TR>
+
+% my @bottom_total = ();
+% foreach my $row ( @{ $data->{'items'} } ) {
+%
+%     #my $color = shift( @{ $opt{'colors'} } );
+%     my $color = shift( @{ $data->{'colors'} } );
+%     my $link = shift( @{ $data->{'links'} } );
+%     my ( $begin, $end ) = ( $fromparam, $toparam );
+%     if ( ref($link) ) {
+%       my $ref = $link;
+%       $link =  $ref->{link};
+%       $begin = $ref->{fromparam};
+%       $end =   $ref->{toparam};
+%     }
+%     $link = $link ? qq(<A HREF="$link) : '';
+%     my $label = shift( @{ $data->{'item_labels'} } );
+
+      <TR>
+
+        <TH>
+          <FONT COLOR="#<% $color %>"><% $label %></FONT>
+        </TH>
+
+%       #my $link = exists($opt{'links'}{$row})
+%             #  ? qq(<A HREF="$opt{'links'}{$row})
+%             #  : '';
+%       my @speriod = @{$data->{speriod}};
+%       my @eperiod = @{$data->{eperiod}};
+%       my $total = 0;
+%    
+%       my $col = 0;
+%       foreach my $column ( @{ shift( @{$data->{data}} ) } ) {
+
+          <TD ALIGN="right" BGCOLOR="#ffffff">
+            <% $link ? $link. "$begin=". shift(@speriod). ";$end=". shift(@eperiod). '">' : '' %><FONT COLOR="#<% $color %>"><% $money_char %><% sprintf($sprintf,, $column) %></FONT><% $link ? '</A>' : '' %>
+          </TD>
+%
+%         $total += $column;
+%         $bottom_total[$col++] += $column;
+%      
+%       } 
+
+%       unless ( $opt{'nototal'} ) { 
+            <TD ALIGN="right" BGCOLOR="#f5f6be">
+              <% $link ? $link. "$begin=". ${$data->{speriod}}[0]. ";$end=". ${$data->{eperiod}}[-1]. '">' : '' %><FONT COLOR="#<% $color %>"><% $money_char %><% sprintf($sprintf, $total) %></FONT><% $link ? '</A>' : '' %>
+            </TD>
+%           $bottom_total[$col++] += $total; 
+%       } 
+
+      </TR>
+
+% } 
+
+% if ( $opt{'bottom_total'} ) {
+%     my @speriod = ( @{$data->{speriod}}, ${$data->{speriod}}[0] );
+%     my @eperiod = ( @{$data->{eperiod}}, ${$data->{eperiod}}[-1] );
+
+  <TR>
+    <TH>Total</TH>
+
+% foreach my $total ( @bottom_total ) { 
+
+      <TD ALIGN="right" BGCOLOR="#f5f6be">
+        <% $opt{'bottom_link'}
+              ? '<A HREF="'. $opt{'bottom_link'}.
+                "$fromparam=". shift(@speriod).
+                ";$toparam=". shift(@eperiod). '">'
+              : ''
+        %>$<% sprintf($sprintf, $total) %><% $opt{'bottom_link'} ? '</A>' : '' %>
+
+      </TD>
+
+% } 
+
+  </TR>
+
+% } 
+
+</TABLE>
+
+<% include('/elements/footer.html') %>
+% } 
+<%once>
+
+</%once>
+<%init>
+
+my(%opt) = @_;
+
+my $sprintf = $opt{'sprintf'} || '%.2f';
+my $fromparam = $opt{'link_fromparam'} || 'begin';
+my $toparam =   $opt{'link_toparam'}   || 'end';
+
+my $conf = new FS::Conf;
+my $money_char = $opt{'disable_money'} ? '' : $conf->config('money_char');
+
+my @items = @{ $opt{'items'} };
+
+foreach my $other (qw( labels graph_labels colors links )) {
+#foreach my $other (qw( labels graph_labels colors )) {
+  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'       => $opt{'items'},
+  '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;
+
+</%init>
diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi
new file mode 100644 (file)
index 0000000..c30ff20
--- /dev/null
@@ -0,0 +1,74 @@
+<% 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 payments receipts );
+if ( $cgi->param('12mo') == 1 ) {
+  @items = map $_.'_12mo', @items;
+}
+
+my %label = (
+  'invoiced' => 'Gross Sales',
+  'netsales' => 'Net Sales',
+  'credits'  => 'Credits',
+  'payments' => 'Gross Receipts',
+  'receipts' => 'Net Receipts',
+);
+
+my %graph_suffix = (
+ 'invoiced' => ' (invoiced)', 
+ 'netsales' => ' (invoiced - applied credits)',
+ 'credits'  => '',
+ 'payments' => ' (payments)',
+ 'receipts' => '/Cashflow (payments - refunds)',
+);
+my %graph_label = map { $_ => $label{$_}.$graph_suffix{$_} } keys %label;
+
+$label{$_.'_12mo'} = $label{$_}. " (previous 12 months)"
+  foreach keys %label;
+
+$graph_label{$_.'_12mo'} = $graph_label{$_}. " (previous 12 months)"
+  foreach keys %graph_label;
+
+my %color = (
+  'invoiced' => '9999ff', #light blue
+  'netsales' => '0000cc', #blue
+  'credits'  => 'cc0000', #red
+  'payments' => '99cc99', #light green
+  'receipts' => '00cc00', #green
+);
+$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;",
+  'payments' => "${p}search/cust_pay.cgi?magic=_date;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 (file)
index 0000000..be5a71a
--- /dev/null
@@ -0,0 +1,35 @@
+<% 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: ' ) %>
+
+<% include('/elements/tr-select-pkg_class.html',
+              'pre_options' => [ '0' => 'all' ],
+              '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>
+-->
+
+</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 (file)
index 0000000..0ff0181
--- /dev/null
@@ -0,0 +1,27 @@
+<% 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: ',
+          )
+%>
+
+</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_money_time.html b/httemplate/graph/report_money_time.html
new file mode 100644 (file)
index 0000000..04e4c19
--- /dev/null
@@ -0,0 +1,39 @@
+<% 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: ' ) %>
+
+<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/images/32clear.gif b/httemplate/images/32clear.gif
new file mode 100644 (file)
index 0000000..5fdcea2
Binary files /dev/null and b/httemplate/images/32clear.gif differ
diff --git a/httemplate/images/ach.png b/httemplate/images/ach.png
new file mode 100644 (file)
index 0000000..fdcd5e6
Binary files /dev/null and b/httemplate/images/ach.png differ
diff --git a/httemplate/images/arrow.down.png b/httemplate/images/arrow.down.png
new file mode 100644 (file)
index 0000000..34cb028
Binary files /dev/null and b/httemplate/images/arrow.down.png differ
diff --git a/httemplate/images/arrow.right.black.png b/httemplate/images/arrow.right.black.png
new file mode 100644 (file)
index 0000000..933c258
Binary files /dev/null and b/httemplate/images/arrow.right.black.png differ
diff --git a/httemplate/images/arrow.right.png b/httemplate/images/arrow.right.png
new file mode 100644 (file)
index 0000000..60bcb76
Binary files /dev/null and b/httemplate/images/arrow.right.png differ
diff --git a/httemplate/images/background-cheat.png b/httemplate/images/background-cheat.png
new file mode 100644 (file)
index 0000000..ad332f6
Binary files /dev/null and b/httemplate/images/background-cheat.png differ
diff --git a/httemplate/images/black-gradient.png b/httemplate/images/black-gradient.png
new file mode 100644 (file)
index 0000000..225732d
Binary files /dev/null and b/httemplate/images/black-gradient.png differ
diff --git a/httemplate/images/black-gray-corner.png b/httemplate/images/black-gray-corner.png
new file mode 100644 (file)
index 0000000..17954cd
Binary files /dev/null and b/httemplate/images/black-gray-corner.png differ
diff --git a/httemplate/images/black-gray-gradient.png b/httemplate/images/black-gray-gradient.png
new file mode 100644 (file)
index 0000000..f5c318f
Binary files /dev/null and b/httemplate/images/black-gray-gradient.png differ
diff --git a/httemplate/images/black-gray-side.png b/httemplate/images/black-gray-side.png
new file mode 100644 (file)
index 0000000..f7a98a4
Binary files /dev/null and b/httemplate/images/black-gray-side.png differ
diff --git a/httemplate/images/black-gray-top.png b/httemplate/images/black-gray-top.png
new file mode 100644 (file)
index 0000000..ed07075
Binary files /dev/null and b/httemplate/images/black-gray-top.png differ
diff --git a/httemplate/images/calendar-disabled.png b/httemplate/images/calendar-disabled.png
new file mode 100644 (file)
index 0000000..81816bc
Binary files /dev/null and b/httemplate/images/calendar-disabled.png differ
diff --git a/httemplate/images/calendar.png b/httemplate/images/calendar.png
new file mode 100644 (file)
index 0000000..1632661
Binary files /dev/null and b/httemplate/images/calendar.png differ
diff --git a/httemplate/images/cvv2.png b/httemplate/images/cvv2.png
new file mode 100644 (file)
index 0000000..48c58d5
Binary files /dev/null and b/httemplate/images/cvv2.png differ
diff --git a/httemplate/images/cvv2_amex.png b/httemplate/images/cvv2_amex.png
new file mode 100644 (file)
index 0000000..82d1f47
Binary files /dev/null and b/httemplate/images/cvv2_amex.png differ
diff --git a/httemplate/images/menu-left-example.png b/httemplate/images/menu-left-example.png
new file mode 100644 (file)
index 0000000..375725c
Binary files /dev/null and b/httemplate/images/menu-left-example.png differ
diff --git a/httemplate/images/menu-top-example.png b/httemplate/images/menu-top-example.png
new file mode 100644 (file)
index 0000000..bd9bea8
Binary files /dev/null and b/httemplate/images/menu-top-example.png differ
diff --git a/httemplate/images/progressbar-empty.png b/httemplate/images/progressbar-empty.png
new file mode 100644 (file)
index 0000000..318219c
Binary files /dev/null and b/httemplate/images/progressbar-empty.png differ
diff --git a/httemplate/images/progressbar-full.png b/httemplate/images/progressbar-full.png
new file mode 100644 (file)
index 0000000..863d8e1
Binary files /dev/null and b/httemplate/images/progressbar-full.png differ
diff --git a/httemplate/images/red_telephone_mimooh_01.png b/httemplate/images/red_telephone_mimooh_01.png
new file mode 100644 (file)
index 0000000..2212ff0
Binary files /dev/null and b/httemplate/images/red_telephone_mimooh_01.png differ
diff --git a/httemplate/images/small-logo.png b/httemplate/images/small-logo.png
new file mode 100644 (file)
index 0000000..1e415e6
Binary files /dev/null and b/httemplate/images/small-logo.png differ
diff --git a/httemplate/index.html b/httemplate/index.html
new file mode 100644 (file)
index 0000000..60ab26f
--- /dev/null
@@ -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 %>"><% $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/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
new file mode 100644 (file)
index 0000000..8488939
--- /dev/null
@@ -0,0 +1,32 @@
+<% include('/elements/header.html', 'Quick payment entry') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.submit.disabled=true;">
+
+<!-- <B>Batch</B> <INPUT TYPE="text" NAME="paybatch"><BR><BR> -->
+
+<% include( "elements/customer-table.html",
+            header => [ '', 'Amount', 'Check #', '' ],
+            fields => [ sub {'$'}, 'paid', 'payinfo', 'error', ],
+            types  => [ 'immutable', '', '', 'immutable', ],
+            sizes  => [ 0, 8, 10, 0, ],
+            param  => { () },
+          ) %>
+
+<!-- <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 (executable)
index 0000000..3c3c48c
--- /dev/null
@@ -0,0 +1,45 @@
+%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(
+#                          'time'=>$time
+                         );
+
+unless ( $error ) {
+  $error = $cust_main->apply_payments_and_credits
+           || $cust_main->collect(
+                                  #'invoice-time'=>$time,
+                                  #'batch_card'=> 'yes',
+                                  #'batch_card'=> 'no',
+                                  #'report_badcard'=> 'yes',
+                                  #'retry_card' => 'yes',
+
+                                  'retry' => '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 (executable)
index 0000000..9334985
--- /dev/null
@@ -0,0 +1,59 @@
+<% 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>
+
+<INPUT TYPE="hidden" NAME="query" VALUE="<% $cgi->keywords %>">
+%  for my $param (qw(agentnum magic status classnum pkgpart)) {
+<INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $cgi->param($param) %>">
+%  }
+%
+% foreach my $field (qw( setup last_bill bill adjourn susp expire cancel )) {
+% 
+  <INPUT TYPE="hidden" NAME="<% $field %>begin" VALUE="<% $cgi->param("${field}.begin") %>">
+  <INPUT TYPE="hidden" NAME="<% $field %>beginning" VALUE="<% $cgi->param("${field}beginning") %>">
+  <INPUT TYPE="hidden" NAME="<% $field %>end" VALUE="<% $cgi->param("${field}.end") %>">
+  <INPUT TYPE="hidden" NAME="<% $field %>ending" VALUE="<% $cgi->param("${field}.ending") %>">
+% }
+
+<% 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]->pkgpart. ': '.
+                                               $_[0]->pkg.     ' - '.
+                                               $_[0]->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/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi
new file mode 100755 (executable)
index 0000000..4919c66
--- /dev/null
@@ -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 (file)
index 0000000..bb4e190
--- /dev/null
@@ -0,0 +1,74 @@
+<% 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',
+             #XXX these need to be sticky on errors too...
+             #'curr_value'     => '',
+             'control_button' => 'document.cust_cancel_popup.submit',
+          )
+%>
+
+</TABLE>
+
+<BR>
+<P ALIGN="CENTER">
+<INPUT TYPE="submit" NAME="submit" VALUE="Cancel customer" disabled='true'> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<INPUT TYPE="BUTTON" VALUE="Don't cancel" onClick="parent.cClick();"> 
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my($custnum, $reasonnum, $submit, $cust_main, $curuser, $class); 
+if ( $cgi->param('error') ) {
+  $custnum        = $cgi->param('custnum');
+  $reasonnum     = $cgi->param('reasonnum');
+} else {
+  my( $query ) = $cgi->keywords;
+  if ( $query =~ /^(\d+)$/ ) {
+    $custnum  = $1;
+  } else {
+    die "illegal query ". $cgi->keywords;
+  }
+}
+
+$curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('Cancel customer');
+
+$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 (executable)
index 0000000..8dffba7
--- /dev/null
@@ -0,0 +1,109 @@
+%# 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) . " $pkgnum: " .$part_pkg->pkg. ' - ' .$part_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:   "%m/%d/%Y",
+    button:     "expire_button",
+    align:      "BR"
+  });
+</SCRIPT>
+%}
+%
+
+<% include('/elements/tr-select-reason.html',
+             'field'          => 'reasonnum',
+             'reason_class'   => $class,
+             'curr_value'     => $reasonnum,
+             'control_button' => 'document.sc_popup.submit',
+          )
+%>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="<% $submit %>" disabled='true'>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $date = time2str("%m/%d/%Y", 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 (executable)
index 0000000..2094494
--- /dev/null
@@ -0,0 +1,120 @@
+<% include('/elements/header.html', 'Domain Catchall Edit') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/catchall.cgi" METHOD=POST>
+
+<PRE>
+
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum |h %>">
+Service #<FONT SIZE=+1><B><% $svcnum ? $svcnum : ' (NEW)' |h %></B></FONT>
+
+<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>
+
+</PRE>
+
+<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 (file)
index 0000000..60f619e
--- /dev/null
@@ -0,0 +1,22 @@
+<% include("/elements/header.html",'Call Detail Record Import') %>
+<FORM ACTION="process/cdr-import.html" METHOD="POST" ENCTYPE="multipart/form-data">
+Import a CSV file containing Call Detail Records (CDRs).<BR><BR>
+CDR Format: <SELECT NAME="format">
+<OPTION VALUE="asterisk">Asterisk</OPTION>
+<OPTION VALUE="unitel">Unitel/RSLCOM</OPTION>
+<OPTION VALUE="simple">Simple</OPTION>
+</SELECT><BR><BR>
+
+Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR>
+
+<INPUT TYPE="submit" VALUE="Upload">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+</%init>
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
new file mode 100755 (executable)
index 0000000..7c88876
--- /dev/null
@@ -0,0 +1,69 @@
+<% include('/elements/header-popup.html', "Change Package") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p %>edit/process/cust_pkg.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+<INPUT TYPE="hidden" NAME="remove_pkg" VALUE="<% $pkgnum %>">
+
+<% ntable('#cccccc') %>
+
+  <TR>
+    <TD>Current package:&nbsp;</TD>
+    <TD>
+      <B><% $part_pkg->pkgpart %>: <% $part_pkg->pkg %> - <% $part_pkg->comment %></B>
+    </TD>
+  </TR>
+  
+  <TR>
+    <TD>New package: </TD>
+    <TD><% include('/elements/select-cust-part_pkg.html',
+                     'cust_main'    => $cust_main,
+                     'element_name' => 'new_pkgpart',
+                     'extra_sql'    => ' AND pkgpart != '. $cust_pkg->pkgpart,
+                     'curr_value'   => ( $cgi->param('error')
+                                           ? scalar($cgi->param('new_pkgpart'))
+                                           : ''
+                                       ),
+                  )
+        %>
+    </TD>
+  </TR>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Change package">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Change customer package');
+
+my $pkgnum;
+if ( $cgi->param('error') ) {
+  $pkgnum = ($cgi->param('remove_pkg'))[0];
+} else {
+  $pkgnum = $cgi->param('pkgnum');
+}
+$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum";
+$pkgnum = $1;
+
+my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } )
+  or die "unknown pkgnum $pkgnum";
+my $custnum = $cust_pkg->custnum;
+
+my $conf = new FS::Conf;
+
+my $cust_main = $cust_pkg->cust_main
+  or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+         " ( pkgnum ". cust_pkg->pkgnum. ")";
+my $agent = $cust_main->agent;
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+</%init>
diff --git a/httemplate/misc/counties.cgi b/httemplate/misc/counties.cgi
new file mode 100644 (file)
index 0000000..c022a27
--- /dev/null
@@ -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_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi
new file mode 100755 (executable)
index 0000000..009a7d4
--- /dev/null
@@ -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 (file)
index 0000000..84da386
--- /dev/null
@@ -0,0 +1,109 @@
+<% include("/elements/header.html",'Batch Customer Import') %>
+
+Import a CSV file containing customer records.
+<BR><BR>
+
+<FORM ACTION="process/cust_main-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
+      <OPTION VALUE="extended-plus_company">Extended plus company
+    </SELECT>
+  </TD>
+</TR>
+
+<TR>
+  <TH ALIGN="right">CSV filename</TH>
+  <TD><INPUT TYPE="file" NAME="csvfile"></TD>
+</TR>
+% #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. ' - '. $part_pkg->comment %></OPTION>
+% } 
+
+    </SELECT>
+  </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>cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments</i>
+<BR><BR> -->
+
+<b>Extended</b> file format is CSV, with 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<%$req%>, paycvv, paydate<%$req%>, invoicing_list, pkgpart, username, _password</i>
+<BR><BR>
+
+<b>Extended plus company</b> file format is CSV, with 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<%$req%>, paycvv, paydate<%$req%>, invoicing_list, pkgpart, username, _password</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.
+</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');
+
+</%init>
diff --git a/httemplate/misc/cust_main-import_charges.cgi b/httemplate/misc/cust_main-import_charges.cgi
new file mode 100644 (file)
index 0000000..3801929
--- /dev/null
@@ -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 (file)
index 0000000..b93c5c1
--- /dev/null
@@ -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 (file)
index 0000000..d8fefa7
--- /dev/null
@@ -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 (file)
index 0000000..849a25b
--- /dev/null
@@ -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/delete-agent_payment_gateway.cgi b/httemplate/misc/delete-agent_payment_gateway.cgi
new file mode 100644 (file)
index 0000000..20a202e
--- /dev/null
@@ -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-cust_credit.cgi b/httemplate/misc/delete-cust_credit.cgi
new file mode 100755 (executable)
index 0000000..03eb472
--- /dev/null
@@ -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 (executable)
index 0000000..38e7e4b
--- /dev/null
@@ -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_refund.cgi b/httemplate/misc/delete-cust_refund.cgi
new file mode 100755 (executable)
index 0000000..983a79d
--- /dev/null
@@ -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 (executable)
index 0000000..17b7bda
--- /dev/null
@@ -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 (executable)
index 0000000..08eedde
--- /dev/null
@@ -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-part_export.cgi b/httemplate/misc/delete-part_export.cgi
new file mode 100755 (executable)
index 0000000..52404e0
--- /dev/null
@@ -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/disable-payment_gateway.cgi b/httemplate/misc/disable-payment_gateway.cgi
new file mode 100644 (file)
index 0000000..13e1f92
--- /dev/null
@@ -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 (file)
index 0000000..57905da
--- /dev/null
@@ -0,0 +1,213 @@
+%if ($format eq "BoM") {
+%
+%  my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) =
+%    $conf->config("batchconfig-$format");
+%  
+<% sprintf( "A%10s%04u%06u%05u%54s\n",$origid,$pay_batch->batchnum,$jdate,$datacenter,"").
+        sprintf( "XD%03u%06u%-15s%-30s%09u%-12s   \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct )
+  %>
+%
+%}elsif ($format eq "PAP"){
+%
+%  my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) =
+%    $conf->config("batchconfig-$format");
+%  
+<% sprintf( "H%10sD%3s%06u%-15s%09u%-12s%04u%19s\n",$origid,$typecode,$cdate,$shortname,$mybank,$myacct,$pay_batch->batchnum,"") %>
+%
+%
+%}elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){
+%#  1;
+%}elsif ($format eq "csv-chase_canada-E-xactBatch"){
+%
+%  my($origid) = $conf->config("batchconfig-$format");
+<% sprintf( '$$E-xactBatchFileV1.0$$%s:%03u$$%s',$sdate,$pay_batch->batchnum, $origid)
+  %>
+%
+%}elsif ($format eq "ach-spiritone"){
+%#  1;
+%}else{
+%  die "Unknown format for batch in batchconfig. \n";
+%}
+%
+%
+%for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum }
+%                           qsearch('cust_pay_batch',
+%                            {'batchnum'=>$pay_batch->batchnum} )
+%) {
+%
+%  $cust_pay_batch->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$/;
+%  my $exp = "$mon$y";
+%
+%  if ( $first_download ) {
+%    my $balance = $cust_pay_batch->cust_main->balance;
+%    if ( $balance <= 0 ) {
+%      my $error = $cust_pay_batch->delete;
+%      if ( $error ) {
+%        $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+%        die $error;
+%      }
+%      next;
+%    } elsif ( $balance < $cust_pay_batch->amount ) {
+%      $cust_pay_batch->amount($balance);
+%      my $error = $cust_pay_batch->replace;
+%      if ( $error ) {
+%        $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+%        die $error;
+%      }
+%    #} elsif ( $balance > $cust_pay_batch->amount ) {
+%    } 
+%  }
+%
+%  $batchcount++;
+%  $batchtotal += $cust_pay_batch->amount;
+%  
+%  if ($format eq "BoM") {
+%
+%    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) %>
+%
+%
+%  } elsif ($format eq "PAP"){
+%
+%    my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo );
+%    
+<% sprintf( "D%-23s%06u%-19s%09u%-12s%010.0f\n",$cust_pay_batch->payname,$cdate,$cust_pay_batch->paybatchnum,$aba,$account,$cust_pay_batch->amount*100) %>
+%
+%
+%  } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch") {
+%
+%    
+,,,,<% $cust_pay_batch->payinfo %>,<% $exp %>,<% $cust_pay_batch->amount %>,<% $cust_pay_batch->paybatchnum %>
+%
+%
+%  } elsif ($format eq "csv-chase_canada-E-xactBatch"){
+%
+%  my $payname=$cust_pay_batch->payname; $payname =~ tr/",/  /; #payinfo too? :P
+<% $cust_pay_batch->paybatchnum %>,<% $cust_pay_batch->custnum %>,<% $cust_pay_batch->invnum %>,"<% $payname %>",00,<% $cust_pay_batch->payinfo %>,<% $cust_pay_batch->amount %>,<% $exp %>,,
+%
+%
+%  }elsif ($format eq "ach-spiritone"){
+%
+%  my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo );
+%  my $payname=$cust_pay_batch->first. " ". $cust_pay_batch->last;
+%  $payname =~ tr/",/  /;                                        #payinfo too?
+%  my $batchline = qq!"$payname","!.$cust_pay_batch->paybatchnum.
+%                  qq!","$aba","$account","27","!.$cust_pay_batch->amount.
+%                  qq!","27","0.00"!;
+%  push @batchlines, $batchline;
+<% $batchline %>
+%
+%  } else {
+%    die "I'm already dead, but you did not know that.\n";
+%  }
+%
+%}
+%
+%if ($format eq "BoM") {
+%
+%  
+<% sprintf( "YD%08u%014.0f%56s\n",$batchcount,$batchtotal*100,"" ).
+        sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %>
+%
+%
+%} elsif ($format eq "PAP"){
+%
+%  
+<% sprintf( "T%08u%014.0f%57s\n",$batchcount,$batchtotal*100,"" ) %>
+%
+%
+%} elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){
+%  #1;
+%} elsif ($format eq "csv-chase_canada-E-xactBatch"){
+%  #1;
+%} elsif ($format eq "ach-spiritone"){
+%  #1;
+%} else {
+%  die "I'm already dead (again), but you did not know that.\n";
+%}
+%
+<%init>
+
+my $conf=new FS::Conf;
+
+#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+http_header('Content-Type' => 'text/plain' );
+
+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;
+} else {
+  $format = $conf->config('batch-default_format');
+}
+
+my $autopost;
+if ( $format eq 'ach-spiritone' ) {
+  $autopost = 1;
+}else{
+  $autopost = 0;
+}
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+my $dbh = dbh;
+
+my $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'O'} );
+my $first_download = 1;
+unless ($pay_batch) {
+  $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'I'} )
+    if $FS::CurrentUser::CurrentUser->access_right('Reprocess batches');
+  $first_download = 0;
+}
+die "No pending batch. \n" unless $pay_batch;
+
+my $error = $pay_batch->set_status('I');
+die "error updating batch status: $error\n" if $error;
+
+my $batchtotal=0;
+my $batchcount=0;
+
+my (@date)=localtime($pay_batch->download);
+my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7] + 1);
+my $cdate = sprintf("%02d", $date[3]).sprintf("%02d", $date[4] + 1).
+            sprintf("%02d", $date[5] % 100);
+my $sdate = sprintf("%02d", $date[5] % 100).'/'.sprintf("%02d", $date[4] + 1).
+            '/'.sprintf("%02d", $date[3]);
+
+my @batchlines = ();
+</%init>
+<%cleanup>
+if ($autopost) {
+  my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+  my $fh = new File::Temp(
+    TEMPLATE => 'paybatch.'. $batchnum .'.XXXXXXXX',
+    DIR      => $dir,
+  ) or die "can't open temp file: $!\n";
+
+  print $fh map{ "$_\n" } @batchlines;
+  seek $fh, 0, 0;
+
+  $error = $pay_batch->import_results( 'filehandle' => $fh,
+                                       'format'     => $format,
+                                     );
+  die $error if $error;
+}
+
+$dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+</%cleanup>
diff --git a/httemplate/misc/dump.cgi b/httemplate/misc/dump.cgi
new file mode 100644 (file)
index 0000000..3b60b20
--- /dev/null
@@ -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/elements/customer-table.html b/httemplate/misc/elements/customer-table.html
new file mode 100644 (file)
index 0000000..fc298b0
--- /dev/null
@@ -0,0 +1,387 @@
+%  # options example...  
+%  #
+%  # #listrefs...
+%  # 'header'      => [ '#', 'Item' ],
+%  # 'fields'      => [
+%  #                    'column',
+%  #                    sub { my ($row,$param) = @_; $param->{"column$row"}; },
+%  #                  ],
+%  # 'sizes'       => [],                         # sizes ignored for immutable
+%  # 'types'       => ['immutable', ''],          # immutable or ''/text
+%  # 'param'       => { column0 => 1 },           # preset column of row 0 to 1
+%  # 
+
+<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 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 $size = $sizes->[$col] || 10;
+      <TD>
+%     if (! $types->[$col] || $types->[$col] eq 'text') {
+        <INPUT TYPE="text" NAME="<% $name %>" SIZE="<% $size %>" VALUE="<% $value %>" >
+%     } elsif ($types->[$col] eq 'immutable') {
+        <% $value %>
+        <INPUT TYPE="hidden" NAME="<% $name %>" VALUE="<% $value %>" >
+%     } else {
+        Cannot represent unknown type: <% $types->[$col] %>
+%     }
+      </TD>
+%     $col++;
+%   }
+
+    </TR>
+% } 
+
+
+</TABLE>
+
+<% 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} %>rownum = <% $row %>;
+
+  function <% $opt{prefix} %>addRow() {
+
+    var table = document.getElementById('<% $opt{prefix} %>OneTrueTable');
+    var tablebody = table.getElementsByTagName('tbody').item(0);
+
+    var row = document.createElement('TR');
+
+    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.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');
+
+%     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('size', <% $sizes->[$col] || 10 %>);
+%     if ($types->[$col] eq 'immutable') {
+        my_input.setAttribute('type', 'hidden');
+%     }
+      my_cell.appendChild(my_input);
+
+    row.appendChild(my_cell);
+
+%     $col++;
+%   }
+
+    tablebody.appendChild(row);
+
+    <% $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{'sizes'} ? [ @{$opt{'sizes'}} ] : [];
+
+my $param = $opt{param};
+$param = $cgi->Vars if $cgi->param('error');
+
+</%init>
diff --git a/httemplate/misc/email-invoice.cgi b/httemplate/misc/email-invoice.cgi
new file mode 100755 (executable)
index 0000000..269722f
--- /dev/null
@@ -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 (file)
index 0000000..e7a0e77
--- /dev/null
@@ -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 (file)
index 0000000..d65fe17
--- /dev/null
@@ -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 (file)
index 0000000..78ca0f6
--- /dev/null
@@ -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/fax-invoice.cgi b/httemplate/misc/fax-invoice.cgi
new file mode 100755 (executable)
index 0000000..e2e6db0
--- /dev/null
@@ -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($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 (file)
index 0000000..39cba07
--- /dev/null
@@ -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 (file)
index 0000000..05420ee
--- /dev/null
@@ -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 (file)
index 0000000..a843523
--- /dev/null
@@ -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/inventory_item-import.html b/httemplate/misc/inventory_item-import.html
new file mode 100644 (file)
index 0000000..f3636c0
--- /dev/null
@@ -0,0 +1,23 @@
+<% include("/elements/header.html", $inventory_class->classname. 's') %>
+
+<FORM ACTION="process/inventory_item-import.html" METHOD="POST" ENCTYPE="multipart/form-data">
+<INPUT TYPE="hidden" NAME="classnum" VALUE="<% $classnum %>">
+Import a file containing <% $inventory_class->classname %>s, one per line.<BR><BR>
+
+Filename: <INPUT TYPE="file" NAME="filename"><BR><BR>
+
+<INPUT TYPE="submit" VALUE="Upload">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+$cgi->param =~ /^(\d+)$/ or errorpage("illegal classnum");
+my $classnum = $1;
+my $inventory_class = qsearchs('inventory_class', { 'classnum' => $classnum } );
+
+</%init>
diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi
new file mode 100755 (executable)
index 0000000..748eaa1
--- /dev/null
@@ -0,0 +1,84 @@
+<% 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',
+);
+
+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/meta-import.cgi b/httemplate/misc/meta-import.cgi
new file mode 100644 (file)
index 0000000..5b3470c
--- /dev/null
@@ -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. ' - '. $part_pkg->comment %></OPTION>
+% } 
+
+</SELECT><BR><BR>
+
+  <table>
+    <tr>
+      <td align="right">DBI data source: </td>
+      <td><INPUT TYPE="text" NAME="data_source"></td>
+    </tr>
+    <tr>
+      <td align="right">DBI username: </td>
+      <td><INPUT TYPE="text" NAME="username"></td>
+    </tr>
+    <tr>
+      <td align="right">DBI password: </td>
+      <td><INPUT TYPE="text" NAME="password"></td>
+    </tr>
+  </table>
+  <INPUT TYPE="submit" VALUE="Import">
+
+  </FORM>
+
+<% 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 (file)
index 0000000..05cd99c
--- /dev/null
@@ -0,0 +1,62 @@
+<% include('/elements/header-popup.html', 'Order new package' ) %>
+
+<SCRIPT TYPE="text/javascript">
+function enable_order_pkg () {
+  if ( document.OrderPkgForm.pkgpart.selectedIndex > 0 ) {
+    document.OrderPkgForm.submit.disabled = false;
+  } else {
+    document.OrderPkgForm.submit.disabled = true;
+  }
+}
+</SCRIPT>
+
+<FORM NAME="OrderPkgForm" ACTION="<% $p %>edit/process/quick-cust_pkg.cgi" METHOD="POST">
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cust_main->custnum %>">
+
+<% ntable("#cccccc", 2) %>
+<TR>
+  <TH ALIGN="right">Package</TH>
+  <TD>
+    <% include('/elements/select-cust-part_pkg.html',
+                 'cust_main' => $cust_main,
+                 'onchange'  => 'enable_order_pkg',
+              )
+    %>
+  </TD>
+</TR>
+
+% 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'),
+            )
+  %>
+% }
+
+</TABLE>
+
+<BR>
+<INPUT NAME="submit" TYPE="submit" VALUE="Order Package" disabled>
+
+</FORM>
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Order customer package');
+
+my $conf = new FS::Conf;
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $custnum = $1;
+my $cust_main = qsearchs({
+  'table'     => 'cust_main',
+  'hashref'   => { 'custnum' => $custnum },
+  'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+
+</%init>
diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi
new file mode 100644 (file)
index 0000000..f99f2f0
--- /dev/null
@@ -0,0 +1,254 @@
+<% 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 %>">
+
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript">
+function OLiframeContent(src, width, height, name) {
+  return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'
+   +(name?' name="'+name+'" id="'+name+'"':'')+' scrolling="auto">'
+   +'<div>[iframe not supported]</div></iframe>');
+}
+</SCRIPT>
+% #include( '/elements/table.html', '#cccccc' ) 
+
+<% ntable('#cccccc') %>
+  <TR>
+    <TD ALIGN="right">Payment amount</TD>
+    <TD>
+      <TABLE><TR><TD BGCOLOR="#ffffff">
+        $<INPUT TYPE="text" NAME="amount" SIZE=8 VALUE="<% $balance > 0 ? sprintf("%.2f", $balance) : '' %>">
+      </TD></TR></TABLE>
+    </TD>
+  </TR>
+% if ( $payby eq 'CARD' ) {
+%     my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' );
+%     my $payname = $cust_main->first. ' '. $cust_main->getfield('last');
+%     my $address1 = $cust_main->address1;
+%     my $address2 = $cust_main->address2;
+%     my $city     = $cust_main->city;
+%     my $state    = $cust_main->state;
+%     my $zip     = $cust_main->zip;
+%     if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+%       $payinfo = $cust_main->paymask;
+%       $paycvv = $cust_main->paycvv;
+%       ( $month, $year ) = $cust_main->paydate_monthyear;
+%       $payname = $cust_main->payname if $cust_main->payname;
+%     }
+%
+
+  <TR>
+    <TD ALIGN="right">Card&nbsp;number</TD>
+    <TD>
+      <TABLE>
+        <TR>
+          <TD>
+            <INPUT TYPE="text" NAME="payinfo" SIZE=20 MAXLENGTH=19 VALUE="<%$payinfo%>"> </TD>
+          <TD>Exp.</TD>
+          <TD>
+            <SELECT NAME="month">
+% for ( ( map "0$_", 1 .. 9 ), 10 .. 12 ) { 
+
+                <OPTION<% $_ == $month ? ' SELECTED' : '' %>><% $_ %>
+% } 
+
+            </SELECT>
+          </TD>
+          <TD> / </TD>
+          <TD>
+            <SELECT NAME="year">
+% my @a = localtime; for ( $a[5]+1900 .. $a[5]+1915 ) { 
+
+                <OPTION<% $_ == $year ? ' SELECTED' : '' %>><% $_ %>
+% } 
+
+            </SELECT>
+          </TD>
+        </TR>
+      </TABLE>
+    </TD>
+  </TR>
+  <TR>
+    <TD ALIGN="right">CVV2</TD>
+    <TD><INPUT TYPE="text" NAME="paycvv" VALUE="<% $paycvv %>" SIZE=4 MAXLENGTH=4>
+        (<A HREF="javascript: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>
+    <TD ALIGN="right">Exact&nbsp;name&nbsp;on&nbsp;card</TD>
+    <TD><INPUT TYPE="text" SIZE=32 MAXLENGTH=80 NAME="payname" VALUE="<%$payname%>"></TD>
+  </TR><TR>
+    <TD ALIGN="right">Card&nbsp;billing&nbsp;address</TD>
+    <TD>
+      <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address1" VALUE="<%$address1%>">
+    </TD>
+  </TR><TR>
+    <TD ALIGN="right">Address&nbsp;line&nbsp;2</TD>
+    <TD>
+      <INPUT TYPE="text" SIZE=40 MAXLENGTH=80 NAME="address2" VALUE="<%$address2%>">
+    </TD>
+  </TR><TR>
+    <TD ALIGN="right">City</TD>
+    <TD>
+      <TABLE>
+        <TR>
+          <TD>
+            <INPUT TYPE="text" NAME="city" SIZE="12" MAXLENGTH=80 VALUE="<%$city%>">
+          </TD>
+          <TD>State</TD>
+          <TD>
+            <SELECT NAME="state">
+% for ( @states ) { 
+
+                <OPTION<% $_ eq $state ? ' SELECTED' : '' %>><% $_ %> 
+% } 
+
+            </SELECT>
+          </TD>
+          <TD>Zip</TD>
+          <TD>
+            <INPUT TYPE="text" NAME="zip" SIZE=11 MAXLENGTH=10 VALUE="<%$zip%>">
+          </TD>
+        </TR>
+      </TABLE>
+    </TD>
+  </TR>
+% } elsif ( $payby eq 'CHEK' ) {
+%     my( $payinfo1, $payinfo2, $payname, $ss, $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>
+  <TR>
+    <TD ALIGN="right">Bank&nbsp;state</TD>
+    <TD><% include('../edit/cust_main/select-state.html', #meh 
+                   'empty'   => '(choose)',
+                   'state'   => $paystate,
+                   'country' => $cust_main->country,
+                   'prefix'  => 'pay',
+                  ) %></TD>
+  </TR>
+  <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>
+  <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('../edit/cust_main/select-state.html', #meh 
+                   'empty'   => '(choose)',
+                   'state'   => $stateid_state,
+                   'country' => $cust_main->country,
+                   'prefix'  => 'stateid_',
+                  ) %></TD>
+  </TR>
+% } 
+
+
+<TR>
+  <TD COLSPAN=2>
+    <INPUT TYPE="checkbox" CHECKED NAME="save" VALUE="1">
+    Remember this information
+  </TD>
+</TR><TR>
+% if ($conf->exists("batch-enable")) {
+  <TD COLSPAN=2>
+    <INPUT TYPE="checkbox" <% ( $conf->exists("paymentforcedtobatch") && $payby eq 'CHEK' ) ? 'CHECKED DISABLED' : '' %> NAME="batch" VALUE="1">
+    Add to current batch
+% if ($conf->exists("paymentforcedtobatch") && $payby eq 'CHEK' ) {
+    <INPUT TYPE="hidden" NAME="batch" VALUE="1">
+% }
+  </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 = '';
+
+#false laziness w/selfservice make_payment.html shortcut for one-country
+my $conf = new FS::Conf;
+my %states = map { $_->state => 1 }
+               qsearch('cust_main_county', {
+                 'country' => $conf->config('countrydefault') || 'US'
+               } );
+my @states = sort { $a cmp $b } keys %states;
+
+my $payunique = "webui-payment-". time. "-$$-". rand() * 2**32;
+
+</%init>
+
+
diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi
new file mode 100755 (executable)
index 0000000..aeef687
--- /dev/null
@@ -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 (file)
index 0000000..8d83d3d
--- /dev/null
@@ -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 (file)
index 0000000..c974d5f
--- /dev/null
@@ -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 (file)
index 0000000..f859f6d
--- /dev/null
@@ -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 (file)
index 0000000..058a225
--- /dev/null
@@ -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 (executable)
index 0000000..d2ab4bf
--- /dev/null
@@ -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_sql(\%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/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html
new file mode 100755 (executable)
index 0000000..d265c18
--- /dev/null
@@ -0,0 +1,79 @@
+<% 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');
+  str2time($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date";
+  $date = $1;
+}
+
+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;
+if ($method eq 'expire' || $method eq 'adjourn'){
+  my %hash = $cust_pkg->hash;
+  $hash{$method} = $date;
+  my $new = new FS::cust_pkg \%hash;
+  $error = $new->replace($cust_pkg, 'reason' => $reasonnum);
+} else {
+  $error = $cust_pkg->$method( 'reason' => $reasonnum );
+}
+
+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 (executable)
index 0000000..0dda2ea
--- /dev/null
@@ -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 (file)
index 0000000..4848fa3
--- /dev/null
@@ -0,0 +1,22 @@
+% if ( $error ) {
+%   errorpage($error);
+% } else {
+    <% include("/elements/header.html",'Import successful') %>
+    <!-- XXX redirect to batch search like the payment entry... -->
+    <% include("/elements/footer.html",'Import successful') %> 
+% }
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $fh = $cgi->upload('csvfile');
+
+my $error = defined($fh)
+  ? FS::cdr::batch_import( {
+      'filehandle' => $fh,
+      'format'     => $cgi->param('format'),
+    } )
+  : 'No file';
+
+</%init>
diff --git a/httemplate/misc/process/cust_main-import.cgi b/httemplate/misc/process/cust_main-import.cgi
new file mode 100644 (file)
index 0000000..aa8cd52
--- /dev/null
@@ -0,0 +1,28 @@
+% 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_import( {
+      filehandle => $fh,
+      agentnum   => scalar($cgi->param('agentnum')),
+      refnum     => scalar($cgi->param('refnum')),
+      pkgpart    => scalar($cgi->param('pkgpart')),
+      #'fields'    => [qw( cust_pkg.setup dayphone first last address1 address2
+      #                   city state zip comments                          )],
+      'format'   => scalar($cgi->param('format')),
+    } )
+  : 'No file';
+
+</%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 (file)
index 0000000..3ca6894
--- /dev/null
@@ -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 (file)
index 0000000..6aa8b1d
--- /dev/null
@@ -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 (file)
index 0000000..d4ff226
--- /dev/null
@@ -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/delete-customer.cgi b/httemplate/misc/process/delete-customer.cgi
new file mode 100755 (executable)
index 0000000..d509a5e
--- /dev/null
@@ -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/inventory_item-import.html b/httemplate/misc/process/inventory_item-import.html
new file mode 100644 (file)
index 0000000..3aae202
--- /dev/null
@@ -0,0 +1,22 @@
+% if ( $error ) {
+%   errorpage($error);
+% } else {
+    <% include("/elements/header.html",'Import successful') %>
+    <!-- XXX redirect to batch search like the payment entry... -->
+    <% include("/elements/footer.html",'Import successful') %> 
+%  }
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Import');
+
+my $fh = $cgi->upload('filename');
+
+my $error = defined($fh)
+  ? FS::inventory_item::batch_import( {
+      'filehandle' => $fh,
+      'classnum'   => $cgi->param('classnum'),
+    } )
+  : 'No file';
+
+</%init>
diff --git a/httemplate/misc/process/link.cgi b/httemplate/misc/process/link.cgi
new file mode 100755 (executable)
index 0000000..df15dca
--- /dev/null
@@ -0,0 +1,72 @@
+%unless ($error) {
+%  #no errors, so let's view this customer.
+%  my $custnum = $new->cust_pkg->custnum;
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum#cust_pkg$pkgnum" ) %>
+%} else {
+% errorpage($error);
+%}
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->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 (file)
index 0000000..68ae49c
--- /dev/null
@@ -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/payment.cgi b/httemplate/misc/process/payment.cgi
new file mode 100644 (file)
index 0000000..2baca1e
--- /dev/null
@@ -0,0 +1,183 @@
+% 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;
+
+$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 state zip ) ],
+  '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 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;
+
+  $cust_main->apply_payments;
+
+}
+
+if ( $cgi->param('save') ) {
+  my $new = new FS::cust_main { $cust_main->hash };
+  if ( $payby eq 'CARD' ) { 
+    $new->set( 'payby' => ( $cgi->param('auto') ? 'CARD' : 'DCRD' ) );
+  } elsif ( $payby eq 'CHEK' ) {
+    $new->set( 'payby' => ( $cgi->param('auto') ? 'CHEK' : 'DCHK' ) );
+  } else {
+    die "unknown payby $payby";
+  }
+  $new->set( 'payinfo' => $payinfo );
+  $new->set( 'paydate' => "$year-$month-01" );
+  $new->set( 'payname' => $payname );
+
+  #false laziness w/FS:;cust_main::realtime_bop - check both to make sure
+  # working correctly
+  my $conf = new FS::Conf;
+  if ( $payby eq 'CARD' &&
+       grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save') ) {
+    $new->set( 'paycvv' => $paycvv );
+  } else {
+    $new->set( 'paycvv' => '');
+  }
+
+  $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
+
+  my $error = $new->replace($cust_main);
+  errorpage("payment processed successfully, but error saving info: $error")
+    if $error;
+  $cust_main = $new;
+}
+
+#success!
+
+</%init>
diff --git a/httemplate/misc/process/recharge_svc.html b/httemplate/misc/process/recharge_svc.html
new file mode 100755 (executable)
index 0000000..147b953
--- /dev/null
@@ -0,0 +1,92 @@
+%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($_) }
+%      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);
+%    }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) {
+%  $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;
+
+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;
+
+</%init>
diff --git a/httemplate/misc/process/timeworked.html b/httemplate/misc/process/timeworked.html
new file mode 100644 (file)
index 0000000..c589d76
--- /dev/null
@@ -0,0 +1,57 @@
+% if ($error) {
+<% $cgi->redirect(popurl(2). "timeworked.html?". $cgi->query_string) %>
+% } else {
+<% $cgi->redirect(popurl(3). "search/timeworked.html") %>
+% }
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Time queue');
+
+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'        => $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 (file)
index 0000000..5dee29b
--- /dev/null
@@ -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/recharge_svc.html b/httemplate/misc/recharge_svc.html
new file mode 100755 (executable)
index 0000000..2302f3f
--- /dev/null
@@ -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/states.cgi b/httemplate/misc/states.cgi
new file mode 100644 (file)
index 0000000..cf2b46e
--- /dev/null
@@ -0,0 +1,7 @@
+%
+%
+%  my $country = $cgi->param('arg');
+%  my @output = states_hash($country);
+%
+%
+[ <% join(', ', map { qq("$_") } @output) %> ]
diff --git a/httemplate/misc/svc_acct-domains.cgi b/httemplate/misc/svc_acct-domains.cgi
new file mode 100644 (file)
index 0000000..5734574
--- /dev/null
@@ -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/timeworked.html b/httemplate/misc/timeworked.html
new file mode 100755 (executable)
index 0000000..db4b64c
--- /dev/null
@@ -0,0 +1,135 @@
+<% 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="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/unapply-cust_credit.cgi b/httemplate/misc/unapply-cust_credit.cgi
new file mode 100755 (executable)
index 0000000..ed739ac
--- /dev/null
@@ -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 (executable)
index 0000000..8cdac18
--- /dev/null
@@ -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/unprovision.cgi b/httemplate/misc/unprovision.cgi
new file mode 100755 (executable)
index 0000000..4ab15fd
--- /dev/null
@@ -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 (executable)
index 0000000..b350693
--- /dev/null
@@ -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 (executable)
index 0000000..91fe1c2
--- /dev/null
@@ -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 (file)
index 0000000..d1a84fd
--- /dev/null
@@ -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 (executable)
index 0000000..7b484e9
--- /dev/null
@@ -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 (file)
index 0000000..35d0ecc
--- /dev/null
@@ -0,0 +1,27 @@
+<% include("/elements/header.html","Whois $domain", menubar(
+  ( $custnum
+    ? ( "View this customer (#$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 $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-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi
new file mode 100644 (file)
index 0000000..67512fa
--- /dev/null
@@ -0,0 +1,22 @@
+%
+%   my $sub = $cgi->param('sub');
+% 
+%   if ( $sub eq 'custnum_search' ) {
+% 
+%     my $custnum = $cgi->param('arg');
+%     my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+%
+%     
+"<% $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) %>
+% } 
+
+
+
diff --git a/httemplate/misc/xmlrpc.cgi b/httemplate/misc/xmlrpc.cgi
new file mode 100644 (file)
index 0000000..1d0383f
--- /dev/null
@@ -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 (file)
index 0000000..25f30e9
--- /dev/null
@@ -0,0 +1,57 @@
+% 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;
+%
+% }
+%
+% my %param = $access_user->options;
+%
+% #XXX autogen
+% my @paramlist = qw( menu_position
+%                     email_address
+%                     vonage-fromnumber vonage-username vonage-password
+%                     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 );
+%
+% if ( $error ) {
+%   $cgi->param('error', $error);
+%   print $cgi->redirect(popurl(1). "pref.html?". $cgi->query_string );
+% } else {
+<% include('/elements/header.html', 'Preferences updated') %>
+<% include('/elements/footer.html') %>
+% }
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
new file mode 100644 (file)
index 0000000..de5bd82
--- /dev/null
@@ -0,0 +1,102 @@
+<% 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>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>
+
+</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>
+
+
+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="<% $FS::CurrentUser::CurrentUser->option('vonage-fromnumber') %>"></TD>
+  </TR>
+
+  <TR>
+    <TH ALIGN="right">Vonage username</TH>
+    <TD><INPUT TYPE="text" NAME="vonage-username" VALUE="<% $FS::CurrentUser::CurrentUser->option('vonage-username') %>"></TD>
+  </TR>
+
+  <TR>
+    <TH ALIGN="right">Vonage password</TH>
+    <TD><INPUT TYPE="password" NAME="vonage-password" VALUE="<% $FS::CurrentUser::CurrentUser->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>
+
+# XSS via your own preferences?  seems unlikely, but nice try anyway...
+( $FS::CurrentUser::CurrentUser->option('menu_position') || 'left' )
+  =~ /^(\w+)$/ or die "illegal menu_position";
+my $menu_position = $1;
+( $FS::CurrentUser::CurrentUser->option('email_address') )
+  =~ /^([,\w\@.]*)$/ or die "illegal email_address";  #too late
+my $email_address = $1;
+
+</%init>
diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html
new file mode 100644 (file)
index 0000000..0facc7f
--- /dev/null
@@ -0,0 +1,91 @@
+<% 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' => [ fields('cdr') ], #XXX fill in some nice names
+               'fields' => [ fields('cdr') ], #XXX fill in some pretty-print
+                                              # processing, etc.
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+my $title = 'Call Detail Records';
+my $hashref = {};
+
+#process params for CDR search, populate $hashref...
+# and fixup $count_query
+
+my @search = ();
+my @qsearch = ();
+
+###
+# freesidestatus
+###
+
+if ( $cgi->param('freesidestatus') eq 'NULL' ) {
+
+  my $title = "Unprocessed $title";
+  $hashref->{'freesidestatus'} = ''; # Record.pm will take care of it
+  push @search, "( freesidestatus IS NULL OR freesidestatus = '' )";
+
+} elsif ( $cgi->param('freesidestatus') =~ /^([\w ]+)$/ ) {
+
+  my $title = "Processed $title";
+  $hashref->{'freesidestatus'} = $1;
+  push @search, "freesidestatus = '$1'";
+
+}
+
+###
+# dates
+###
+
+my $str2time_sql = str2time_sql;
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @search,
+my @dsearch = ( "$str2time_sql calldate) >= $beginning ",
+                "$str2time_sql calldate) <= $ending"
+             );
+push @search, @dsearch;
+push @qsearch, @search;
+
+
+###
+# src/dest
+###
+
+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'";
+}
+
+###
+# 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;
+
+</%init>
diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html
new file mode 100755 (executable)
index 0000000..9166f6d
--- /dev/null
@@ -0,0 +1,226 @@
+<% 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'      => [
+                   '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' => 'rrrr'.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*$/ ) {
+
+  $count_query =
+    "SELECT COUNT(*) FROM cust_bill $join_cust_main".
+    "  WHERE invnum = $2 AND $agentnums_sql"; #agent virtualization
+  $sql_query = {
+    'table'     => 'cust_bill',
+    'addl_from' => $join_cust_main,
+    'hashref'   => { 'invnum' => $2 },
+    #'select'    => '*',
+    'extra_sql' => " AND $agentnums_sql", #agent virtualization
+  };
+
+} else {
+
+  #some false laziness w/cust_bill::re_X
+  my @where;
+  my $orderby = 'ORDER BY cust_bill._date';
+
+  if ( $cgi->param('beginning')
+       && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+    $search{'begin'} = str2time($1);
+  }
+  if ( $cgi->param('ending')
+        && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+    $search{'end'} = str2time($1) + 86399;
+  }
+
+  if ( $cgi->param('begin') =~ /^(\d+)$/ ) {
+    $search{'begin'} = $1;
+  }
+  if ( $cgi->param('end') =~ /^(\d+)$/ ) {
+    $search{'end'} = $1;
+  }
+
+  if ( $cgi->param('invnum_min') =~ /^\s*(\d+)\s*$/ ) {
+    $search{'invnum_min'} = $1;
+  }
+  if ( $cgi->param('invnum_max') =~ /^\s*(\d+)\s*$/ ) {
+    $search{'invnum_max'} = $1;
+  }
+
+  if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+    $search{'agentnum'} = $1;
+  }
+
+  $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( \%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 $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 qq!<INPUT TYPE="hidden" NAME="$_" VALUE="$search{$_}">!, keys %search ),
+ 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 =  [
+                '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');
+
+</%init>
diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi
new file mode 100644 (file)
index 0000000..ff4168d
--- /dev/null
@@ -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( \%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 (executable)
index 0000000..87bb3b7
--- /dev/null
@@ -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' ) %>
+
+      <!--<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_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
new file mode 100644 (file)
index 0000000..17b4bc2
--- /dev/null
@@ -0,0 +1,191 @@
+<% include( 'elements/search.html',
+                 'title'       => 'Line items',
+                 'name'        => 'line items',
+                 'query'       => $query,
+                 'count_query' => $count_query,
+                 'count_addl'  => [ $money_char. '%.2f total', ],
+                 'header'      => [
+                   '#',
+                   'Description',
+                   'Setup charge',
+                   'Recurring charge',
+                   'Invoice',
+                   'Date',
+                   FS::UI::Web::cust_header(),
+                 ],
+                 'fields'      => [
+                   'billpkgnum',
+                   sub { $_[0]->pkgnum > 0
+                           ? $_[0]->get('pkg')
+                           : $_[0]->get('itemdesc')
+                       },
+                   #strikethrough or "N/A ($amount)" or something these when
+                   # they're not applicable to pkg_tax search
+                   sub { sprintf($money_char.'%.2f', shift->setup ) },
+                   sub { sprintf($money_char.'%.2f', shift->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' => 'rlrrrc'.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($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_pkg = "
+    LEFT JOIN cust_pkg USING ( pkgnum )
+    LEFT JOIN part_pkg USING ( pkgpart )
+";
+
+my $where = " WHERE _date >= $beginning AND _date <= $ending ";
+
+$where .= " AND payby != 'COMP' "
+  unless $cgi->param('include_comp_cust');
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+  $where .= " AND agentnum = $1 ";
+}
+
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+  if ( $1 == 0 ) {
+    $where .= " AND classnum IS NULL ";
+  } else {
+    $where .= " AND classnum = $1 ";
+  }
+}
+
+if ( $cgi->param('out') ) {
+
+  $where .= "
+    AND 0 = (
+      SELECT COUNT(*) FROM cust_main_county
+      WHERE (    cust_main_county.county  = cust_main.county
+              OR ( cust_main_county.county IS NULL AND cust_main.county  =  '' )
+              OR ( cust_main_county.county  =  ''  AND cust_main.county IS NULL)
+              OR ( cust_main_county.county IS NULL AND cust_main.county IS NULL)
+            )
+        AND (    cust_main_county.state   = cust_main.state
+              OR ( cust_main_county.state  IS NULL AND cust_main.state  =  ''  )
+              OR ( cust_main_county.state   =  ''  AND cust_main.state IS NULL )
+              OR ( cust_main_county.state  IS NULL AND cust_main.state IS NULL )
+            )
+        AND cust_main_county.country = cust_main.country
+        AND cust_main_county.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') );
+  $where .= "
+    AND ( county  = $county OR $county = '' )
+    AND ( state   = $state  OR $state = '' )
+    AND   country = $country
+  ";
+  $where .= ' AND taxclass = '. dbh->quote( $cgi->param('taxclass') )
+    if $cgi->param('taxclass');
+
+}
+
+$where .= ' AND pkgnum != 0' if $cgi->param('nottax');
+
+$where .= ' AND pkgnum = 0' if $cgi->param('istax');
+
+$where .= " AND tax = 'Y'" if $cgi->param('cust_tax');
+
+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
+                           )
+                         )
+    ";
+
+  $where .= " 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 )
+            ";
+
+} else {
+
+  $count_query =
+    "SELECT COUNT(*), SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)";
+
+}
+$count_query .= " FROM cust_bill_pkg $join_cust  $join_pkg $where";
+
+my $query = {
+  'table'     => 'cust_bill_pkg',
+  'addl_from' => "$join_cust $join_pkg",
+  'hashref'   => {},
+  'select'    => join(', ',
+                   'cust_bill_pkg.*',
+                   'cust_bill._date',
+                   '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/cust_credit.html b/httemplate/search/cust_credit.html
new file mode 100755 (executable)
index 0000000..e4975c8
--- /dev/null
@@ -0,0 +1,104 @@
+<% include( 'elements/search.html',
+                 'title'       => $title,
+                 'name'        => 'credits',
+                 'query'       => $sql_query,
+                 'count_query' => $count_query,
+                 'count_addl'  => [ '$%.2f total credited', ],
+                 #'redirect'    => $link,
+                 'header'      => [ 'Amount',
+                                    'Date',
+                                    FS::UI::Web::cust_header(),
+                                    'By',
+                                    'Reason'
+                                  ],
+                 'fields'      => [
+                   #'crednum',
+                   sub { sprintf('$%.2f', shift->amount ) },
+                   sub { time2str('%b %d %Y', shift->_date ) },
+                   \&FS::UI::Web::cust_fields,
+                   'otaker',
+                   'reason',
+                 ],
+                 #'align' => 'rrrllll',
+                 'align' => 'rr'.FS::UI::Web::cust_aligns().'ll',
+                 '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_event.html b/httemplate/search/cust_event.html
new file mode 100644 (file)
index 0000000..d55b5c6
--- /dev/null
@@ -0,0 +1,285 @@
+<% 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 ) {
+    my $alt_templatename = $part_event->templatename;
+    my $alt_link = "$alt_templatename-". $cust_event->tablenum;
+
+    my $conf = new FS::Conf;
+    my $cust_bill = $cust_event->cust_X;
+
+    $status .= qq{
+          ( <A HREF="${p}view/cust_bill.cgi?$alt_link">view</A>
+          | <A HREF="${p}view/cust_bill-pdf.cgi?$alt_link.pdf">view
+              typeset</A>
+          | <A HREF="${p}misc/print-invoice.cgi?$alt_link">re-print</A>
+    };
+
+    if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { 
+      $status .= qq{
+            | <A HREF="${p}misc/email-invoice.cgi?$alt_link">re-email</A>
+      };
+    } 
+   
+    if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) { 
+      $status .= qq{
+            | <A HREF="${p}misc/fax-invoice.cgi?$alt_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;
+    [ "${p}view/cust_main.cgi?$custnum#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 = ();
+
+if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+  push @search, "cust_main.agentnum = $1";
+  #my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+  #die "unknown agentnum $1" unless $agent;
+}
+
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+push @search, "cust_event._date >= $beginning",
+              "cust_event._date <= $ending";
+
+if ( $cgi->param('failed') ) {
+  push @search, "statustext != ''",
+                "statustext IS NOT NULL",
+                "statustext != 'N/A'";
+}
+
+#if ( $cgi->param('part_event.payby') =~ /^(\w+)$/ ) {
+#  push @search, "part_event.payby = '$1'";
+#}
+
+if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+  push @search, "cust_main.custnum = '$1'";
+}
+if ( $cgi->param('invnum') =~ /^(\d+)$/ ) {
+  push @search, "part_event.eventtable = 'cust_bill'",
+                "tablenum = '$1'";
+}
+if ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+  push @search, "part_event.eventtable = 'cust_pkg'",
+                "tablenum = '$1'";
+}
+
+#here is the agent virtualization
+push @search, $curuser->agentnums_sql( 'table' => 'cust_main' );
+
+my $where = 'WHERE '. join(' AND ', @search );
+
+my $join = "
+       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 )
+                         )
+";
+           #'LEFT JOIN cust_main  USING ( custnum   ) ';
+
+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 $failed = $cgi->param('failed');
+
+my $html_init = join("\n", map {
+  ( my $action = $_ ) =~ s/_$//;
+  include('/elements/progress-init.html',
+            $_.'form',
+            [ 'action', 'beginning', 'ending', 'failed' ],
+            "../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
+  qq!<INPUT TYPE="hidden" NAME="beginning" VALUE="$beginning">!,
+  qq!<INPUT TYPE="hidden" NAME="ending"    VALUE="$ending">!,
+  qq!<INPUT TYPE="hidden" NAME="failed"    VALUE="$failed">!,
+  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 (executable)
index 0000000..0c252e4
--- /dev/null
@@ -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 (file)
index 0000000..56df924
--- /dev/null
@@ -0,0 +1,99 @@
+<% 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";
+}
+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 (executable)
index 0000000..1ddafae
--- /dev/null
@@ -0,0 +1,727 @@
+%die "access denied"
+%  unless $FS::CurrentUser::CurrentUser->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' ) {
+%      $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><% $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 = $part_pkg->pkg;
+%      my $comment = $part_pkg->comment;
+%      my $pkgview = "${p}view/cust_main.cgi?$custnum#cust_pkg$pkgnum";
+%      my @cust_svc = @{shift @lol_cust_svc};
+%      #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } );
+%      my $rowspan = scalar(@cust_svc) || 1;
+%
+%      print $n1, qq!<TD 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 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 (executable)
index 0000000..c050c5b
--- /dev/null
@@ -0,0 +1,172 @@
+<% include( 'elements/search.html',
+                  'title'       => 'Customer Search Results', 
+                  '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
+                                   ],
+              )
+%>
+<%once>
+
+my $link = [ "${p}view/cust_main.cgi?", 'custnum' ];
+
+</%once>
+<%init>
+
+die "access denied"
+  unless ( $FS::CurrentUser::CurrentUser->access_right('List customers') &&
+           $FS::CurrentUser::CurrentUser->access_right('List packages')
+         );
+
+my $dbh = dbh;
+my $conf = new FS::Conf;
+my $countrydefault = $conf->config('countrydefault');
+
+my($query) = $cgi->keywords;
+
+my @where = ();
+
+##
+# parse agent
+##
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) {
+  push @where,
+    "agentnum = $1";
+}
+
+##
+# parse cancelled package checkbox
+##
+
+my $pkgwhere = "";
+
+$pkgwhere .= "AND (cancel = 0 or cancel is null)"
+  unless $cgi->param('cancelled_pkgs');
+
+my $orderby;
+
+##
+# dates
+##
+
+foreach my $field (qw( signupdate )) {
+
+  my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field);
+
+  next if $beginning == 0 && $ending == 4294967295;
+
+  push @where,
+    "cust_main.$field IS NOT NULL",
+    "cust_main.$field >= $beginning",
+    "cust_main.$field <= $ending";
+
+  $orderby ||= "ORDER BY cust_main.$field";
+
+}
+
+###
+# payby
+###
+
+my @payby = grep /^([A-Z]{4})$/, $cgi->param('payby');
+if ( @payby ) {
+  push @where, '( '. join(' OR ', map "cust_main.payby = '$_'", @payby). ' )';
+}
+
+##
+# amounts
+##
+
+my $balance_sql = FS::cust_main->balance_sql();
+
+push @where, map { s/current_balance/$balance_sql/; $_ }
+                 FS::UI::Web::parse_lt_gt($cgi, 'current_balance');
+
+##
+# 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($cgi->param('cust_fields')),
+             );
+
+my (@extra_headers) = ();
+my (@extra_fields) = ();
+
+if ($cgi->param('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 $orderby",
+};
+
+
+</%init>
diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi
new file mode 100755 (executable)
index 0000000..e5465ae
--- /dev/null
@@ -0,0 +1,247 @@
+<% include( 'elements/search.html',
+                 'title'       => $title,
+                 'name'        => 'payments',
+                 'query'       => $sql_query,
+                 'count_query' => $count_query,
+                 'count_addl'  => [ '$%.2f total paid', ],
+                 'header'      => [ 'Payment',
+                                    'Amount',
+                                    'Date',
+                                    'By',
+                                    FS::UI::Web::cust_header(),
+                                  ],
+                 'fields'      => [
+                   sub {
+                     my $cust_pay = shift;
+                     if ( $cust_pay->payby eq 'CARD' ) {
+                       'Card #'. $cust_pay->paymask;
+                     } elsif ( $cust_pay->payby eq 'CHEK' ) {
+                       'E-check acct#'. $cust_pay->payinfo;
+                     } elsif ( $cust_pay->payby eq 'BILL' ) {
+                       'Check #'. $cust_pay->payinfo;
+                     } elsif ( $cust_pay->payby eq 'PREP' ) {
+                       'Prepaid card #'. $cust_pay->payinfo;
+                     } elsif ( $cust_pay->payby eq 'CASH' ) {
+                       'Cash '. $cust_pay->payinfo;
+                     } elsif ( $cust_pay->payby eq 'WEST' ) {
+                       'Western Union'; #. $cust_pay->payinfo;
+                     } elsif ( $cust_pay->payby eq 'MCRD' ) {
+                       'Manual credit card'; #. $cust_pay->payinfo;
+                     } else {
+                       $cust_pay->payby. ' '. $cust_pay->payinfo;
+                     }
+                   },
+                   sub { sprintf('$%.2f', shift->paid ) },
+                   sub { time2str('%b %d %Y', shift->_date ) },
+                   sub { my $o = shift->otaker;
+                         $o = 'auto billing'          if $o eq 'fs_daily';
+                         $o = 'customer self-service' if $o eq 'fs_selfservice';
+                         $o;
+                       },
+                   \&FS::UI::Web::cust_fields,
+                 ],
+                 #'align' => 'lrrrll',
+                 'align' => 'rrrc'.FS::UI::Web::cust_aligns(),
+                 'links' => [
+                   $link,
+                   $link,
+                   $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 $title = 'Payment Search Results';
+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('payby') ) {
+      $cgi->param('payby') =~
+        /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/
+          or die "illegal payby ". $cgi->param('payby');
+      push @search, "cust_pay.payby = '$1'";
+      if ( $3 ) {
+
+        my $cardtype = $3;
+
+        my $search;
+        if ( $cardtype eq 'VisaMC' ) {
+          #avoid posix regexes for portability
+          $search =
+            " ( (     substring(cust_pay.payinfo from 1 for 1) = '4'     ".
+            "     AND substring(cust_pay.payinfo from 1 for 4) != '4936' ".
+            "     AND substring(cust_pay.payinfo from 1 for 6)           ".
+            "         NOT SIMILAR TO '49030[2-9]'                        ".
+            "     AND substring(cust_pay.payinfo from 1 for 6)           ".
+            "         NOT SIMILAR TO '49033[5-9]'                        ".
+            "     AND substring(cust_pay.payinfo from 1 for 6)           ".
+            "         NOT SIMILAR TO '49110[1-2]'                        ".
+            "     AND substring(cust_pay.payinfo from 1 for 6)           ".
+            "         NOT SIMILAR TO '49117[4-9]'                        ".
+            "     AND substring(cust_pay.payinfo from 1 for 6)           ".
+            "         NOT SIMILAR TO '49118[1-2]'                        ".
+            "   )".
+            "   OR substring(cust_pay.payinfo from 1 for 2) = '51' ".
+            "   OR substring(cust_pay.payinfo from 1 for 2) = '52' ".
+            "   OR substring(cust_pay.payinfo from 1 for 2) = '53' ".
+            "   OR substring(cust_pay.payinfo from 1 for 2) = '54' ".
+            "   OR substring(cust_pay.payinfo from 1 for 2) = '54' ".
+            "   OR substring(cust_pay.payinfo from 1 for 2) = '55' ".
+            "   OR substring(cust_pay.payinfo from 1 for 2) = '36' ". #Diner's int'l processed as Visa/MC inside US
+            " ) ";
+        } elsif ( $cardtype eq 'Amex' ) {
+          $search =
+            " (    substring(cust_pay.payinfo from 1 for 2 ) = '34' ".
+            "   OR substring(cust_pay.payinfo from 1 for 2 ) = '37' ".
+            " ) ";
+        } elsif ( $cardtype eq 'Discover' ) {
+          $search =
+            " (    substring(cust_pay.payinfo from 1 for 4 ) = '6011'  ".
+            "   OR substring(cust_pay.payinfo from 1 for 2 ) = '65'    ".
+            "   OR substring(cust_pay.payinfo from 1 for 3 ) = '622'   ". #China Union Pay processed as Discover outside CN
+            " ) ";
+        } elsif ( $cardtype eq 'Maestro' ) { 
+          $search =
+            " (    substring(cust_pay.payinfo from 1 for 2 ) = '63'     ".
+            "   OR substring(cust_pay.payinfo from 1 for 2 ) = '67'     ".
+            "   OR substring(cust_pay.payinfo from 1 for 6 ) = '564182' ".
+            "   OR substring(cust_pay.payinfo from 1 for 4 ) = '4936'   ".
+            "   OR substring(cust_pay.payinfo from 1 for 6 )            ".
+            "      SIMILAR TO '49030[2-9]'                             ".
+            "   OR substring(cust_pay.payinfo from 1 for 6 )            ".
+            "      SIMILAR TO '49033[5-9]'                             ".
+            "   OR substring(cust_pay.payinfo from 1 for 6 )            ".
+            "      SIMILAR TO '49110[1-2]'                             ".
+            "   OR substring(cust_pay.payinfo from 1 for 6 )            ".
+            "      SIMILAR TO '49117[4-9]'                             ".
+            "   OR substring(cust_pay.payinfo from 1 for 6 )            ".
+            "      SIMILAR TO '49118[1-2]'                             ".
+            " ) ";
+        } else {
+          die "unknown card type $cardtype";
+        }
+
+        my $masksearch = $search;
+        $masksearch =~ s/cust_pay\.payinfo/cust_pay.paymask/gi;
+
+        push @search,
+          "( $search OR ( cust_pay.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, "cust_pay.payinfo = '$1'";
+    }
+
+    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, 'paid' );
+
+    $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');
+  }
+
+  #here is the agent virtualization
+  push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+  my $search = ' WHERE '. join(' AND ', @search);
+
+  $count_query = "SELECT COUNT(*), SUM(paid) ".
+                 "FROM cust_pay LEFT JOIN cust_main USING ( custnum )".
+                 $search;
+
+  $sql_query = {
+    'table'     => 'cust_pay',
+    'select'    => join(', ',
+                     'cust_pay.*',
+                     '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(paid) FROM cust_pay".
+                 "  WHERE payinfo = '$payinfo' AND payby = '$payby'".
+                 "  AND ". $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+  $sql_query = {
+    'table'     => 'cust_pay',
+    'hashref'   => { 'payinfo' => $payinfo,
+                     'payby'   => $payby    },
+    'extra_sql' => $FS::CurrentUser::CurrentUser->agentnums_sql.
+                   " ORDER BY _date",
+  };
+
+}
+
+my $link = [ "${p}view/cust_pay.html?paynum=", 'paynum' ];
+
+my $cust_link = sub {
+  my $cust_pay = shift;
+  $cust_pay->cust_main_custnum
+    ? [ "${p}view/cust_main.cgi?", 'custnum' ] 
+    : '';
+};
+
+</%init>
diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi
new file mode 100755 (executable)
index 0000000..60dca8c
--- /dev/null
@@ -0,0 +1,191 @@
+<% 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')
+           #&& $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')
+          ) 
+  ) {
+    $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!</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!</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_pkg.cgi b/httemplate/search/cust_pkg.cgi
new file mode 100755 (executable)
index 0000000..20f1154
--- /dev/null
@@ -0,0 +1,237 @@
+<% include( 'elements/search.html',
+                  'html_init'   => $html_init, 
+                  'title'       => 'Package Search Results', 
+                  'name'        => 'packages',
+                  'query'       => $sql_query,
+                  'count_query' => $count_query,
+                  #'redirect'    => $link,
+                  'header'      => [ '#',
+                                     'Package',
+                                     'Class',
+                                     'Status',
+                                     'Freq.',
+                                     'Setup',
+                                     'Last bill',
+                                     'Next bill',
+                                     'Adjourn',
+                                     'Susp.',
+                                     'Expire',
+                                     'Cancel',
+                                     'Reason',
+                                     FS::UI::Web::cust_header(
+                                       $cgi->param('cust_fields')
+                                     ),
+                                     'Services',
+                                   ],
+                  'fields'      => [
+                    'pkgnum',
+                    sub { #my $part_pkg = $part_pkg{shift->pkgpart};
+                          #$part_pkg->pkg; # ' - '. $part_pkg->comment;
+                          $_[0]->pkg; # ' - '. $_[0]->comment;
+                        },
+                    'classname',
+                    sub { ucfirst(shift->status); },
+                    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 = '';
+                          if ($self->getfield('cancel') ||
+                            $self->getfield('suspend')) {
+                              my $reason = $self->last_reason;# too inefficient?
+                              $return = $reason->reason if $reason;
+
+                          }
+                          $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' => 'rlcclrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r',
+                  'links' => [
+                    $link,
+                    $link,
+                    '',
+                    '',
+                    '',
+                    '',
+                    '',
+                    '',
+                    '',
+                    '',
+                    '',
+                    '',
+                    '',
+                    ( map { $_ ne 'Cust. Status' ? $clink : '' }
+                          FS::UI::Web::cust_header(
+                                                    $cgi->param('cust_fields')
+                                                  )
+                    ),
+                    '',
+                  ],
+                  'extra_choices_callback'=> $extra_choices, 
+              )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('List packages');
+
+# my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {});
+
+  my %search_hash = ();
+  
+  $search_hash{'query'} = $cgi->keywords;
+  
+  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_sql(\%search_hash);
+my $count_query = delete($sql_query->{'count_query'});
+
+my $link = sub {
+  [ "${p}view/cust_main.cgi?".shift->custnum.'#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 = '';
+for (qw (overlibmws overlibmws_iframe overlibmws_draggable iframecontentmws))
+{
+  $html_init .=
+    qq!<SCRIPT TYPE="text/javascript" SRC="$fsurl/elements/$_.js"></SCRIPT>!;
+}
+
+my $extra_choices = sub {
+  my $query = shift;
+  my $choices = '';
+
+  my $url = qq!overlib( OLiframeContent('!. popurl(2).
+            qq!misc/bulk_change_pkg.cgi?$query', 768, 336, !.
+            qq!'bulk_pkg_change_popup' ), CAPTION, 'Change Packages'!.
+            qq!, STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, !.
+            qq!CLOSECLICK ); return false;!;
+
+  if ($FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages')) {
+    $choices .= qq!<BR><A HREF="javascript:void(0);"!.
+                qq!onClick="$url">Change these packages</A>!;
+  }
+
+  return $choices;
+};
+
+</%init>
diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html
new file mode 100644 (file)
index 0000000..3beca4d
--- /dev/null
@@ -0,0 +1,138 @@
+<% 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;
+
+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_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi
new file mode 100644 (file)
index 0000000..604502d
--- /dev/null
@@ -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, "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/search.html b/httemplate/search/elements/search.html
new file mode 100644 (file)
index 0000000..e1bc024
--- /dev/null
@@ -0,0 +1,853 @@
+<%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
+    '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' =>
+   
+    ###
+    # 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_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_right' => 'Access Right', #opt. right to view global records
+    '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...
+    
+  );
+
+</%doc>
+% if ( $type eq 'csv' ) {
+%
+%   #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+%   http_header('Content-Type' => 'text/plain' );
+%
+%   my $csv = new Text::CSV_XS { 'always_quote' => 1,
+%                                'eol'          => "\n", #"\015\012", #"\012"
+%                              };
+%
+%   $csv->combine(@$header); #or die $csv->status;
+%    
+<% $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 %>
+%
+%
+%   }
+%
+% #} elsif ( $type eq 'excel' ) {
+% } elsif ( $type =~ /\.xls$/ ) {
+%
+%   #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://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));
+%
+%   my($r,$c) = (0,0);
+%
+%   $worksheet->write($r, $c++, $_) foreach @$header;
+%
+%   foreach my $row ( @$rows ) {
+%     $r++;
+%     $c = 0;
+%
+%     if ( $opt{'fields'} ) {
+%
+%       #my $links = $opt{'links'} ? [ @{$opt{'links'}} ] : '';
+%       #my $aligns = $opt{'align'} ? [ @{$opt{'align'}} ] : '';
+%
+%       foreach my $field ( @{$opt{'fields'}} ) {
+%         #my $align = $aligns ? shift @$aligns : '';
+%         #$align = " ALIGN=$align" if $align;
+%         #my $a = '';
+%         #if ( $links ) {
+%         #  my $link = shift @$links;
+%         #  $link = &{$link}($row) if ref($link) eq 'CODE';
+%         #  if ( $link ) {
+%         #    my( $url, $method ) = @{$link};
+%         #    if ( ref($method) eq 'CODE' ) {
+%         #      $a = $url. &{$method}($row);
+%         #    } else {
+%         #      $a = $url. $row->$method();
+%         #    }
+%         #    $a = qq(<A HREF="$a">);
+%         #  }
+%         #}
+%         if ( ref($field) eq 'CODE' ) {
+%           foreach my $value ( &{$field}($row) ) {
+%             if ( ref($value) eq 'ARRAY' ) { 
+%               $worksheet->write($r, $c++, '(N/A)' ); #unimplemented
+%             } else {
+%               $worksheet->write($r, $c++, $value );
+%             }
+%           }
+%         } else {
+%           $worksheet->write($r, $c++, $row->$field() );
+%         }
+%       }
+%
+%     } else {
+%       $worksheet->write($r, $c++, $_) foreach @$row;
+%     }
+%
+%   }
+%
+%   $workbook->close();# or die "Error creating .xls file: $!";
+%
+%   http_header('Content-Length' => length($data) );
+%    
+<% $data %>
+%
+%
+% } else { # regular HTML
+%
+%   if ( exists($opt{'redirect'}) && scalar(@$rows) == 1 && $total == 1
+%        && $type ne 'html-print'
+%      ) {
+%     my $redirect = $opt{'redirect'};
+%     $redirect = &{$redirect}($rows->[0]) if ref($redirect) eq 'CODE';
+%     my( $url, $method ) = @$redirect;
+%     redirect( $url. $rows->[0]->$method() );
+%   } 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' ) {
+
+        <% include( '/elements/header-popup.html', $opt{'title'} ) %>
+
+%     } else {
+%
+%       my @menubar = ();
+%       if ( $opt{'menubar'} ) {
+%         @menubar = @{ $opt{'menubar'} };
+%       #} else {
+%       #  @menubar = ( 'Main menu' => $p );
+%       }
+
+        <% 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>
+
+                <% $total %> total <% $opt{'name'} %>
+
+%               if ( $confmax && $total > $confmax && $type ne 'html-print' ) {
+%                 $cgi->delete('maxrecords');
+%                 $cgi->param('_dummy', 1);
+
+%#                 ( show <SELECT NAME="maxrecords" onChange="this.form.submit();">
+                  ( show <SELECT NAME="maxrecords" onChange="window.location = '<% $cgi->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="<% $cgi->self_url %>">Excel spreadsheet</A><BR>
+
+%               $cgi->param('_type', 'csv'); 
+                as <A HREF="<% $cgi->self_url %>">CSV file</A><BR>
+
+%               $cgi->param('_type', 'html-print'); 
+                as <A HREF="<% $cgi->self_url %>">printable copy</A>
+
+              <% $opt{'extra_choices_callback'}
+                 ? &{$opt{'extra_choices_callback'}}($cgi->query_string)
+                 : ''
+              %>
+
+              </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;
+%                 foreach my $header ( @{ $opt{header} } ) { 
+%                   my $label = ref($header) ? $header->{label} : $header;
+%                   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 %>"
+                        <% $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;
+%                         $link = &{$link}($row)
+%                           if ref($link) eq 'CODE';
+%
+%                         my $onclick = shift @$onclicks;
+%                         $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>);
+%                         }
+%                       }
+%
+%                       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'} } ) { 
+                      <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' ) {
+
+        </BODY></HTML>
+      
+%     } else {
+
+        <% defined($opt{'html_foot'}) 
+              ? ( ref($opt{'html_foot'})
+                    ? &{$opt{'html_foot'}}()
+                    : $opt{'html_foot'}
+                )
+              : ''
+        %>
+
+        <% include( '/elements/footer.html' ) %>
+
+%     }
+
+%   } 
+%
+% } 
+<%init>
+
+my(%opt) = @_;
+#warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n";
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my %align = (
+  'l' => 'left',
+  'r' => 'right',
+  'c' => 'center',
+  ' ' => '',
+  '.' => '',
+);
+$opt{align} = [ map $align{$_}, split(//, $opt{align}) ],
+  unless !$opt{align} || ref($opt{align});
+
+if ( $opt{'agent_virt'} ) {
+
+  my $agentnums_sql = $curuser->agentnums_sql(
+                        'null_right' => $opt{'agent_null_right'}
+                      );
+
+  $opt{'query'}{'extra_sql'} .=
+    ( $opt{'query'}       =~ /WHERE/i ? ' 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 style color size )) {
+      $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 $type = $cgi->param('_type') =~ /^(csv|\w*\.xls|html(-print)?)$/
+           ? $1 : 'html';
+
+my $limit = '';
+my($confmax, $maxrecords, $total, $offset, $count_arrayref);
+
+unless ( $type =~ /^(csv|\w*\.xls)$/ ) {
+
+  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;
+
+  }
+
+  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;
+  $count_arrayref = $count_sth->fetchrow_arrayref;
+  $total = $count_arrayref->[0];
+
+}
+
+# run the query
+
+my $header = [ map { ref($_) ? $_->{'label'} : $_ } @{$opt{header}} ];
+my $rows;
+if ( ref($opt{query}) ) {
+
+  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'};";
+  $rows = [ qsearch({
+    'select'    => $opt{'query'}->{'select'},
+    'table'     => $opt{'query'}->{'table'}, 
+    'addl_from' => (exists($opt{'query'}->{'addl_from'}) ? $opt{'query'}->{'addl_from'} : ''),
+    'hashref'   => $opt{'query'}->{'hashref'} || {}, 
+    'extra_sql' => $opt{'query'}->{'extra_sql'},
+    'order_by'  => $opt{'query'}->{'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/inventory_item.html b/httemplate/search/inventory_item.html
new file mode 100644 (file)
index 0000000..cd37e26
--- /dev/null
@@ -0,0 +1,125 @@
+<% include( 'elements/search.html',
+                 'title'       => $title,
+
+                 #less lame to use Lingua:: something to pluralize
+                 'name'        => $inventory_class->classname. 's',
+
+                 'query'       => {
+                                    'table'   => 'inventory_item',
+                                    'hashref' => { 'classnum' => $classnum },
+                                    'select'  => join(', ',
+                                        'inventory_item.*',
+                                        'cust_main.custnum',
+                                        FS::UI::Web::cust_sql_fields(),
+                                      ),
+                                    'extra_sql' => $extra_sql,
+                                    'addl_from' => $addl_from,
+                                  },
+
+                 'count_query' => $count_query,
+
+                 'header'      => [
+                   '#',
+                   $inventory_class->classname,
+                   'Service',
+                   FS::UI::Web::cust_header(),
+                 ],
+
+                 '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,
+
+                 ],
+                 '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(),
+                            ],
+
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $classnum = $cgi->param('classnum');
+$classnum =~ /^(\d+)$/ or errorpage("illegal classnum $classnum");
+$classnum = $1;
+
+my $inventory_class = qsearchs( {
+  'table'     => 'inventory_class',
+  'hashref'   => { 'classnum' => $classnum },
+} );
+
+my $title = $inventory_class->classname. ' Inventory';
+
+#little false laziness with SQL fragments in inventory_class.pm
+my $extra_sql = '';
+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 WHERE classnum = $classnum $extra_sql";
+
+my $link = sub {
+  my $inventory_item = shift;
+  if ( $inventory_item->svcnum ) {
+    [ "${p}view/svc_acct.cgi?", '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 ) ';
+
+</%init>
diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi
new file mode 100755 (executable)
index 0000000..cb21717
--- /dev/null
@@ -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 = str2time($1);
+  push @where, "download >= $begin";
+}
+if ( $cgi->param('ending')
+      && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+  $end = str2time($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?batchnum=", 'batchnum' ];
+
+</%init>
diff --git a/httemplate/search/pay_batch.html b/httemplate/search/pay_batch.html
new file mode 100644 (file)
index 0000000..5907169
--- /dev/null
@@ -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/prepay_credit.html b/httemplate/search/prepay_credit.html
new file mode 100644 (file)
index 0000000..96391fc
--- /dev/null
@@ -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}view/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/queue.html b/httemplate/search/queue.html
new file mode 100644 (file)
index 0000000..125a6f7
--- /dev/null
@@ -0,0 +1,138 @@
+<% 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/
+                                                    );
+                                 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;)!;
+                                 }
+                                 $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 (file)
index 0000000..f65b00d
--- /dev/null
@@ -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.
+                       '</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_cdr.html b/httemplate/search/report_cdr.html
new file mode 100644 (file)
index 0000000..06f0b1a
--- /dev/null
@@ -0,0 +1,44 @@
+<% include('/elements/header.html', 'Call Detail Record Search' ) %>
+
+<FORM ACTION="cdr.html" METHOD="GET">
+
+<TABLE>
+  <TR>
+    <TD ALIGN="right">Status: </TD>
+    <TD>
+      <SELECT NAME="freesidestatus">
+        <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>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search Call Detail Records">
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+</%init>
diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html
new file mode 100644 (file)
index 0000000..892f5c4
--- /dev/null
@@ -0,0 +1,34 @@
+<% include('/elements/header.html', 'Invoice report criteria' ) %>
+
+<FORM ACTION="cust_bill.html" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE>
+  <% include( '/elements/tr-select-agent.html',
+                 'curr_value' => scalar( $cgi->param('agentnum') ),
+                 'label'      => 'Invoices for agent: ',
+             )
+  %>
+  <% include( '/elements/tr-input-beginning_ending.html' ) %>
+  <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_credit.html b/httemplate/search/report_cust_credit.html
new file mode 100644 (file)
index 0000000..be02e9f
--- /dev/null
@@ -0,0 +1,47 @@
+<% 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: ',
+             )
+  %>
+
+  <% 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 (file)
index 0000000..c1f9edb
--- /dev/null
@@ -0,0 +1,65 @@
+<% 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>
+
+      <% include( '/elements/tr-select-agent.html' ) %>
+
+      <!--<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/report_cust_main-zip.html b/httemplate/search/report_cust_main-zip.html
new file mode 100644 (file)
index 0000000..19b7ecc
--- /dev/null
@@ -0,0 +1,52 @@
+<% 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>
+
+      <% include( '/elements/tr-select-agent.html',
+                     'curr_value' => scalar( $cgi->param('agentnum') ),
+                     'label'      => 'For agent: ',
+                 )
+      %>
+
+    </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 (executable)
index 0000000..758c269
--- /dev/null
@@ -0,0 +1,82 @@
+<% 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 BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+    </TR>
+    <% include( '/elements/tr-select-agent.html',
+                   ($cgi->param('agentnum') || ''),
+               )
+    %>
+
+%   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,
+                  'curr_value' => { map { $_ => 1 } FS::payby->cust_payby },
+              )
+    %>
+    
+    <% 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>
+
+    <TR>
+      <TH BGCOLOR="#e8e8e8" 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')
+         );;
+
+</%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 (file)
index 0000000..d7cb0ae
--- /dev/null
@@ -0,0 +1,78 @@
+<% include('/elements/header.html', 'Payment report' ) %>
+
+<FORM ACTION="cust_pay.cgi" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE>
+
+  <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: ',
+             )
+  %>
+
+  <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+  <% 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_cust_pay_batch.html b/httemplate/search/report_cust_pay_batch.html
new file mode 100644 (file)
index 0000000..0cd656c
--- /dev/null
@@ -0,0 +1,43 @@
+<% 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: ',
+             )
+  %>
+
+  <% 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 (executable)
index 0000000..d210446
--- /dev/null
@@ -0,0 +1,148 @@
+<% include('/elements/header.html', 'Package Report' ) %>
+
+<FORM ACTION="cust_pkg.cgi" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="bill">
+
+  <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+    <TR>
+      <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+        <FONT SIZE="+1">Search options</FONT>
+      </TH>
+    </TR>
+
+    <% include( '/elements/tr-select-agent.html',
+                   'curr_value' => scalar( $cgi->param('agentnum') ),
+               )
+    %>
+
+    <% 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)',
+               )
+    %>
+
+%   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>
+
+%   }
+    
+    <% include( '/elements/tr-selectmultiple-part_pkg.html' ) %> 
+
+    <TR>
+      <TH BGCOLOR="#e8e8e8" COLSPAN=2>&nbsp;</TH>
+    </TR>
+
+    <TR>
+      <TH BGCOLOR="#e8e8e8" 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');
+
+</%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'             => {},
+  '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_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi
new file mode 100644 (file)
index 0000000..27dbcbf
--- /dev/null
@@ -0,0 +1,87 @@
+<% include("/elements/header.html", 'Prepaid Income (Unearned Revenue) Report') %>
+
+<% table() %>
+  <TR>
+    <TH>Actual Unearned Revenue</TH>
+    <TH>Legacy Unearned Revenue</TH>
+  </TR>
+  <TR>
+    <TD ALIGN="right">$<% $total %>
+    <TD ALIGN="right">
+      <% $now == $time ? "\$$total_legacy" : '<i>N/A</i>'%>
+    </TD>
+  </TR>
+
+</TABLE>
+<BR>
+Actual unearned revenue is the amount of unearned revenue Freeside has  
+actually invoiced for packages with longer-than monthly terms.
+<BR><BR>
+Legacy unearned revenue is the amount of unearned revenue represented by 
+customer packages.  This number may be larger than actual unearned 
+revenue if you have imported longer-than monthly customer packages from
+a previous billing system.
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+#doesn't yet deal with daily/weekly packages
+
+#needs to be re-written in sql for efficiency
+
+my $time = time;
+
+my $now = $cgi->param('date') && str2time($cgi->param('date')) || $time;
+$now =~ /^(\d+)$/ or die "unparsable date?";
+$now = $1;
+
+my( $total, $total_legacy ) = ( 0, 0 );
+
+my @cust_bill_pkg =
+  grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ }
+    qsearch( 'cust_bill_pkg', {
+                                'recur' => { op=>'!=', value=>0 },
+                                'edate' => { op=>'>', value=>$now },
+                              }, );
+
+my @cust_pkg = 
+  grep { $_->part_pkg->recur != 0
+         && $_->part_pkg->freq !~ /^([01]|\d+[dw])$/
+       }
+    qsearch ( 'cust_pkg', {
+                            'bill' => { op=>'>', value=>$now }
+                          } );
+
+foreach my $cust_bill_pkg ( @cust_bill_pkg) { 
+  my $period = $cust_bill_pkg->edate - $cust_bill_pkg->sdate;
+
+  my $elapsed = $now - $cust_bill_pkg->sdate;
+  $elapsed = 0 if $elapsed < 0;
+
+  my $remaining = 1 - $elapsed/$period;
+
+  my $unearned = $remaining * $cust_bill_pkg->recur;
+  $total += $unearned;
+
+}
+
+foreach my $cust_pkg ( @cust_pkg ) {
+  my $period = $cust_pkg->bill - $cust_pkg->last_bill;
+
+  my $elapsed = $now - $cust_pkg->last_bill;
+  $elapsed = 0 if $elapsed < 0;
+
+  my $remaining = 1 - $elapsed/$period;
+
+  my $unearned = $remaining * $cust_pkg->part_pkg->recur; #!! only works for flat/legacy
+  $total_legacy += $unearned;
+
+}
+
+$total = sprintf('%.2f', $total);
+$total_legacy = sprintf('%.2f', $total_legacy);
+
+</%init>
diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html
new file mode 100644 (file)
index 0000000..81adb64
--- /dev/null
@@ -0,0 +1,43 @@
+<% include('/elements/header.html', 'Prepaid Income (Unearned Revenue) Report',
+  '',
+  '',
+  '<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 ACTION="report_prepaid_income.cgi" METHOD="GET">
+    <TABLE>
+      <TR>
+        <TD>Prepaid income (unearned revenue) as of </TD>
+        <TD>
+          <INPUT TYPE="text" NAME="date" ID="date_text" VALUE="now">
+          <IMG SRC="../images/calendar.png" ID="date_button" STYLE="cursor: pointer" TITLE="Select date">
+        </TD>
+      </TR>
+      <TR>
+        <TD>
+        </TD>
+        <TD><i>m/d/y</i></TD>
+      </TR>
+    </TABLE>
+<SCRIPT TYPE="text/javascript">
+  Calendar.setup({
+    inputField: "date_text",
+    ifFormat:   "%m/%d/%Y",
+    button:     "date_button",
+    align:      "BR"
+  });
+</SCRIPT>
+
+<INPUT TYPE="submit" VALUE="Generate report">
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+</%init>
diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi
new file mode 100755 (executable)
index 0000000..06aea19
--- /dev/null
@@ -0,0 +1,187 @@
+<% include( 'elements/search.html',
+                 'title'       => 'Accounts Receivable Aging Summary',
+                 'name'        => 'customers',
+                 'query'       => $sql_query,
+                 'count_query' => $count_sql,
+                 'header'      => [
+                                    FS::UI::Web::cust_header(),
+                                    '0-30',
+                                    '30-60',
+                                    '60-90',
+                                    '90+',
+                                    'Total',
+                                  ],
+                 'footer'      => [
+                                    'Total',
+                                    ( map '',
+                                          ( 1 .. 
+                                            scalar(FS::UI::Web::cust_header()-1)
+                                          )
+                                    ),
+                                    sprintf( $money_char.'%.2f',
+                                             $row->{'balance_0_30'} ),
+                                    sprintf( $money_char.'%.2f',
+                                             $row->{'balance_30_60'} ),
+                                    sprintf( $money_char.'%.2f',
+                                             $row->{'balance_60_90'} ),
+                                    sprintf( $money_char.'%.2f',
+                                             $row->{'balance_90_0'} ),
+                                    sprintf( '<b>'. $money_char.'%.2f'. '</b>',
+                                             $row->{'balance_0_0'} ),
+                                  ],
+                 'fields'      => [
+                                    \&FS::UI::Web::cust_fields,
+                                    format_balance('0_30'),
+                                    format_balance('30_60'),
+                                    format_balance('60_90'),
+                                    format_balance('90_0'),
+                                    format_balance('0_0'),
+                                  ],
+                 'links'       => [
+                                    ( map { $_ ne 'Cust. Status' ? $clink : '' }
+                                          FS::UI::Web::cust_header()
+                                    ),
+                                    '',
+                                    '',
+                                    '',
+                                    '',
+                                    '',
+                                  ],
+                 #'align'       => 'rlccrrrrr',
+                 'align'       => FS::UI::Web::cust_aligns(). 'rrrrr',
+                 #'size'        => [ '', '', '-1', '-1', '', '', '', '',  '', ],
+                 #'style'       => [ '', '',  'b',  'b', '', '', '', '', 'b', ],
+                 'size'        => [ ( map '', FS::UI::Web::cust_header() ),
+                                    #'-1', '', '', '', '',  '', ],
+                                    '', '', '', '',  '', ],
+                 'style'       => [ FS::UI::Web::cust_styles(),
+                                    #'b', '', '', '', '', 'b', ],
+                                    '', '', '', '', 'b', ],
+                 'color'       => [
+                                    FS::UI::Web::cust_colors(),
+                                    '',
+                                    '',
+                                    '',
+                                    '',
+                                    '',
+                                  ],
+
+             )
+%>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+
+my @ranges = (
+  [  0, 30 ],
+  [ 30, 60 ],
+  [ 60, 90 ],
+  [ 90,  0 ],
+  [  0,  0 ],
+);
+
+my $owed_cols = join(',', map balance( @$_ ), @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') ) {
+
+  my $days = 0;
+  if ( $cgi->param('days') =~ /^\s*(\d+)\s*$/ ) {
+    $days = $1;
+  }
+
+  push @where, balance($days, 0, 'no_as'=>1). " > 0";
+
+}
+
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+  my $agentnum = $1;
+  push @where, "agentnum = $agentnum";
+}
+
+#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'    => "*, $owed_cols, $packages_cols",
+  'extra_sql' => $where,
+  'order_by'  => "order by coalesce(lower(company), ''), lower(last)",
+};
+
+my $join = 'LEFT JOIN cust_main USING ( custnum )';
+
+my $total_sql = "SELECT ".
+  join(',', map balance( @$_, total=>1, join=>$join, where=>\@where ), @ranges);
+
+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' ];
+
+</%init>
+<%once>
+
+my $conf = new FS::Conf;
+
+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")
+#   #options for totals
+#   '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, %opt) = @_;
+
+  my $as = $opt{'no_as'} ? '' : " AS balance_${start}_$end";
+
+  #handle start and end ranges (86400 = 24h * 60m * 60s)
+  my $str2time = str2time_sql;
+  $start = $start ? "( $str2time now() ) - ".($start * 86400). ' )' : '';
+  $end   = $end   ? "( $str2time now() ) - ".($end   * 86400). ' )' : '';
+
+  $opt{'unapplied_date'} = 1;
+
+  FS::cust_main->balance_date_sql( $start, $end, %opt ). $as;
+
+}
+
+sub format_balance { #closures help alot
+  my $range = shift;
+  sub { sprintf( $money_char.'%.2f', shift->get("balance_$range") ) };
+}
+
+</%once>
diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html
new file mode 100755 (executable)
index 0000000..8040e57
--- /dev/null
@@ -0,0 +1,35 @@
+<% include('/elements/header.html', 'Accounts Receivable Aging Summary' ) %>
+
+<FORM NAME="OneTrueForm" ACTION="report_receivables.cgi" METHOD="GET">
+
+<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+  <TR>
+    <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left">
+      <FONT SIZE="+1">Search options</FONT>
+    </TH>
+  </TR>
+
+  <% include( '/elements/tr-select-agent.html' ) %>
+  
+  <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>
+
+</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_rt_transaction.html b/httemplate/search/report_rt_transaction.html
new file mode 100644 (file)
index 0000000..89629e8
--- /dev/null
@@ -0,0 +1,22 @@
+<% 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">
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('List rating data');
+
+</%init>
diff --git a/httemplate/search/report_svc_acct.html b/httemplate/search/report_svc_acct.html
new file mode 100755 (executable)
index 0000000..f5a0ff9
--- /dev/null
@@ -0,0 +1,91 @@
+<% include('/elements/header.html', 'Account Report' ) %>
+
+<FORM ACTION="svc_acct.cgi" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="advanced">
+
+  <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+
+    <TR>
+      <TH BGCOLOR="#e8e8e8" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+    </TR>
+    <% include( '/elements/tr-select-agent.html',
+                   ($cgi->param('agentnum') || ''),
+               )
+    %>
+
+    <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 BGCOLOR="#e8e8e8" 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');
+
+</%init>
+<%once>
+
+my %label = (
+  'last_login'  => 'Last login',
+  'last_logout' => 'Last logout',
+);
+
+</%once>
diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi
new file mode 100755 (executable)
index 0000000..b029ec0
--- /dev/null
@@ -0,0 +1,582 @@
+<% include("/elements/header.html", "$agentname Sales 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>
+% } 
+
+  </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 ) {
+%
+%       if ( $bgcolor eq $bgcolor1 ) {
+%         $bgcolor = $bgcolor2;
+%       } else {
+%         $bgcolor = $bgcolor1;
+%       }
+%
+%       my $link = '';
+%       if ( $region->{'label'} ne 'Total' ) {
+%         if ( $region->{'label'} eq $out ) {
+%           $link = ';out=1';
+%         } else {
+%           $link = ';'. $region->{'url_param'};
+%         }
+%       }
+%
+%
+%
+%
+%  
+
+
+    <TR>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+        <A HREF="<% $baselink. $link %>;nottax=1"><% $money_char %><% sprintf('%.2f', $region->{'total'} ) %></A>
+      </TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+        <A HREF="<% $baselink. $link %>;nottax=1;cust_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_cust'} ) %></A>
+      </TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+        <A HREF="<% $baselink. $link %>;nottax=1;pkg_tax=Y"><% $money_char %><% sprintf('%.2f', $region->{'exempt_pkg'} ) %></A>
+      </TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> - </B></FONT></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+        <A HREF="<% $exemptlink. $link %>"><% $money_char %><% sprintf('%.2f', $region->{'exempt_monthly'} ) %></A>
+        </TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><FONT SIZE="+1"><B> = </B></FONT></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+        <% $money_char %><% sprintf('%.2f', $region->{'taxable'} ) %></A>
+      </TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> X </B></FONT>' %></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right"><% $region->{'rate'} %></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} eq 'Total' ? '' : '<FONT FACE="sans-serif" SIZE="+1"><B> = </B></FONT>' %></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+        <% $money_char %><% sprintf('%.2f', $region->{'owed'} ) %>
+      </TD>
+% unless ( $cgi->param('show_taxclasses') ) { 
+
+        <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+          <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A>
+        </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>
+  </TR>
+% #some false laziness w/above
+%     $bgcolor1 = '#eeeeee';
+%     $bgcolor2 = '#ffffff';
+%     foreach my $region ( @base_regions ) {
+%
+%       if ( $bgcolor eq $bgcolor1 ) {
+%         $bgcolor = $bgcolor2;
+%       } else {
+%         $bgcolor = $bgcolor1;
+%       }
+%
+%       my $link = '';
+%       #if ( $region->{'label'} ne 'Total' ) {
+%         if ( $region->{'label'} eq $out ) {
+%           $link = ';out=1';
+%         } else {
+%           $link = ';'. $region->{'url_param'};
+%         }
+%       #}
+%  
+
+
+    <TR>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"><% $region->{'label'} %></TD>
+      <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+        <A HREF="<% $baselink. $link %>;istax=1"><% $money_char %><% sprintf('%.2f', $region->{'tax'} ) %></A>
+      </TD>
+    </TR>
+% } 
+%
+%     if ( $bgcolor eq $bgcolor1 ) {
+%       $bgcolor = $bgcolor2;
+%     } else {
+%       $bgcolor = $bgcolor1;
+%     }
+%  
+
+
+  <TR>
+   <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">Total</TD>
+    <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ALIGN="right">
+      <A HREF="<% $baselink %>;istax=1"><% $money_char %><% sprintf('%.2f', $tax ) %></A>
+    </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 $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 $from_join_cust = "
+    FROM cust_bill_pkg
+    $join_cust
+"; 
+my $join_pkg = "
+    LEFT JOIN cust_pkg USING ( pkgnum )
+    LEFT JOIN part_pkg USING ( pkgpart )
+";
+
+my $where = "WHERE _date >= $beginning AND _date <= $ending ";
+my @base_param = qw( county county state state country );
+if ( $conf->exists('tax-ship_address') ) {
+
+  $where .= "
+      AND (    (     ( ship_last IS NULL     OR  ship_last  = '' )
+                 AND ( county       = ? OR ? = '' )
+                 AND ( state        = ? OR ? = '' )
+                 AND   country      = ?
+               )
+            OR (       ship_last IS NOT NULL AND ship_last != ''
+                 AND ( ship_county  = ? OR ? = '' )
+                 AND ( ship_state   = ? OR ? = '' )
+                 AND   ship_country = ?
+               )
+          )
+  ";
+  #    AND payby != 'COMP'
+
+  push @base_param, @base_param;
+
+} else {
+
+  $where .= "
+      AND ( county  = ? OR ? = '' )
+      AND ( state   = ? OR ? = '' )
+      AND   country = ?
+  ";
+  #    AND payby != 'COMP'
+
+}
+
+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 $gotcust = "
+  WHERE 0 < ( SELECT COUNT(*) FROM cust_main
+";
+if ( $conf->exists('tax-ship_address') ) {
+
+  $gotcust .= "
+                WHERE
+
+                (    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 (    cust_main_county.country = cust_main.country )
+                    AND (    cust_main_county.state = cust_main.state
+                          OR cust_main_county.state = ''
+                          OR cust_main_county.state IS NULL )
+                    AND (    cust_main_county.county = cust_main.county
+                          OR cust_main_county.county = ''
+                          OR cust_main_county.county IS NULL )
+                  )
+  
+                  OR
+  
+                  (       ship_last IS NOT NULL AND ship_last != ''
+                    AND (    cust_main_county.country = cust_main.ship_country )
+                    AND (    cust_main_county.state = cust_main.ship_state
+                          OR cust_main_county.state = ''
+                          OR cust_main_county.state IS NULL )
+                    AND (    cust_main_county.county = cust_main.ship_county
+                          OR cust_main_county.county = ''
+                          OR cust_main_county.county IS NULL )
+                  )
+
+                )
+
+                LIMIT 1
+            )
+  ";
+
+} else {
+
+  $gotcust .= "
+                WHERE ( cust_main.county  = cust_main_county.county
+                        OR cust_main_county.county = ''
+                        OR cust_main_county.county IS NULL )
+                  AND ( cust_main.state   = cust_main_county.state
+                        OR cust_main_county.state = ''
+                        OR cust_main_county.state IS NULL )
+                  AND ( cust_main.country = cust_main_county.country )
+                LIMIT 1
+            )
+  ";
+
+}
+
+my($total, $tot_taxable, $owed, $tax) = ( 0, 0, 0, 0, 0 );
+my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0 );
+my $out = 'Out of taxable region(s)';
+my %regions = ();
+foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) {
+  #warn $r->county. ' '. $r->state. ' '. $r->country. "\n";
+
+  my $label = getlabel($r);
+  $regions{$label}->{'label'} = $label;
+  $regions{$label}->{'url_param'} = join(';', map "$_=".$r->$_(), qw( county state country ) );
+
+  my @param = @base_param;
+  my $mywhere = $where;
+
+  if ( $r->taxclass ) {
+
+    $mywhere .= " AND taxclass = ? ";
+    push @param, 'taxclass';
+    $regions{$label}->{'url_param'} .= ';taxclass='. $r->taxclass
+      if $cgi->param('show_taxclasses');
+
+  } else {
+
+    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 ( $r->$opt_field() ) {
+        $same_query .= " AND $opt_field = ?";
+        push @same_param, $opt_field;
+      } else {
+        $same_query .= " AND $opt_field IS NULL";
+      }
+    }
+
+    my @taxclasses = list_sql( $r, \@same_param, $same_query );
+
+    if ( scalar(@taxclasses) ) {
+      $mywhere .= ' AND '. join(' AND ', map ' taxclass != ? ', @taxclasses );
+      push @param, @taxclasses;
+    }
+  
+  }
+
+  my $fromwhere = $from_join_cust. $join_pkg. $mywhere. " AND payby != 'COMP' ";
+
+#  my $label = getlabel($r);
+#  $regions{$label}->{'label'} = $label;
+
+  my $nottax = 'pkgnum != 0';
+
+  ## calculate total for this region
+
+  my $t = scalar_sql($r, \@param,
+    "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) $fromwhere AND $nottax"
+  );
+  $total += $t;
+  $regions{$label}->{'total'} += $t;
+
+  ## 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'"
+#    );
+#  }
+
+  my $x_cust = scalar_sql($r, \@param,
+    "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur)
+     $fromwhere AND $nottax AND tax = 'Y' "
+  );
+
+  $exempt_cust += $x_cust;
+  $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 )
+    "
+  );
+  $exempt_pkg += $x_pkg;
+  $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 $join_pkg
+     $mywhere"
+  );
+#  if ( $x_monthly ) {
+#    #warn $r->taxnum(). ": $x_monthly\n";
+#    $taxable -= $x_monthly;
+#  }
+
+  $exempt_monthly += $x_monthly;
+  $regions{$label}->{'exempt_monthly'} += $x_monthly;
+
+  my $taxable = $t - $x_cust - $x_pkg - $x_monthly;
+
+  $tot_taxable += $taxable;
+  $regions{$label}->{'taxable'} += $taxable;
+
+  $owed += $taxable * ($r->tax/100);
+  $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 $taxwhere = "$from_join_cust $where AND payby != 'COMP' ";
+my @taxparam = @base_param;
+my %base_regions = ();
+#foreach my $label ( keys %regions ) {
+foreach my $r (
+  qsearch( 'cust_main_county',
+           {},
+           "DISTINCT
+              country,
+              state,
+              county,
+              CASE WHEN taxname IS NULL THEN '' ELSE taxname END AS taxname,".
+
+             #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"
+           ,
+           $gotcust
+         )
+) {
+
+  #warn join('-', map { $r->$_() } qw( country state county taxname ) )."\n";
+
+  my $label = getlabel($r);
+
+  #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' ";
+  #my @param = @base_param; 
+
+  #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) ".
+            " $taxwhere AND pkgnum = 0 $named_tax";
+
+  my $x = scalar_sql($r, \@taxparam, $sql );
+  $tax += $x;
+  $regions{$label}->{'tax'} += $x;
+
+  if ( $cgi->param('show_taxclasses') ) {
+    my $base_label = getlabel($r, 'no_taxclass'=>1 );
+    $base_regions{$base_label}->{'label'} = $base_label;
+    $base_regions{$base_label}->{'url_param'} =
+      join(';', map "$_=".$r->$_(), qw( county state country ) );
+    $base_regions{$base_label}->{'tax'} += $x;
+  }
+
+}
+
+#ordering
+my @regions =
+  map $regions{$_},
+  sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
+  keys %regions;
+
+my @base_regions =
+  map $base_regions{$_},
+  sort { ( ($a eq $out) cmp ($b eq $out) ) || ($b cmp $a) }
+  keys %base_regions;
+
+push @regions, {
+  'label'          => 'Total',
+  'url_param'      => '',
+  'total'          => $total,
+  'exempt_cust'    => $exempt_cust,
+  'exempt_pkg'     => $exempt_pkg,
+  'exempt_monthly' => $exempt_monthly,
+  'taxable'        => $tot_taxable,
+  'rate'           => '',
+  'owed'           => $owed,
+  'tax'            => $tax,
+};
+
+#-- 
+
+sub getlabel {
+  my $r = shift;
+  my %opt = @_;
+
+  my $label;
+  if (
+    $r->tax == 0 
+    && ! scalar( qsearch('cust_main_county', { 'state'   => $r->state,
+                                               'county'  => $r->county,
+                                               '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 ) {
+    $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 = "$label (". $r->taxclass. ")"
+      if $r->taxclass
+      && $cgi->param('show_taxclasses')
+      && ! $opt{'no_taxclass'};
+    #$label = $r->taxname. " ($label)" if $r->taxname;
+  }
+  return $label;
+}
+
+#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;
+}
+
+sub list_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;
+  map $_->[0], @{ $sth->fetchall_arrayref };
+}
+
+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";
+
+</%init>
diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html
new file mode 100755 (executable)
index 0000000..35b290c
--- /dev/null
@@ -0,0 +1,42 @@
+<% include('/elements/header.html', 'Tax Report' ) %>
+
+<FORM ACTION="report_tax.cgi" METHOD="GET">
+
+<TABLE>
+
+ <% include( '/elements/tr-select-agent.html' ) %>
+
+ <% include( '/elements/tr-input-beginning_ending.html' ) %>
+% my $conf = new FS::Conf;
+%    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');
+
+</%init>
diff --git a/httemplate/search/rt_transaction.html b/httemplate/search/rt_transaction.html
new file mode 100644 (file)
index 0000000..651f289
--- /dev/null
@@ -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 (file)
index 0000000..df9b8cd
--- /dev/null
@@ -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 (file)
index 0000000..24b17d3
--- /dev/null
@@ -0,0 +1,305 @@
+<% 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(
+%            $beginning, $ending, $cgi_svc_acct, $ip, $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 = '';
+my $ending = '';
+if ( $cgi->param('beginning')
+     && $cgi->param('beginning') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) {
+  $beginning = str2time($1);
+}
+if ( $cgi->param('ending')
+     && $cgi->param('ending') =~ /^([ 0-9\-\/\:\w]{0,54})$/ ) {
+  $ending = str2time($1); # + 86399;
+}
+if ( $cgi->param('begin') && $cgi->param('begin') =~ /^(\d+)$/ ) {
+  $beginning = $1;
+}
+if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) {
+  $ending = $1;
+}
+
+my $cgi_svc_acct = '';
+if ( $cgi->param('svcnum') =~ /^(\d+)$/ ) {
+  $cgi_svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $1 } );
+} elsif ( $cgi->param('username') =~ /^([^@]+)\@([^@]+)$/ ) {
+  my %search = { 'username' => $1 };
+  my $svc_domain = qsearchs('svc_domain', { 'domain' => $2 } );
+  if ( $svc_domain ) {
+    $search{'domsvc'} = $svc_domain->svcnum;
+  } else {
+    delete $search{'username'};
+  }
+  $cgi_svc_acct = qsearchs( 'svc_acct', \%search )
+    if keys %search;
+} elsif ( $cgi->param('username') =~ /^(.+)$/ ) {
+  $cgi_svc_acct = qsearchs( 'svc_acct', { 'username' => $1 } );
+}
+
+my $ip = '';
+if ( $cgi->param('ip') =~ /^((\d+\.){3}\d+)$/ ) {
+  $ip = $1;
+}
+
+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 (file)
index 0000000..660a54f
--- /dev/null
@@ -0,0 +1,59 @@
+<% 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>
+% } 
+
+
+<% include( '/elements/tr-input-beginning_ending.html', '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 (executable)
index 0000000..c2b3d8f
--- /dev/null
@@ -0,0 +1,289 @@
+<% 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,
+             )
+%>
+<%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));
+
+  $return .= sprintf(' (%.2fx)', $seconds / $permonth ) if $permonth;
+
+  $return;
+
+}
+
+</%once>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->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 @extra_sql = ();
+
+my @header = ( '#', 'Service', 'Account', 'UID', 'Last Login' );
+my @fields = ( 'svcnum', 'svc', 'email', 'uid', 'last_login_text' );
+my @links = ( $link, $link, $link, $link, $link );
+my $align = 'rlllr';
+my @color = ( '', '', '', '', '' );
+my @style = ( '', '', '', '', '' );
+
+if ( $cgi->param('domain') ) { 
+  my $svc_domain =
+    qsearchs('svc_domain', { 'domain' => $cgi->param('domain') } );
+  unless ( $svc_domain ) {
+    #it would be nice if this looked more like the other "not found"
+    #errors, but this will do for now.
+    errorpage("Domain ". $cgi->param('domain'). " not found at all");
+  } else {
+    push @extra_sql, 'domsvc = '. $svc_domain->svcnum;
+  }
+}
+
+my $timepermonth = '';
+
+my $orderby = 'ORDER BY svcnum';
+if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+
+  push @extra_sql, 'pkgnum IS NULL'
+    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"
+      if $sortby eq 'uid' || $sortby eq 'seconds' || $sortby eq 'last_login';
+    $orderby = "ORDER BY $sortby";
+  }
+
+  if ( $sortby eq 'seconds' ) {
+    #push @header, 'Time remaining';
+    push @header, 'Time';
+    push @fields, sub { my $svc_acct = shift; format_time($svc_acct->seconds) };
+    push @links, '';
+    $align .= 'r';
+    push @color, '';
+    push @style, '';
+
+    my $conf = new FS::Conf;
+    if ( $conf->exists('svc_acct-display_paid_time_remaining') ) {
+      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;
+          return format_time($seconds) unless $timepermonth;
+          #my $recur = $part_pkg->calc_recur($cust_pkg);
+          my $recur = $part_pkg->base_recur($cust_pkg);
+          my $balance = $cust_pkg->cust_main->balance;
+          my $months_unpaid = $balance / $recur;
+          my $time_unpaid = $months_unpaid * $timepermonth;
+          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, '', '', '', '';
+    }
+
+  }
+
+} 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 = "";
+
+  if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) {
+    push @extra_sql, "agentnum = $1";
+  }
+
+  my $pkgpart = join (' OR cust_pkg.pkgpart=',
+                      grep {$_} map { /^(\d+)$/; } ($cgi->param('pkgpart')));
+  push @extra_sql,  '(cust_pkg.pkgpart=' . $pkgpart . ')' if $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') =~ /^(\d+)$/ ) {
+  push @extra_sql, "popnum = $1";
+  $orderby = "ORDER BY LOWER(username)";
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+  push @extra_sql, "svcpart = $1";
+  $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). ' )';
+
+}
+
+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();
+
+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_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(),
+                  ),
+  'extra_sql' => "$extra_sql $orderby",
+  'addl_from' => $addl_from,
+};
+
+</%init>
diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi
new file mode 100755 (executable)
index 0000000..5671161
--- /dev/null
@@ -0,0 +1,132 @@
+%die "access denied"
+%  unless $FS::CurrentUser::CurrentUser->access_right('List services');
+%
+%my $conf = new FS::Conf;
+%
+%my @svc_broadband = ();
+%my $sortby=\*svcnum_sort;
+%#XXX agent-virtualization needs to be finished :/
+%my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql( 
+%                      'null_right' => 'View/link unlinked services'
+%                    );
+%
+%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+%
+%  @svc_broadband = qsearch(
+%    'table'     => 'svc_broadband',
+%    'hashref'   => {},
+%    #needs the join first 'extra_sql' => "WHERE $agentnums_sql",
+%  );
+%
+%  if ( $cgi->param('magic') eq 'unlinked' ) {
+%    @svc_broadband = grep { qsearchs('cust_svc', {
+%                                                   'svcnum' => $_->svcnum,
+%                                                   'pkgnum' => '',
+%                                                 }
+%                                    )
+%                          }
+%                    @svc_broadband;
+%  } else {
+%
+%  if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+%    my $sortby = $1;
+%    if ( $sortby eq 'blocknum' ) {
+%      $sortby = \*blocknum_sort;
+%    }
+%  }
+%
+%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+%
+%  @svc_broadband =
+%    qsearch( {
+%               'table'     => 'svc_broadband',
+%               'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum )',
+%               'extra_sql' => "WHERE svcpart = $1",
+%             }
+%           );
+%
+%} elsif ( $cgi->param('ip_addr') =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) {
+%  my $ip_addr = $1;
+%  @svc_broadband = qsearchs('svc_broadband',{'ip_addr'=>$ip_addr});
+%}
+%
+%my %routerbyblock = ();
+%foreach my $router (qsearch('router', {})) {
+%  foreach ($router->addr_block) {
+%    $routerbyblock{$_->blocknum} = $router;
+%  }
+%}
+%
+%if ( scalar(@svc_broadband) == 1 ) {
+%  print $cgi->redirect(popurl(2). "view/svc_broadband.cgi?". $svc_broadband[0]->svcnum);
+%  #exit;
+%} elsif ( scalar(@svc_broadband) == 0 ) {
+%
+
+<!-- mason kludge -->
+%
+% errorpage("No matching broadband services found!");
+%} else {
+%
+
+<!-- mason kludge -->
+%
+%  my($total)=scalar(@svc_broadband);
+%  print header("Broadband Search Results",''), <<END;
+%
+%    $total matching broadband services found
+%    <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
+%      <TR>
+%        <TH>Service #</TH>
+%      <TH>Router</TH>
+%        <TH>IP Address</TH>
+%      </TR>
+%END
+%
+%  foreach my $svc_broadband (
+%    sort $sortby (@svc_broadband)
+%  ) {
+%    my($svcnum,$ip_addr,$routername,$routernum)=(
+%      $svc_broadband->svcnum,
+%      $svc_broadband->ip_addr,
+%      $routerbyblock{$svc_broadband->blocknum}->routername,
+%      $routerbyblock{$svc_broadband->blocknum}->routernum,
+%    );
+%
+%    my $rowspan = 1;
+%
+%    print <<END;
+%    <TR>
+%      <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$svcnum</A></TD>
+%      <TD ROWSPAN=$rowspan><A HREF="${p}view/router.cgi?$routernum">$routername</A></TD>
+%      <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_broadband.cgi?$svcnum">$ip_addr</A></TD>
+%END
+%
+%    #print @rows;
+%    print "</TR>";
+%
+%  }
+% 
+%  print <<END;
+%    </TABLE>
+%  </BODY>
+%</HTML>
+%END
+%
+%}
+%
+%sub svcnum_sort {
+%  $a->getfield('svcnum') <=> $b->getfield('svcnum');
+%}
+%
+%sub blocknum_sort {
+%  if ($a->getfield('blocknum') == $b->getfield('blocknum')) {
+%    $a->getfield('ip_addr') cmp $b->getfield('ip_addr');
+%  } else {
+%    $a->getfield('blocknum') cmp $b->getfield('blocknum');
+%  }
+%}
+%
+%
+%
+
diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi
new file mode 100755 (executable)
index 0000000..08ffdba
--- /dev/null
@@ -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 (executable)
index 0000000..2710d75
--- /dev/null
@@ -0,0 +1,153 @@
+%die "access denied"
+%  unless $FS::CurrentUser::CurrentUser->access_right('List services');
+%
+%my $conf = new FS::Conf;
+%
+%my @svc_external = ();
+%my @h_svc_external = ();
+%my $sortby=\*svcnum_sort;
+%if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) {
+%
+%  @svc_external=qsearch('svc_external',{});
+%
+%  if ( $cgi->param('magic') eq 'unlinked' ) {
+%    @svc_external = grep { qsearchs('cust_svc', {
+%                                                  'svcnum' => $_->svcnum,
+%                                                  'pkgnum' => '',
+%                                                }
+%                                   )
+%                         }
+%                   @svc_external;
+%  }
+%
+%  if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+%    my $sortby = $1;
+%    if ( $sortby eq 'id' ) {
+%      $sortby = \*id_sort;
+%    }
+%  }
+%
+%} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+%
+%  @svc_external =
+%    qsearch( 'svc_external', {}, '',
+%               " WHERE $1 = ( SELECT svcpart FROM cust_svc ".
+%               "              WHERE cust_svc.svcnum = svc_external.svcnum ) "
+%    );
+%
+%} elsif ( $cgi->param('title') =~ /^(.*)$/ ) {
+%  $sortby=\*id_sort;
+%  @svc_external=qsearch('svc_external',{ title => $1 });
+%  if( $cgi->param('history') == 1 ) {
+%    @h_svc_external=qsearch('h_svc_external',{ title => $1 });
+%  }
+%} elsif ( $cgi->param('id') =~ /^([\w\-\.]+)$/ ) {
+%  my $id = $1;
+%  @svc_external = qsearchs('svc_external',{'id'=>$id});
+%}
+%
+%if ( scalar(@svc_external) == 1 ) {
+%
+%  
+<% $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum) %>
+%
+%
+%} elsif ( scalar(@svc_external) == 0 ) {
+%
+%  
+<% include('/elements/header.html', 'External Search Results' ) %>
+
+  No matching external services found
+% } else {
+%
+%  
+<% include('/elements/header.html', 'External Search Results', '') %>
+
+    <% scalar(@svc_external) %> matching external services found
+    <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
+      <TR>
+        <TH>Service #</TH>
+        <TH><% FS::Msgcat::_gettext('svc_external-id') || 'External&nbsp;ID' %></TH>
+        <TH><% FS::Msgcat::_gettext('svc_external-title') || 'Title' %></TH>
+      </TR>
+%
+%  foreach my $svc_external (
+%    sort $sortby (@svc_external)
+%  ) {
+%    my($svcnum, $id, $title)=(
+%      $svc_external->svcnum,
+%      $svc_external->id,
+%      $svc_external->title,
+%    );
+%
+%    my $rowspan = 1;
+%
+%    print <<END;
+%    <TR>
+%      <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$svcnum</A></TD>
+%      <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$id</A></TD>
+%      <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_external.cgi?$svcnum">$title</A></TD>
+%END
+%
+%    #print @rows;
+%    print "</TR>";
+%
+%  }
+%  if( scalar(@h_svc_external) > 0 ) {
+%    print <<HTML;
+%    </TABLE>
+%    <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
+%      <TR>
+%        <TH>Freeside ID</TH>
+%        <TH>Service #</TH>
+%        <TH>Title</TH>
+%        <TH>Date</TH>
+%      </TR>
+%HTML
+%
+%    foreach my $h_svc ( @h_svc_external ) {
+%        my($svcnum, $id, $title, $user, $date)=(
+%            $h_svc->svcnum,
+%            $h_svc->id,
+%            $h_svc->title,
+%            $h_svc->history_user,
+%            $h_svc->history_date,
+%        );
+%        my $rowspan = 1;
+%        my ($h_cust_svc) = qsearchs( 'h_cust_svc', {
+%            svcnum  =>  $svcnum,
+%        });
+%        my $cust_pkg = qsearchs( 'cust_pkg', {
+%            pkgnum  =>  $h_cust_svc->pkgnum,
+%        });
+%        my $custnum = $cust_pkg->custnum;
+%
+%        print <<END;
+%        <TR>
+%          <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$custnum</A></TD>
+%          <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$svcnum</A></TD>
+%          <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$title</A></TD>
+%          <TD ROWSPAN=$rowspan><A HREF="${p}view/cust_main.cgi?$custnum">$date</A></TD>
+%        </TR>
+%END
+%    }
+%  }
+%
+%  print <<END;
+%    </TABLE>
+%  </BODY>
+%</HTML>
+%END
+%
+%}
+%
+%sub svcnum_sort {
+%  $a->getfield('svcnum') <=> $b->getfield('svcnum');
+%}
+%
+%sub id_sort {
+%  $a->getfield('id') <=> $b->getfield('id');
+%}
+%
+%
+
diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi
new file mode 100755 (executable)
index 0000000..2bcd0c8
--- /dev/null
@@ -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 (file)
index 0000000..49340c6
--- /dev/null
@@ -0,0 +1,117 @@
+<% include( 'elements/search.html',
+                 'title'             => "Phone number search results",
+                 'name'              => 'phone numbers',
+                 'query'             => $sql_query,
+                 'count_query'       => $count_query,
+                 'redirect'          => $link,
+                 'header'            => [ '#',
+                                          'Service',
+                                          'Country code',
+                                          'Phone number',
+                                          FS::UI::Web::cust_header(),
+                                        ],
+                 'fields'            => [ 'svcnum',
+                                          'svc',
+                                          'countrycode',
+                                          'phonenum',
+                                          \&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 $orderby = 'ORDER BY svcnum';
+my %svc_phone = ();
+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('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',
+                    'cust_main.custnum',
+                    FS::UI::Web::cust_sql_fields(),
+                 ),
+  'extra_sql' => "$extra_sql $orderby",
+  'addl_from' => $addl_from,
+};
+
+my $link = [ "${p}view/svc_phone.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_www.cgi b/httemplate/search/svc_www.cgi
new file mode 100755 (executable)
index 0000000..2e3c461
--- /dev/null
@@ -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 (file)
index 0000000..b72dd0e
--- /dev/null
@@ -0,0 +1,117 @@
+<% 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' => sub {
+                                  '<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>
+
+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 $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]; } ];
+
+</%init>
diff --git a/httemplate/view/cust_bill-logo.cgi b/httemplate/view/cust_bill-logo.cgi
new file mode 100755 (executable)
index 0000000..9c1c1d7
--- /dev/null
@@ -0,0 +1,21 @@
+<% $conf->config_binary("logo$templatename.png") %>
+<%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($query) = $cgi->keywords;
+$query =~ /^([^\.\/]*)$/;
+my $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 (executable)
index 0000000..f09e1b7
--- /dev/null
@@ -0,0 +1,28 @@
+<% $pdf %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)(.pdf)?$/;
+my $templatename = $2;
+my $invnum = $3;
+
+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( '', $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_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi
new file mode 100755 (executable)
index 0000000..5313dbf
--- /dev/null
@@ -0,0 +1,24 @@
+<% $cust_bill->print_ps( '', $templatename) %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $templatename = $2;
+my $invnum = $3;
+
+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 (executable)
index 0000000..9517255
--- /dev/null
@@ -0,0 +1,119 @@
+<% include("/elements/header.html",'Invoice View', menubar(
+  "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+)) %>
+
+
+% if ( $cust_bill->owed > 0
+%        && ( $payby{'BILL'} || $payby{'CASH'} || $payby{'WEST'} || $payby{'MCRD'} )
+%      )
+%   {
+%     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>
+% } 
+
+
+<A HREF="<% $p %>misc/print-invoice.cgi?<% $link %>">Re-print this invoice</A>
+% if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { 
+
+  | <A HREF="<% $p %>misc/email-invoice.cgi?<% $link %>">Re-email
+      this invoice</A>
+% } 
+% if ( $conf->exists('hylafax') && length($cust_bill->cust_main->fax) ) { 
+
+  | <A HREF="<% $p %>misc/fax-invoice.cgi?<% $link %>">Re-fax
+      this invoice</A>
+% } 
+
+
+<BR><BR>
+% if ( $conf->exists('invoice_latex') ) { 
+
+  <A HREF="<% $p %>view/cust_bill-pdf.cgi?<% $link %>.pdf">View typeset invoice</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('', $templatename) ) %>
+% } else { 
+
+  <PRE><% join('', $cust_bill->print_text('', $templatename) ) %></PRE>
+% } 
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('View invoices');
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^((.+)-)?(\d+)$/;
+my $templatename = $2;
+my $invnum = $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_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 $custnum = $cust_bill->custnum;
+
+#my $printed = $cust_bill->printed;
+
+my $link = $templatename ? "$templatename-$invnum" : $invnum;
+
+</%init>
+
+
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
new file mode 100755 (executable)
index 0000000..4ca777d
--- /dev/null
@@ -0,0 +1,187 @@
+<% include("/elements/header.html","Customer View: ". $cust_main->name ) %>
+
+% if ( $curuser->access_right('Edit customer') ) { 
+  <A HREF="<% $p %>edit/cust_main.cgi?<% $custnum %>">Edit this customer</A> | 
+% } 
+
+<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>
+
+<SCRIPT TYPE="text/javascript">
+function areyousure(href, message) {
+  if (confirm(message) == true)
+    window.location.href = href;
+}
+</SCRIPT>
+
+% if ( $curuser->access_right('Cancel customer')
+%        && $cust_main->ncancelled_pkgs
+%      ) {
+
+  <% cust_cancel_link($cust_main) %> | 
+
+% } 
+
+% 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 ( $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>
+% my $notecount = scalar($cust_main->notes());
+% if ( ! $conf->exists('cust_main-disable_notes') || $notecount) {
+
+<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')
+%      ) {
+
+  <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_main_note.cgi?custnum=<% $cust_main->custnum %>', 616, 386, 'cust_main_note_popup' ), CAPTION, 'Enter customer note', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK); return false;">Add customer note</A>
+
+%   }
+
+<BR>
+
+%   if ($notecount) {
+
+<iframe src="<% $p %>view/cust_main/notes.html?custnum=<% $cust_main->custnum %>" height="186" width="616" name="cust_main_notes" frameborder="0" marginborder="0" marginheight="0" scrolling="auto">
+  <div><br>[iframe not supported]<br><br></div>
+</iframe>
+
+%   }else{ # make firefox happy wrt POSTDATA
+
+<iframe src="<% $p %>view/cust_main/notes.html?custnum=<% $cust_main->custnum %>" height="24" width="616" name="cust_main_notes" frameborder="0" marginborder="0" marginheight="0" scrolling="auto">
+  <div><br>[iframe not supported]<br><br></div>
+</iframe>
+
+%   }
+
+% }
+
+
+% if ( $conf->config('ticket_system') ) { 
+
+  <BR><BR>
+  <% include('cust_main/tickets.html', $cust_main ) %>
+% } 
+
+
+<BR><BR>
+
+% #XXX enable me# if ( $curuser->access_right('View customer packages') { 
+<% include('cust_main/packages.html', $cust_main ) %>
+% #}
+
+% if ( $conf->config('payby-default') ne 'HIDE' ) { 
+  <% include('cust_main/payment_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;
+
+die "No customer specified (bad URL)!" unless $cgi->keywords;
+my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
+$query =~ /^(\d+)$/;
+my $custnum = $1;
+my $cust_main = qsearchs( {
+  'table'     => 'cust_main',
+  'hashref'   => { 'custnum' => $custnum },
+  'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die "Customer not found!" unless $cust_main;
+
+</%init>
+<%once>
+
+
+sub cust_cancel_link { cust_popup_link( 'misc/cancel_cust.html',
+                                        'Cancel&nbsp;this&nbsp;customer',
+                                        'Confirm Cancellation',
+                                        '#ff0000',
+                                        @_,
+                                      );
+}
+
+#false laziness w/view/cust_main/packages.html
+
+sub cust_popup_link {
+  my($action, $label, $actionlabel, $color, $cust_main) = @_;
+  $action .= '?'. $cust_main->custnum;
+  popup_link($action, $label, $actionlabel, $color);
+}
+
+sub popup_link {
+  my($action, $label, $actionlabel, $color) = @_;
+  $color ||= '#333399';
+  qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('$p$action', 540, 336, 'pkg_or_svc_action_popup' ), CAPTION, '$actionlabel', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '$color', CGCOLOR, '$color', CLOSETEXT, '' ); return false;">$label</A>!;
+
+# CLOSETEXT, '', 
+#WIDTH, 576, HEIGHT, 128, TEXTSIZE, 3,
+#BGCOLOR, '#ff0000', CGCOLOR, '#ff0000'
+}
+
+</%once>
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
new file mode 100644 (file)
index 0000000..fa3863b
--- /dev/null
@@ -0,0 +1,214 @@
+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->payinfo );
+%
+
+
+    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"><% 'x'x(length($account)-2). substr($account,(length($account)-2)) %></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>
+% } 
+
+
+<TR>
+  <TD ALIGN="right">Tax&nbsp;exempt</TD>
+  <TD BGCOLOR="#ffffff"><% $cust_main->tax ? 'yes' : 'no' %></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Postal&nbsp;invoices</TD>
+  <TD BGCOLOR="#ffffff">
+    <% ( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no' %>
+  </TD>
+</TR>
+<TR>
+  <TD ALIGN="right">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>
+% } 
+
+</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') || '$';
+
+</%init>
diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html
new file mode 100644 (file)
index 0000000..e88c02e
--- /dev/null
@@ -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 (file)
index 0000000..9528db2
--- /dev/null
@@ -0,0 +1,112 @@
+%
+%  my( $cust_main ) = @_;
+%  my $conf = new FS::Conf;
+%  my $date_format = ($conf->config('date_format') || "%m/%d/%Y");
+%
+
+
+<% ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
+
+<TR>
+  <TD ALIGN="right">Customer&nbsp;number</TD>
+  <TD BGCOLOR="#ffffff"><% $cust_main->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 @agents = qsearch( 'agent', {} );
+%  my $agent;
+%  unless ( scalar(@agents) == 1 ) {
+%    $agent = qsearchs('agent',{ 'agentnum' => $cust_main->agentnum } );
+%
+
+
+<TR>
+  <TD ALIGN="right">Agent</TD>
+  <TD BGCOLOR="#ffffff"><% $agent->agentnum %>: <% $agent->agent %></TD>
+</TR>
+%
+%  } else {
+%    $agent = $agents[0];
+%  }
+%
+%  if ( $cust_main->agent_custid ) {
+%
+
+
+<TR>
+  <TD ALIGN="right">Agent customer ref#</TD>
+  <TD BGCOLOR="#ffffff"><% $cust_main->agent_custid %></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 = DateTime->from_epoch(epoch => $cust_main->birthdate,
+%                                  time_zone=>'floating',
+%                                 );
+
+  <TR>
+    <TD ALIGN="right">Date of Birth</TD>
+    <TD BGCOLOR="#ffffff"><% $cust_main->birthdate ne '' ? $dt->strftime($date_format) : '' %></TD>
+  </TR>
+
+% }
+
+</TABLE></TD></TR></TABLE>
+
diff --git a/httemplate/view/cust_main/notes.html b/httemplate/view/cust_main/notes.html
new file mode 100755 (executable)
index 0000000..f2d1169
--- /dev/null
@@ -0,0 +1,93 @@
+%
+% my $conf = new FS::Conf;
+% my $curuser = $FS::CurrentUser::CurrentUser;
+%
+% $cgi->param('custnum') =~ /^(\d+)$/
+%   or die "No customer specified (bad URL)!";
+% my $custnum = $1;
+%
+% my $cust_main = qsearchs('cust_main', {'custnum' => $custnum} );
+% die "Custimer not found!" unless $cust_main;
+%
+
+<STYLE TYPE="text/css">
+
+body { background: #e8e8e8 }
+.inv table { border: none }
+.inv TH { border: none }
+.inv TD { border: none }
+
+</STYLE>
+
+% my (@notes) = $cust_main->notes();
+% if ( scalar(@notes) ) { 
+
+<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_crossframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/iframecontentmws.js"></SCRIPT>
+
+<TABLE CLASS="inv" CELLSPACING=0 CELLPADDING=0 BORDER=0 >
+
+%   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 $clickjs = qq!onclick="overlib( OLiframeContent('${pop}edit/! .
+%                 qq!cust_main_note.cgi?custnum=$custnum&! .
+%                 qq!notenum=$notenum', 616, ! .
+%                 qq!386, 'cust_main_note_popup' ), CAPTION, 'Edit customer ! .
+%                 qq!note', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, ! .
+%                 qq!CLOSECLICK, FRAME, top); return false;"!;
+%
+%   my ($el, $eel);
+%   if ($curuser->access_right('Edit customer note') ) {
+%     $el  = qq!<A HREF="javascript:void(0);" $clickjs>!;
+%     $eel = qq!</A>!;
+%   }else{
+%     $el = $eel = '';
+%   }
+
+<TR>
+  <% note_datestr($note,$conf,$bgcolor, $el, $eel) %>
+  <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+    <% $el %> &nbsp;<%$note->otaker%>&nbsp; <% $eel %>
+  </TD>
+  <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+    &nbsp;<%$note->comments%>
+  </TD>
+</TR>
+
+% } #end display notes
+
+</TABLE>
+
+% } 
+%
+%#subroutines
+%
+%sub note_datestr {
+% my($note, $conf, $bgcolor, $el, $eel) = @_ or return '';
+% my $format=qq{<TD class="inv" bgcolor="$bgcolor" align="left">$el<B>%b</B>$eel</TD>}.
+%            qq{<TD class="inv" bgcolor="$bgcolor" align="right">$el<B>&nbsp;%o,</B>$eel</TD>}.
+%            qq{<TD class="inv" bgcolor="$bgcolor" align="right">$el<B>&nbsp;%Y&nbsp;</B>$eel</TD>};
+% $format .= qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="right">$el<B>&nbsp;%l$eel</TD>}.
+%            qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="center">$el<B>:</B>$eel</TD>}.
+%            qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="left">$el<B>%M</B>$eel</TD>}.
+%            qq{<TD class="inv" bgcolor="$bgcolor" ALIGN="left">$el<B>&nbsp;%P&nbsp;</B>$eel</TD>}
+%     if $conf->exists('cust_main_note-display_times');
+%   ( my $strip = time2str($format, $note->_date) ) =~ s/ (\d)/$1/g;
+%   $strip;
+% }
+%
+
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
new file mode 100755 (executable)
index 0000000..167c625
--- /dev/null
@@ -0,0 +1,628 @@
+<A NAME="cust_pkg"><FONT SIZE="+2">Packages</FONT></A><BR>
+
+% my $s = 0;
+% if ( $curuser->access_right('Order customer package') ) { 
+  <% $s++ ? ' | ' : '' %>
+  <% order_pkg_link($cust_main) %>
+% } 
+
+% if ( $curuser->access_right('One-time charge')
+%        && $conf->config('payby-default') ne 'HIDE'
+%      ) {
+%
+  <% $s++ ? ' | ' : '' %>
+  <% popup_link('edit/quick-charge.html?custnum='. $cust_main->custnum, 'One-time charge', 'One-time charge', '#333399', 545) %>
+% } 
+% 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>
+% 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')
+%             )
+%        )
+%     {
+%       $cgi->param('showcancelledpackages', 1);
+%
+
+  ( <a href="<% $cgi->self_url %>">show
+%   } else {
+%       $cgi->param('showcancelledpackages', 0);
+%
+
+  ( <a href="<% $cgi->self_url %>">hide
+%   } 
+
+ cancelled packages</a> )
+% } 
+% 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>
+  <TH CLASS="grid" BGCOLOR="#cccccc">Services</TH>
+</TR>
+
+%foreach my $cust_pkg (@$packages) {
+%
+%  my $part_pkg = $cust_pkg->part_pkg;
+%
+%  if ( $bgcolor eq $bgcolor1 ) {
+%    $bgcolor = $bgcolor2;
+%  } else {
+%    $bgcolor = $bgcolor1;
+%  }
+
+
+<!--pkgnum: <% $cust_pkg->pkgnum %>-->
+<TR>
+
+  <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+
+    <A NAME="cust_pkg<% $cust_pkg->pkgnum %>" ID="cust_pkg<% $cust_pkg->pkgnum %>"><% $cust_pkg->pkgnum %></A>:
+    <% $part_pkg->pkg %> - <% $part_pkg->comment %>
+    <BR>
+
+    <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('Customize customer package') ) { $br=1;
+
+            (&nbsp;<%pkg_customize_link($cust_pkg,$cust_main->custnum)%>&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>
+
+  <TD CLASS="inv" BGCOLOR="<% $bgcolor %>">
+    <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%">
+%
+%  sub myfreq {
+%    my $part_pkg = shift;
+%    my $freq = $part_pkg->freq_pretty;
+%    $freq =~ s/ /&nbsp;/g;
+%    $freq;
+%  }
+%
+%  #this should use cust_pkg->status and cust_pkg->statuscolor eventually
+%  #my $colspan = $conf->exists('cust_pkg-display_times') ? 8 : 4;
+%  #my $width = $conf->exists('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';
+%  }
+%
+%
+% if ( $cust_pkg->get('cancel') ) { #status: cancelled
+
+    <% pkg_status_row($cust_pkg, 'Cancelled', 'cancel', 'color'=>'FF0000' ) %>
+
+    <% pkg_status_row_colspan(
+         ( $cust_pkg->last_reason ? $cust_pkg->last_reason->reason : '' ), '',
+         'align' => 'right', 'color' => 'ff0000', 'size' => '-2',
+       )
+    %>
+
+%   unless ( $cust_pkg->get('setup') ) { 
+
+        <% pkg_status_row_colspan('Never billed') %>
+
+%   } else { 
+
+       <% pkg_status_row( $cust_pkg, 'Setup', 'setup' ) %>
+       <% pkg_status_row_changed( $cust_pkg ) %>
+       <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill' ) %>
+       <% pkg_status_row_if( $cust_pkg, 'Suspended', 'susp' ) %>
+
+%   } 
+%
+% } else { 
+%
+%   if ( $cust_pkg->get('susp') ) { #status: suspended
+
+    <% pkg_status_row( $cust_pkg, 'Suspended', 'susp', 'color'=>'FF9900' ) %>
+
+    <% pkg_status_row_colspan(
+         ( $cust_pkg->last_reason ? $cust_pkg->last_reason->reason : '' ), '',
+         'align' => 'right', 'color' => 'FF9900', 'size' => '-2',
+       )
+    %>
+
+%   unless ( $cust_pkg->get('setup') ) { 
+      <% pkg_status_row_colspan('Never billed') %>
+%   } else { 
+      <% pkg_status_row($cust_pkg, 'Setup', 'setup' ) %>
+%   } 
+
+    <% pkg_status_row_changed( $cust_pkg ) %>
+    <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill' ) %>
+%   # pkg_status_row($cust_pkg, 'Next bill', 'bill')
+    <% pkg_status_row_if( $cust_pkg, 'Expires', 'expire' ) %>
+
+    <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('Not&nbsp;yet&nbsp;billed&nbsp;(one-time&nbsp;charge)') %>
+
+          <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("Not&nbsp;yet&nbsp;billed&nbsp;($billed_or_prepaid&nbsp;". myfreq($part_pkg). ')' ) %>
+
+%       } 
+%
+%     } else { #setup
+%
+%       unless ( $part_pkg->freq ) { 
+
+          <% pkg_status_row_colspan('One-time&nbsp;charge') %>
+
+          <% pkg_status_row($cust_pkg, 'Billed', 'setup') %>
+
+%       } else { 
+%
+%         if (scalar($cust_pkg->overlimit)) {
+
+            <% pkg_status_row_colspan(
+                 'Overlimit',
+                 $billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
+                 'color' => 'FFD000',
+               )
+            %>
+
+%         } else {
+            <% pkg_status_row_colspan(
+                 'Active',
+                 $billed_or_prepaid. '&nbsp;'. myfreq($part_pkg),
+                 'color' => '00CC00',
+               )
+            %>
+%         } 
+
+          <% pkg_status_row($cust_pkg, 'Setup', 'setup') %>
+
+%       } 
+%
+%     } 
+
+      <% pkg_status_row_changed( $cust_pkg ) %>
+      <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill' ) %>
+      <% pkg_status_row_if( $cust_pkg, $next_bill_or_prepaid_until, 'bill' ) %>
+      <% pkg_status_row_if( $cust_pkg, 'Will suspend on', 'adjourn' ) %>
+      <% pkg_status_row_if( $cust_pkg, 'Expires', 'expire' ) %>
+
+%     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('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>
+
+<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>
+      </TR>
+
+      <TR>
+        <TD ALIGN="right" COLSPAN="2" VALIGN="top" STYLE="padding-bottom:1px;padding-top:0px"><FONT SIZE="-2" COLOR="#FFD000">
+
+            <% $cust_svc->overlimit ? "Overlimit: ". time2str('%b %o %Y' . ($conf->exists('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"><FONT SIZE="-2">
+
+%         if ( $curuser->access_right('Unprovision customer service') ) { 
+            (&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=2 ALIGN="center" STYLE="padding-bottom:4px;padding-top:0px">
+          <B><% svc_provision_link($cust_pkg, $part_svc, $conf, $curuser) %></B>
+        </TD>
+      </TR>
+
+%   } 
+
+% } 
+
+</TABLE>
+</TD>
+% } #end display packages
+%
+
+
+</TABLE>
+% } else { 
+
+<BR>
+% } 
+% 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 = get_packages($cust_main, $conf);
+
+my $colspan = $conf->exists('cust_pkg-display_times') ? 8 : 4;
+my $width = $conf->exists('cust_pkg-display_times') ? '38%' : '56%';
+
+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);
+  $html   .= qq(</TD>);
+  $html   .= pkg_datestr($cust_pkg, $field, $conf).'</TR>';
+
+  $html;
+}
+
+sub pkg_status_row_if {
+  my( $cust_pkg, $title, $field, %opt ) = @_;
+  $cust_pkg->get($field) ? pkg_status_row(@_) : '';
+}
+
+sub pkg_status_row_changed {
+  my($cust_pkg) = @_;
+  return '' unless $cust_pkg->change_date;
+  my $html = pkg_status_row( $cust_pkg, 'Package&nbsp;changed', 'change_date' );
+  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. ' - '. $part_pkg->comment;
+    $html .= pkg_status_row_colspan( $label, '', size=>'-1', align=>'right' );
+  }
+  $html;
+}
+
+sub pkg_status_row_colspan {
+  my($title, $addl, %opt) = @_;
+
+  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);
+  $html   .= qq(</TD></TR>);
+
+  $html;
+
+}
+
+</%init>
+<%once>
+
+#subroutines
+
+sub get_packages {
+  my $cust_main = shift or return undef;
+  my $conf = shift;
+  
+  my @packages = ();
+  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';
+  }
+
+  [ $cust_main->$method() ];
+}
+
+sub svc_provision_link {
+  my ($cust_pkg, $part_svc, $conf, $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
+       && $conf->exists('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 ( $conf->exists('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>!;
+}
+
+sub pkg_datestr {
+  my($cust_pkg, $field, $conf) = @_ 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 $conf->exists('cust_pkg-display_times');
+  my $strip = time2str($format, $cust_pkg->get($field) );
+  $strip =~ s/ (\d)/$1/g;
+  $strip;
+}
+
+sub pkg_change_link    { pkg_popup_link('misc/change_pkg.cgi?dummy=value',
+                                          'Change&nbsp;package',
+                                          'Change',
+                                          '',
+                                          @_
+                                       );
+                       }
+
+sub pkg_suspend_link   { pkg_popup_link( 'misc/cancel_pkg.html?method=suspend',
+                                         'Suspend&nbsp;now',
+                                         'Suspend',
+                                         '#FF9900',
+                                         @_
+                                       );
+                       }
+
+sub pkg_adjourn_link   { pkg_popup_link( 'misc/cancel_pkg.html?method=adjourn',
+                                         'Suspend&nbsp;later',
+                                         'Adjourn',
+                                         '#CC6600',
+                                         @_
+                                       );
+                       }
+
+sub pkg_unsuspend_link { pkg_link('misc/unsusp_pkg',    'Unsuspend',           @_ ); }
+sub pkg_expire_link    { pkg_link('misc/expire_pkg',    'Cancel&nbsp;later',   @_ ); }
+sub pkg_dates_link     { pkg_link('edit/REAL_cust_pkg', 'Edit&nbsp;dates',     @_ ); }
+
+sub pkg_cancel_link    { pkg_popup_link( 'misc/cancel_pkg.html?method=cancel',
+                                         'Cancel&nbsp;now',
+                                         'Cancel',
+                                         '#ff0000',
+                                         @_
+                                       );
+                       }
+
+sub pkg_expire_link    { pkg_popup_link( 'misc/cancel_pkg.html?method=expire',
+                                         'Cancel&nbsp;later',
+                                         'Expire', #"Cancel package $num later"
+                                         '#CC0000',
+                                         @_
+                                       );
+                       }
+
+sub svc_recharge_link  { svc_popup_link( 'misc/recharge_svc.html',
+                                         'Recharge',
+                                         'Recharge',
+                                         '#333399',
+                                         @_
+                                       );
+                       }
+
+sub order_pkg_link { cust_popup_link( 'misc/order_pkg.html',
+                                      'Order&nbsp;new&nbsp;package',
+                                      'Order new package',
+                                      '#333399',
+                                      @_
+                                    );
+                   }
+
+sub pkg_event_link {
+  my($cust_pkg) = @_;
+  qq!<a href="${p}search/cust_event.html?pkgnum=!. $cust_pkg->pkgnum. qq!">!.
+  'View package events'.
+  '</a>';
+}
+
+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_popup_link {
+  my($action, $label, $actionlabel, $color, $cust_pkg) = @_;
+  $action .= '&pkgnum='. $cust_pkg->pkgnum;
+  $actionlabel .= ' package '. $cust_pkg->pkgnum;
+  popup_link($action, $label, $actionlabel, $color, 768);
+}
+
+sub svc_popup_link {
+  my($action, $label, $actionlabel, $color, $cust_svc) = @_;
+  $action .= '?svcnum='. $cust_svc->svcnum;
+  $actionlabel .= ' service '. $cust_svc->svcnum;
+  popup_link($action, $label, $actionlabel, $color);
+}
+
+sub cust_popup_link {
+  my($action, $label, $actionlabel, $color, $cust_main) = @_;
+  $action .= '?'. $cust_main->custnum;
+  popup_link($action, $label, $actionlabel, $color);
+}
+
+sub popup_link {
+  my($action, $label, $actionlabel, $color, $width) = @_;
+  $color ||= '#333399';
+  $width ||= 540;
+  qq!<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('$p$action', $width, 336, 'pkg_or_svc_action_popup' ), CAPTION, '$actionlabel', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '$color', CGCOLOR, '$color' ); return false;">$label</A>!;
+}
+
+sub pkg_customize_link {
+  my $cust_pkg = shift or return '';
+  my $custnum = $cust_pkg->custnum;
+  qq!<A HREF="${p}edit/part_pkg.cgi?!.
+    "keywords=$custnum;".
+    "clone=". $cust_pkg->part_pkg->pkgpart. ';'.
+    "pkgnum=". $cust_pkg->pkgnum.
+    qq!">Customize</A>!;
+}
+
+</%once>
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
new file mode 100644 (file)
index 0000000..44a8885
--- /dev/null
@@ -0,0 +1,630 @@
+<BR><BR><A NAME="history"><FONT SIZE="+2">Payment History</FONT></A><BR>
+
+% my $s = 0;
+% if ( $payby{'BILL'} && $curuser->access_right('Post payment') ) { 
+  <% $s++ ? ' | ' : '' %>
+  <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_pay.cgi?popup=1;payby=BILL;custnum=<% $custnum %>', 392, 336, 'cust_pay_popup' ), CAPTION, 'Enter check payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter check payment</A>
+% } 
+
+% if ( $payby{'CASH'} && $curuser->access_right('Post payment') ) { 
+  <% $s++ ? ' | ' : '' %>
+  <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_pay.cgi?popup=1;payby=CASH;custnum=<% $custnum %>', 392, 336, 'cust_pay_popup' ), CAPTION, 'Enter cash payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter cash payment</A>
+% } 
+
+% 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) credit card payment</A>
+% } 
+
+<BR>
+
+% if ( $curuser->access_right('Post credit') ) { 
+  <A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<% $p %>edit/cust_credit.cgi?<% $custnum %>', 392, 336, 'cust_credit_popup' ), CAPTION, 'Enter credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">Enter credit</A>
+  <BR>
+% } 
+
+% if ( $curuser->access_right('View customer tax exemptions') ) { 
+  <A HREF="<% $p %>search/cust_tax_exempt_pkg.cgi?custnum=<% $custnum %>">View tax exemptions</A>
+  <BR>
+% } 
+
+% if ( $conf->exists('batch-enable')
+%      && $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>
+% } 
+
+%#get payment history
+%my @history = ();
+%
+%#invoices
+%foreach my $cust_bill ($cust_main->cust_bill) {
+%  my $pre = ( $cust_bill->owed > 0 )
+%              ? '<B><FONT SIZE="+1" COLOR="#FF0000">Open '
+%              : '';
+%  my $post = ( $cust_bill->owed > 0 ) ? '</FONT></B>' : '';
+%  my $invnum = $cust_bill->invnum;
+%  my $link = $curuser->access_right('View invoices')
+%               ? qq!<A HREF="${p}view/cust_bill.cgi?$invnum">!
+%               : '';
+%  my $events = '';
+%  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=!.
+%      $cust_bill->invnum. '">(&nbsp;View invoice events&nbsp;)</A></FONT>';
+%  }
+%  push @history, {
+%    'date'   => $cust_bill->_date,
+%    'desc'   => $link. $pre.
+%                "Invoice #$invnum (Balance \$". $cust_bill->owed. ')'.
+%                $post. ( $link ? '</A>' : '' ). $events,
+%    'charge' => $cust_bill->charged,
+%  };
+%}
+%
+%#payments (some false laziness w/credits)
+%foreach my $cust_pay ($cust_main->cust_pay) {
+%
+%  my $payby = $cust_pay->payby;
+%
+%  my $payinfo;
+%  if ( $payby eq 'CARD' ) {
+%    $payinfo = $cust_pay->paymask;
+%  } elsif ( $payby eq 'CHEK' && $cust_pay->payinfo =~ /^(\d+)\@(\d+)$/ ) {
+%    $payinfo = "ABA $2, Acct# $1";
+%  } 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( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
+%  if (    scalar(@cust_bill_pay)   == 0
+%       && scalar(@cust_pay_refund) == 0 ) {
+%    #completely unapplied
+%    $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+%    $post = '</FONT></B>';
+%    if ( $curuser->access_right('Apply payment') ) {
+%      $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_bill_pay.cgi?!.
+%               $cust_pay->paynum.
+%               qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!;
+%    }
+%  } elsif (    scalar(@cust_bill_pay)   == 1
+%            && scalar(@cust_pay_refund) == 0
+%            && $cust_pay->unapplied == 0     ) {
+%    #applied to one invoice, the usual situation
+%    $desc = ' applied to Invoice #'. $cust_bill_pay[0]->invnum;
+%  } elsif (    scalar(@cust_bill_pay)   == 0
+%            && scalar(@cust_pay_refund) == 1
+%            && $cust_pay->unapplied == 0     ) {
+%    #applied to one refund
+%    $desc = ' refunded on '. time2str("%D", $cust_pay_refund[0]->_date);
+%  } else {
+%    #complicated
+%    $desc = '<BR>';
+%    foreach my $app ( sort { $a->_date <=> $b->_date }
+%                           ( @cust_bill_pay, @cust_pay_refund ) ) {
+%      if ( $app->isa('FS::cust_bill_pay') ) {
+%        $desc .= '&nbsp;&nbsp;'.
+%                 '$'. $app->amount.
+%                 ' applied to Invoice #'. $app->invnum.
+%                 '<BR>';
+%                 #' on '. time2str("%D", $cust_bill_pay->_date).
+%      } elsif ( $app->isa('FS::cust_pay_refund') ) {
+%        $desc .= '&nbsp;&nbsp;'.
+%                 '$'. $app->amount.
+%                 ' refunded on '. time2str("%D", $app->_date).
+%                 '<BR>';
+%      } else {
+%        die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund";
+%      }
+%    }
+%    if ( $cust_pay->unapplied > 0 ) {
+%      $desc .= '&nbsp;&nbsp;'.
+%               '<B><FONT COLOR="#FF0000">$'.
+%               $cust_pay->unapplied. ' unapplied</FONT></B>';
+%      if ( $curuser->access_right('Apply payment') ) {
+%        $desc .= qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_bill_pay.cgi?!.
+%                 $cust_pay->paynum. 
+%                 qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!;
+%      }
+%      $desc .= '<BR>';
+%    }
+%  }
+%
+%  my $view = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}view/cust_pay.html?link=popup;paynum=!.
+%             $cust_pay->paynum.
+%             qq!', 540, 336, 'view_cust_pay_popup' ), CAPTION, 'Payment Receipt', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">view receipt</A>)!;
+%
+%  my $refund = '';
+%  my $refund_days = $conf->config('card_refund-days') || 120;
+%  if (    $cust_pay->closed !~ /^Y/i
+%       && $cust_pay->payby =~ /^(CARD|CHEK)$/
+%       && time-$cust_pay->_date < $refund_days*86400
+%       && $cust_pay->unrefunded > 0
+%       && $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
+%       && $conf->exists('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';
+%
+%  push @history, {
+%    'date'    => $cust_pay->_date,
+%    'desc'    => $pre. "Payment$post by $otaker $info$desc".
+%                 "$view$apply$refund$void$delete$unapply",
+%    'payment' => $cust_pay->paid,
+%    'target'  => $target,
+%  };
+%}
+%
+%#voided payments
+%foreach my $cust_pay_void ($cust_main->cust_pay_void) {
+%
+%  my $payby = $cust_pay_void->payby;
+%  my $payinfo = $payby eq 'CARD'
+%                  ? $cust_pay_void->paymask
+%                  : $cust_pay_void->payinfo;
+%
+%  $payby =~ s/^BILL$/Check #/ if $payinfo;
+%  $payby =~ s/^CHEK$/Electronic check /;
+%  $payby =~ s/^BILL$//;
+%  $payby =~ s/^(CARD|COMP)$/$1 /;
+%  my $info = $payby ? " ($payby$payinfo)" : '';
+%
+%  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>)!;
+%  }
+%
+%  push @history, {
+%    'date'   => $cust_pay_void->_date,
+%    'desc'   => "<DEL>Payment $info</DEL> <I>voided ".
+%                time2str("%D", $cust_pay_void->void_date).
+%                " by ". $cust_pay_void->otaker. '</i>'. $unvoid,
+%    'void_payment' => $cust_pay_void->paid,
+%  };
+%
+%}
+%
+%#credits (some false laziness w/payments)
+%foreach my $cust_credit ($cust_main->cust_credit) {
+%
+%  my @cust_credit_bill = $cust_credit->cust_credit_bill;
+%  my @cust_credit_refund = $cust_credit->cust_credit_refund;
+%
+%  my( $pre, $post, $desc, $apply, $ext ) = ( '', '', '', '', '' );
+%  if (    scalar(@cust_credit_bill)   == 0
+%       && scalar(@cust_credit_refund) == 0 ) {
+%    #completely unapplied
+%    $pre = '<B><FONT COLOR="#FF0000">Unapplied ';
+%    $post = '</FONT></B>';
+%    if ( $curuser->access_right('Apply credit') ) {
+%      $apply = qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!.
+%               $cust_credit->crednum.
+%               qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!;
+%    }
+%  } elsif (    scalar(@cust_credit_bill)   == 1
+%            && scalar(@cust_credit_refund) == 0
+%            && $cust_credit->credited == 0      ) {
+%    #applied to one invoice, the usual situation
+%    $desc = ' applied to Invoice #'. $cust_credit_bill[0]->invnum;
+%  } elsif (    scalar(@cust_credit_bill)   == 0
+%            && scalar(@cust_credit_refund) == 1
+%            && $cust_credit->credited == 0      ) {
+%    #applied to one refund
+%    $desc = ' refunded on '.  time2str("%D", $cust_credit_refund[0]->_date);
+%  } else {
+%    #complicated
+%    $desc = '<BR>';
+%    foreach my $app ( sort { $a->_date <=> $b->_date }
+%                           ( @cust_credit_bill, @cust_credit_refund ) ) {
+%      if ( $app->isa('FS::cust_credit_bill') ) {
+%        $desc .= '&nbsp;&nbsp;'.
+%                 '$'. $app->amount.
+%                 ' applied to Invoice #'. $app->invnum.
+%                 '<BR>';
+%                 #' on '. time2str("%D", $app->_date).
+%      } elsif ( $app->isa('FS::cust_credit_refund') ) {
+%        $desc .= '&nbsp;&nbsp;'.
+%                 '$'. $app->amount.
+%                 ' refunded on '. time2str("%D", $app->_date).
+%                 '<BR>';
+%      } else {
+%        die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund";
+%      }
+%    }
+%    if ( $cust_credit->credited > 0 ) {
+%      $desc .= '&nbsp;&nbsp;<B><FONT COLOR="#FF0000">$'.
+%               $cust_credit->credited. ' unapplied</FONT></B>';
+%      if ( $curuser->access_right('Apply credit') ) {
+%        $desc .= qq! (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('${p}edit/cust_credit_bill.cgi?!.
+%                 $cust_credit->crednum.
+%                 qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply</A>)!;
+%      }
+%      $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>)!;
+%  }
+%  
+%  push @history, {
+%    'date'   => $cust_credit->_date,
+%    'desc'   => $pre. "Credit$post by ". $cust_credit->otaker.
+%                ( $cust_credit->reason
+%                  ? ' ('. $cust_credit->reason. ')'
+%                  : ''
+%              ).
+%                "$desc$apply$delete$unapply",
+%    'credit' => $cust_credit->amount,
+%  };
+%
+%}
+%
+%#refunds
+%foreach my $cust_refund ($cust_main->cust_refund) {
+%
+%  my $payby = $cust_refund->payby;
+%  my $payinfo = $payby eq 'CARD'
+%                  ? $cust_refund->paymask
+%                  : $cust_refund->payinfo;
+%
+%  $payby =~ s/^BILL$/Check #/ if $payinfo;
+%  $payby =~ s/^CHEK$/Electronic check /;
+%  $payby =~ s/^(CARD|COMP)$/$1 /;
+%
+%  my $delete = '';
+%  if ( $cust_refund->closed !~ /^Y/i
+%       && $conf->exists('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>)!;
+%  }
+%
+%  push @history, {
+%    'date'   => $cust_refund->_date,
+%    'desc'   => "Refund ($payby$payinfo) by ". $cust_refund->otaker. "<BR>".
+%                $delete,
+%    'refund' => $cust_refund->refund,
+%  };
+%
+%}
+%
+%
+
+
+<% 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>Charge</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
+%
+%sub balance_forward_row {
+%  my( $b, $date ) = @_;
+%  my $conf = new FS::Conf;
+%  my $money_char = $conf->config('money_char') || '$';
+%  ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/-&nbsp;\$/;
+
+   <TR ID="balance_forward_row">
+     <TD CLASS="grid" BGCOLOR="#dddddd">
+       <% time2str("%D",$date) %>
+     </TD>
+
+     <TD CLASS="grid" BGCOLOR="#dddddd">
+       <I>Starting balance on <% time2str("%D",$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"><I><% $balance_forward %></I></TD>
+
+   </TR>
+%}
+%
+%my $balance = 0;
+%my %target = ();
+%my $money_char = $conf->config('money_char') || '$';
+%
+%my $years =  $conf->config('payment_history-years') || 2;
+%my $older_than = time - $years * 31556736; #60*60*24*365.24
+%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'});
+%    }
+%
+%  }
+%
+%  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("%D",$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);
+%}
+
+</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 $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'        => '',
+);
+
+</%init>
diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html
new file mode 100644 (file)
index 0000000..b5d581d
--- /dev/null
@@ -0,0 +1,84 @@
+<A NAME="tickets"><FONT SIZE="+2">Tickets</FONT></A>
+<BR>
+
+(<A HREF="<% $open_link %>">View <% $openlabel %> tickets for this customer</A>)
+(<A HREF="<% $res_link  %>">View resolved tickets for this customer</A>)
+<BR>
+(<A HREF="<% $new_link  %>">Create new ticket for this customer</A>)
+
+<% 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_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 (file)
index 0000000..4037d35
--- /dev/null
@@ -0,0 +1,133 @@
+% if ( $link eq 'popup' ) { 
+
+  <% include('/elements/header-popup.html', "Payment Receipt" ) %>
+
+  <CENTER><A HREF="javascript:self.parent.location = '<% $pr_link %>'">Print</A></CENTER><BR>
+
+% } elsif ( $link eq 'print' ) { 
+
+  <% include('/elements/header-popup.html', "Payment 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 (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+     )
+  %>
+  <BR><BR>
+
+% } else { 
+
+  <% include('/elements/header.html', "Payment Receipt", menubar(
+       "View this customer (#$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>
+
+<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>
+%   }
+
+% }
+
+</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 customer payments');
+
+$cgi->param('paynum') =~ /^(\d+)$/ or die "no paynum";
+my $paynum = $1;
+
+my $link = '';
+if ( $cgi->param('link') =~ /^(\w+)$/ ) {
+  $link = $1;
+}
+
+my $cust_pay = qsearchs({
+  'select'    => 'cust_pay.*',
+  'table'     => 'cust_pay',
+  'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+  'hashref'   => { 'paynum' => $paynum },
+  'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die "Payment #$paynum not found!" unless $cust_pay;
+
+my $pr_link = "${p}view/cust_pay.html?link=print;paynum=$paynum";
+
+my $custnum = $cust_pay->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/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
new file mode 100644 (file)
index 0000000..0500248
--- /dev/null
@@ -0,0 +1,136 @@
+%  # options example...
+%  #
+%  # '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' => 
+%
+%
+% if ( $custnum ) { 
+
+
+  <% include("/elements/header.html","View $label: $value") %>
+
+  <% include( '/elements/small_custview.html', $custnum, '', 1,
+     "${p}view/cust_main.cgi") %>
+  <BR>
+% } else { 
+
+
+  <SCRIPT>
+  function areyousure(href) {
+      if (confirm("Permanently delete this <% $label %>?") == true)
+          window.location.href = href;
+  }
+  </SCRIPT>
+
+  <% include("/elements/header.html","View $label: $value", menubar(
+      "Cancel this (unaudited) $label" =>
+            "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')"
+  )) %>
+% } 
+
+
+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';
+%     }
+%
+
+
+  <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>
+<% 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,
+}) 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 $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/logo.cgi b/httemplate/view/logo.cgi
new file mode 100644 (file)
index 0000000..aeca0f3
--- /dev/null
@@ -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/svc_Common.html b/httemplate/view/svc_Common.html
new file mode 100644 (file)
index 0000000..bb3a6dd
--- /dev/null
@@ -0,0 +1,29 @@
+<%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>
+<% include('elements/svc_Common.html',
+             'table'    => $table,
+            'edit_url' => $p."edit/svc_Common.html?svcdb=$table;svcnum=",
+            %opt,
+          )
+%>
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
new file mode 100755 (executable)
index 0000000..e6d2b69
--- /dev/null
@@ -0,0 +1,396 @@
+% 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",'Account View', menubar(
+    "Cancel this (unaudited) account" =>
+            "javascript:areyousure(\'${p}misc/cancel-unaudited.cgi?$svcnum\')",
+  )) %>
+
+% } 
+
+% 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=$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>
+% } 
+
+% my @part_svc = ();
+% if ($FS::CurrentUser::CurrentUser->access_right('Change customer service')) {
+
+    <SCRIPT TYPE="text/javascript">
+      function enable_change () {
+        if ( document.OneTrueForm.svcpart.selectedIndex > 1 ) {
+          document.OneTrueForm.submit.disabled = false;
+        } else {
+          document.OneTrueForm.submit.disabled = true;
+        }
+      }
+    </SCRIPT>
+
+    <FORM NAME="OneTrueForm" ACTION="<%$p%>edit/process/cust_svc.cgi">
+    <INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+    <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+%   #print qq!<BR><A HREF="../misc/sendconfig.cgi?$svcnum">Send account information</A>!; 
+% 
+%   if ( $pkgnum ) { 
+%     @part_svc = grep {    $_->svcdb   eq 'svc_acct'
+%                        && $_->svcpart != $part_svc->svcpart }
+%                 $cust_pkg->available_part_svc;
+%   } else {
+%     @part_svc = qsearch('part_svc', {
+%       svcdb    => 'svc_acct',
+%       disabled => '',
+%       svcpart  => { op=>'!=', value=>$part_svc->svcpart },
+%     } );
+%   }
+%
+% }
+
+Service #<B><% $svcnum %></B>
+| <A HREF="<%$p%>edit/svc_acct.cgi?<%$svcnum%>">Edit this service</A>
+
+% if ( @part_svc ) { 
+
+| <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>
+
+% } 
+
+
+<% &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">Username</TD>
+  <TD BGCOLOR="#ffffff"><% $svc_acct->username %></TD>
+</TR>
+<TR>
+  <TD ALIGN="right">Domain</TD>
+  <TD BGCOLOR="#ffffff"><% $domain %></TD>
+</TR>
+
+<TR>
+  <TD ALIGN="right">Password</TD>
+  <TD BGCOLOR="#ffffff">
+% my $password = $svc_acct->_password; 
+% if ( $password =~ /^\*\w+\* (.*)$/ ) {
+%         $password = $1;
+%    
+
+      <I>(login disabled)</I>
+% } 
+% if ( $conf->exists('showpasswords') ) { 
+
+      <PRE><% encode_entities($password) %></PRE>
+% } else { 
+
+      <I>(hidden)</I>
+% } 
+
+
+  </TD>
+</TR>
+% $password = ''; 
+% if ( $conf->exists('security_phrase') ) {
+%     my $sec_phrase = $svc_acct->sec_phrase;
+%
+
+  <TR>
+    <TD ALIGN="right">Security phrase</TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->sec_phrase %></TD>
+  </TR>
+% } 
+% if ( $svc_acct->popnum ) {
+%    my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum});
+%
+
+  <TR>
+    <TD ALIGN="right">Access number</TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct_pop->text %></TD>
+  </TR>
+% } 
+% if ($svc_acct->uid ne '') { 
+
+  <TR>
+    <TD ALIGN="right">UID</TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->uid %></TD>
+  </TR>
+% } 
+% if ($svc_acct->gid ne '') { 
+
+  <TR>
+    <TD ALIGN="right">GID</TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->gid %></TD>
+  </TR>
+% } 
+% if ($svc_acct->finger ne '') { 
+
+  <TR>
+    <TD ALIGN="right">GECOS</TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->finger %></TD>
+  </TR>
+% } 
+% if ($svc_acct->dir ne '') { 
+
+  <TR>
+    <TD ALIGN="right">Home directory</TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->dir %></TD>
+  </TR>
+% } 
+% if ($svc_acct->shell ne '') { 
+
+  <TR>
+    <TD ALIGN="right">Shell</TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->shell %></TD>
+  </TR>
+% } 
+% if ($svc_acct->quota ne '') { 
+
+  <TR>
+    <TD ALIGN="right">Quota</TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->quota %></TD>
+  </TR>
+% } 
+% if ($svc_acct->slipip) { 
+
+  <TR>
+    <TD ALIGN="right">IP address</TD>
+    <TD BGCOLOR="#ffffff">
+      <% ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' )
+            ? "<I>(Dynamic)</I>"
+            : $svc_acct->slipip
+      %>
+    </TD>
+  </TR>
+% } 
+% 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) )
+%       : FS::UI::bytecount::display_bytecount($svc_acct->$uf);
+    <TR>
+      <TD ALIGN="right"><% $ulabel{$uf} %> remaining</TD>
+      <TD BGCOLOR="#ffffff"><% $v %></TD>
+    </TR>
+
+%   }
+% }
+% foreach my $attribute ( grep /^radius_/, $svc_acct->fields ) {
+%  $attribute =~ /^radius_(.*)$/;
+%  my $pattribute = $FS::raddb::attrib{$1};
+%
+
+  <TR>
+    <TD ALIGN="right">Radius (reply) <% $pattribute %></TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->getfield($attribute) %></TD>
+  </TR>
+% } 
+% foreach my $attribute ( grep /^rc_/, $svc_acct->fields ) {
+%  $attribute =~ /^rc_(.*)$/;
+%  my $pattribute = $FS::raddb::attrib{$1};
+%
+
+  <TR>
+    <TD ALIGN="right">Radius (check) <% $pattribute %></TD>
+    <TD BGCOLOR="#ffffff"><% $svc_acct->getfield($attribute) %></TD>
+  </TR>
+% } 
+
+
+<TR>
+  <TD ALIGN="right">RADIUS groups</TD>
+  <TD BGCOLOR="#ffffff"><% join('<BR>', $svc_acct->radius_groups) %></TD>
+</TR>
+%
+%# Can this be abstracted further?  Maybe a library function like
+%# widget('HTML', 'view', $svc_acct) ?  It would definitely make UI 
+%# style management easier.
+%
+% foreach (sort { $a cmp $b } $svc_acct->virtual_fields) { 
+
+  <% $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)) %>
+% } 
+
+
+</TABLE></TD></TR></TABLE>
+</FORM>
+<BR><BR>
+
+% if ( @svc_www ) {
+  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>
+% }
+
+<% join("<BR>", $conf->config('svc_acct-notes') ) %>
+<BR><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 $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,
+});
+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;
+
+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;
+
+my @svc_www = qsearch({
+  'select'    => 'svc_www.*',
+  'table'     => 'svc_www',
+  'addl_from' => $addl_from,
+  'hashref'   => { 'usersvc' => $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_broadband.cgi b/httemplate/view/svc_broadband.cgi
new file mode 100644 (file)
index 0000000..e614fe4
--- /dev/null
@@ -0,0 +1,211 @@
+<%include("/elements/header.html",'Broadband Service View', menubar(
+  ( ( $custnum )
+    ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+      )                                                                       
+    : ( "Cancel this (unaudited) website" =>
+          "${p}misc/cancel-unaudited.cgi?$svcnum" )
+  )
+))
+%>
+
+<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>
+        <TR>
+          <TD ALIGN="right">Router</TD>
+          <TD BGCOLOR="#ffffff"><%$routernum%>: <%$routername%></TD>
+        </TR>
+        <TR>
+          <TD ALIGN="right">Download Speed</TD>
+          <TD BGCOLOR="#ffffff"><%$speed_down%></TD>
+        </TR>
+        <TR>
+          <TD ALIGN="right">Upload Speed</TD>
+          <TD BGCOLOR="#ffffff"><%$speed_up%></TD>
+        </TR>
+        <TR>
+          <TD ALIGN="right">IP Address</TD>
+          <TD BGCOLOR="#ffffff"><%$ip_addr%></TD>
+        </TR>
+        <TR>
+          <TD ALIGN="right">IP Netmask</TD>
+          <TD BGCOLOR="#ffffff"><%$ip_netmask%></TD>
+        </TR>
+        <TR>
+          <TD ALIGN="right">IP Gateway</TD>
+          <TD BGCOLOR="#ffffff"><%$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,
+}) or die "svc_broadband: Unknown svcnum $svcnum";
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+  $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+  $custnum = $cust_pkg->custnum;
+} else {
+  $cust_pkg = '';
+  $custnum = '';
+}
+#eofalse
+
+my $addr_block = $svc_broadband->addr_block;
+my $router = $addr_block->router;
+
+if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" };
+
+my (
+     $routername,
+     $routernum,
+     $speed_down,
+     $speed_up,
+     $ip_addr,
+     $ip_gateway,
+     $ip_netmask,
+     $mac_addr,
+     $latitude,
+     $longitude,
+     $altitude,
+     $vlan_profile,
+     $auth_key,
+     $description,
+   ) = (
+     $router->getfield('routername'),
+     $router->getfield('routernum'),
+     $svc_broadband->getfield('speed_down'),
+     $svc_broadband->getfield('speed_up'),
+     $svc_broadband->getfield('ip_addr'),
+     $addr_block->ip_gateway,
+     $addr_block->NetAddr->mask,
+     $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 (executable)
index 0000000..a58d75e
--- /dev/null
@@ -0,0 +1,161 @@
+<% include("/elements/header.html",'Domain View', menubar(
+  ( ( $pkgnum || $custnum )
+    ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+      )
+    : ( "Delete this (unaudited) domain" =>
+          "javascript:areyousure('${p}misc/cancel-unaudited.cgi?$svcnum', 'Delete $domain and all records?' )" )
+  )
+)) %>
+
+Service #<% $svcnum %>
+<BR>Service: <B><% $part_svc->svc %></B>
+<BR>Domain name: <B><% $domain %></B>
+<BR>Catch all email 
+% if ( $FS::CurrentUser::CurrentUser->access_right('Edit domain catchall') ) {
+    <BR>Catch all email<A HREF="<% ${p} %>misc/catchall.cgi?<% $svcnum %>">(change)</A>:
+} else {
+    <BR>Catch all email:
+% }
+
+<% $email ? "<B>$email</B>" : "<I>(none)<I>" %>
+<BR><BR><A HREF="<% ${p} %>misc/whois.cgi?custnum=<%$custnum%>;svcnum=<%$svcnum%>;domain=<%$domain%>">View whois information.</A>
+<BR><BR>
+<SCRIPT>
+  function areyousure(href, 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>
+
+% 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') ) {
+    <BR>
+    <FORM METHOD="POST" ACTION="<%$p%>edit/process/domain_record.cgi">
+      <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+      <INPUT TYPE="text" NAME="reczone"> 
+      <INPUT TYPE="hidden" NAME="recaf" VALUE="IN"> IN 
+      <SELECT NAME="rectype">
+%       foreach (qw( A NS CNAME MX PTR TXT) ) { 
+          <OPTION VALUE="<%$_%>"><%$_%></OPTION>
+%       } 
+      </SELECT>
+      <INPUT TYPE="text" NAME="recdata">
+      <INPUT TYPE="submit" VALUE="Add record">
+    </FORM>
+
+    <BR><BR>
+    or
+    <BR><BR>
+
+    <FORM NAME="SlaveForm" METHOD="POST" ACTION="<%$p%>edit/process/domain_record.cgi">
+      <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+%     if ( @records ) { 
+         Delete all records and 
+%     } 
+      Slave from nameserver IP 
+      <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+      <INPUT TYPE="hidden" NAME="reczone" VALUE="@"> 
+      <INPUT TYPE="hidden" NAME="recaf" VALUE="IN">
+      <INPUT TYPE="hidden" NAME="rectype" VALUE="_mstr">
+      <INPUT TYPE="text" NAME="recdata">
+      <INPUT TYPE="submit" VALUE="Slave domain" onClick="return slave_areyousure()">
+    </FORM>
+
+% }
+
+<BR><BR>
+
+<% joblisting({'svcnum'=>$svcnum}, 1) %>
+
+<% 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_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 $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+  $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+  $custnum=$cust_pkg->getfield('custnum');
+} else {
+  $cust_pkg = '';
+  $custnum = '';
+}
+
+my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } );
+die "Unknown svcpart" unless $part_svc;
+
+my $email = '';
+if ($svc_domain->catchall) {
+  my $svc_acct = qsearchs('svc_acct',{'svcnum'=> $svc_domain->catchall } );
+  die "Unknown svcpart" unless $svc_acct;
+  $email = $svc_acct->email;
+}
+
+my $domain = $svc_domain->domain;
+
+</%init>
diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi
new file mode 100644 (file)
index 0000000..553d236
--- /dev/null
@@ -0,0 +1,62 @@
+<% include("/elements/header.html",'External Service View', menubar(
+  ( ( $custnum )
+    ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+      )                                                                       
+    : ( "Cancel this (unaudited) external service" =>
+          "${p}misc/cancel-unaudited.cgi?$svcnum" )
+  ),
+)) %>
+
+<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,
+}) or die "svc_external: Unknown svcnum $svcnum";
+
+my $conf = new FS::Conf;
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+  $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+  $custnum = $cust_pkg->custnum;
+} else {
+  $cust_pkg = '';
+  $custnum = '';
+}
+#eofalse
+
+</%init>
diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi
new file mode 100755 (executable)
index 0000000..7451477
--- /dev/null
@@ -0,0 +1,92 @@
+% 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,
+%});
+%die "Unknown svcnum" unless $svc_forward;
+%
+%my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+%my $pkgnum = $cust_svc->getfield('pkgnum');
+%my($cust_pkg, $custnum);
+%if ($pkgnum) {
+%  $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+%  $custnum=$cust_pkg->getfield('custnum');
+%} else {
+%  $cust_pkg = '';
+%  $custnum = '';
+%}
+%
+%my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } )
+%  or die "Unkonwn svcpart";
+%
+%print header('Mail Forward View', menubar(
+%  ( ( $pkgnum || $custnum )
+%    ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+%      )
+%    : ( "Cancel this (unaudited) mail forward" =>
+%          "${p}misc/cancel-unaudited.cgi?$svcnum" )
+%  )
+%));
+%
+%my($srcsvc,$dstsvc,$dst) = (
+%  $svc_forward->srcsvc,
+%  $svc_forward->dstsvc,
+%  $svc_forward->dst,
+%);
+%my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : '';
+%
+%my $svc = $part_svc->svc;
+%
+%my $source;
+%if ($srcsvc) {
+%  my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc})
+%    or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc";
+%  $source = $svc_acct->email;
+%} else {
+%  $source = $src;
+%}
+%
+%my $destination;
+%if ($dstsvc) {
+%  my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$dstsvc})
+%    or die "Corrupted database: no svc_acct.svcnum matching dstsvc $dstsvc";
+%  $destination = $svc_acct->email;
+%} else {
+%  $destination = $dst;
+%}
+%
+%print qq!<A HREF="${p}edit/svc_forward.cgi?$svcnum">Edit this information</A>!.
+%      ntable("#cccccc",2).
+%      '<TR><TD ALIGN="right">Service number</TD>'.
+%        qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!.
+%      '<TR><TD ALIGN="right">Service</TD>'.
+%        qq!<TD BGCOLOR="#ffffff">$svc</TD></TR>!.
+%      qq!<TR><TD ALIGN="right">Email to</TD>!.
+%        qq!<TD BGCOLOR="#ffffff">$source</TD></TR>!.
+%      qq!<TR><TD ALIGN="right">Forwards to </TD>!.
+%        qq!<TD BGCOLOR="#ffffff">$destination</TD></TR>!;
+%
+%foreach (sort { $a cmp $b } $svc_forward->virtual_fields) {
+%  print $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)),
+%      "\n";
+%}
+%
+%print qq!  </TABLE>!.
+%      '<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
+%      '</BODY></HTML>'
+%;
+%
+%
+
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
new file mode 100644 (file)
index 0000000..732f3cd
--- /dev/null
@@ -0,0 +1,10 @@
+<% include('elements/svc_Common.html',
+              'table'  => 'svc_phone',
+              'fields' => [qw( countrycode phonenum )], #pin
+              'labels' => {
+                            'countrycode' => 'Country code',
+                            'phonenum'    => 'Phone number',
+                            'pin'         => 'PIN',
+                          },
+           )
+%>
diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi
new file mode 100644 (file)
index 0000000..d6d458c
--- /dev/null
@@ -0,0 +1,88 @@
+% 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,
+%}) or die "svc_www: Unknown svcnum $svcnum";
+%
+%#false laziness w/all svc_*.cgi
+%my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+%my $pkgnum = $cust_svc->getfield('pkgnum');
+%my($cust_pkg, $custnum);
+%if ($pkgnum) {
+%  $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+%  $custnum = $cust_pkg->custnum;
+%} else {
+%  $cust_pkg = '';
+%  $custnum = '';
+%}
+%#eofalse
+%
+%my $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;
+
+<% include("/elements/header.html", "Website View", menubar(
+    ( ( $custnum )
+      ? ( "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+        )                                                                       
+      : ( "Cancel this (unaudited) website" =>
+            "${p}misc/cancel-unaudited.cgi?$svcnum" )
+    ),
+  ))
+%>
+
+%print qq!<A HREF="${p}edit/svc_www.cgi?$svcnum">Edit this information</A><BR>!.
+%      ntable("#cccccc"). '<TR><TD>'. ntable("#cccccc",2).
+%      qq!<TR><TD ALIGN="right">Service number</TD>!.
+%        qq!<TD BGCOLOR="#ffffff">$svcnum</TD></TR>!.
+%      qq!<TR><TD ALIGN="right">Website name</TD>!.
+%        qq!<TD BGCOLOR="#ffffff"><A HREF="http://$www">$www<A></TD></TR>!;
+%if (  $part_svc->part_svc_column('usersvc')->columnflag ne 'F'
+%   || $part_svc->part_svc_column('usersvc')->columnvalue !~ /^\s*$/) {
+%  print qq!<TR><TD ALIGN="right">Account</TD>!.
+%        qq!<TD BGCOLOR="#ffffff">!;
+%
+%  if ( $usersvc ) {
+%    print qq!<A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A>!;
+%  } else {
+%    print '</i>(none)</i>';
+%  }
+%
+%  print '</TD></TR>';
+%}
+    <% qq!<TR><TD ALIGN="right">Config lines</TD>! %>
+    <% qq!<TD BGCOLOR="#ffffff"><pre>!.$cgi->escapeHTML(join("\n",$svc_www->config))."</pre></TD></TR>" %>
+%
+%foreach (sort { $a cmp $b } $svc_www->virtual_fields) {
+%  print $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)),
+%      "\n";
+%}
+%
+%
+%print '</TABLE></TD></TR></TABLE>'.
+%      '<BR>'. joblisting({'svcnum'=>$svcnum}, 1);
+
+<% include('/elements/footer.html') %>
diff --git a/init.d/freeside-init b/init.d/freeside-init
new file mode 100644 (file)
index 0000000..a62b187
--- /dev/null
@@ -0,0 +1,79 @@
+#! /bin/sh
+#
+# chkconfig: 345 86 16
+# description: Freeside daemons
+
+QUEUED_USER=%%%QUEUED_USER%%%
+
+SELFSERVICE_USER=%%%SELFSERVICE_USER%%%
+SELFSERVICE_MACHINES="%%%SELFSERVICE_MACHINES%%%"
+
+#INSTALLSCRIPT/INSTALLSITEBIN from Makefile.PL
+PATH="$PATH:/usr/local/bin"
+export PATH
+
+case "$1" in
+  start)
+        # Start daemons.
+        echo -n "Starting freeside-queued: "
+        freeside-queued $QUEUED_USER
+        echo "done."
+
+        #echo -n "Starting freeside-sqlradius-radacctd: "
+        #freeside-sqlradius-radacctd $QUEUED_USER
+        #echo "done."
+
+        echo -n "Starting freeside-prepaidd: "
+        freeside-prepaidd $QUEUED_USER
+        echo "done."
+
+        for MACHINE in $SELFSERVICE_MACHINES; do
+          echo -n "Starting freeside-selfservice-server to $MACHINE: "
+          freeside-selfservice-server $SELFSERVICE_USER $MACHINE
+          echo "done."
+        done
+
+        ;;
+  stop)
+        # Stop daemons.
+        echo -n "Stopping freeside-queued: "
+        kill `cat /var/run/freeside-queued.pid`
+        echo "done."
+
+        #and
+        killall freeside-queued
+
+        echo -n "Stopping freeside-sqlradius-radacctd: "
+        kill `cat /var/run/freeside-sqlradius-radacctd.pid`
+        echo "done."
+
+        echo -n "Stopping freeside-prepaidd: "
+        kill `cat /var/run/freeside-prepaidd.pid`
+        echo "done."
+
+        if [ -e /var/run/freeside-selfservice-server.$SELFSERVICE_USER.pid ]
+        then
+          echo -n "Stopping (old) freeside-selfservice-server: "
+          kill `cat /var/run/freeside-selfservice-server.$SELFSERVICE_USER.pid`
+          rm /var/run/freeside-selfservice-server.$SELFSERVICE_USER.pid
+        fi
+
+        for MACHINE in $SELFSERVICE_MACHINES; do
+          echo -n "Stopping freeside-selfservice-server to $MACHINE: "
+          kill `cat /var/run/freeside-selfservice-server.$SELFSERVICE_USER.$MACHINE.pid`
+          echo "done."
+        done
+
+        ;;
+
+  restart)
+        $0 stop
+        $0 start
+        ;;
+  *)
+        echo "Usage: freeside {start|stop|restart}"
+        exit 1
+esac
+
+exit 0
+
diff --git a/rpm/INSTALL b/rpm/INSTALL
new file mode 100644 (file)
index 0000000..d39bf70
--- /dev/null
@@ -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.spec b/rpm/freeside.spec
new file mode 100644 (file)
index 0000000..897b2df
--- /dev/null
@@ -0,0 +1,347 @@
+%{!?_initrddir:%define _initrddir /etc/rc.d/init.d}
+%{!?version:%define version 1.9}
+%{!?release:%define release 1}
+
+Summary: Freeside ISP Billing System
+Name: freeside
+Version: %{version}
+Release: %{release}
+License: AGPL
+Group: Applications/Internet
+URL: http://www.sisd.com/freeside/
+Packager: Richard Siddall <richard.siddall@elirion.net>
+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
+Requires: tetex-latex
+Requires: perl-Fax-Hylafax-Client
+
+%define freeside_document_root /var/www/freeside
+%define freeside_cache         /var/cache/subsys/freeside
+%define freeside_conf          /etc/freeside
+%define freeside_export                /etc/freeside
+%define freeside_lock          /var/lock/freeside
+%define freeside_log           /var/log/freeside
+%define        rt_enabled              0
+%define apache_conffile                /etc/httpd/conf/httpd.conf
+%define        apache_confdir          /etc/httpd/conf.d
+%define        apache_version          2
+%define        fs_queue_user           fs_queue
+%define        fs_selfservice_user     fs_selfservice
+%define        fs_cron_user            fs_daily
+%define        db_types                Pg mysql
+
+%define _rpmlibdir     /usr/lib/rpm
+
+%description
+Freeside is a flexible ISP billing system written by Ivan Kohler
+
+%package mason
+Summary: HTML::Mason interface for %{name}
+Group: Applications/Internet
+Prefix: /var/www/freeside
+Requires: mod_ssl
+Requires: perl-Apache-DBI
+Conflicts: %{name}-apacheasp
+Provides: %{name}-frontend
+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
+
+%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
+
+%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
+Conflicts: %{name}
+
+%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} so you cannot install the billing system and self-service interface on the same computer.
+
+%prep
+%setup
+%{__rm} bin/pod2x # Only useful to Ivan Kohler now
+perl -pi -e 's|/usr/local/bin|%{buildroot}%{_bindir}|g' FS/Makefile.PL
+perl -pi -e 's|\s+-o\s+freeside\s+| |g' Makefile
+perl -ni -e 'print if !/\s+chown\s+/;' Makefile
+
+# 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\)$' | 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
+CFLAGS="$RPM_OPT_FLAGS" perl Makefile.PL PREFIX=$RPM_BUILD_ROOT%{_prefix} SITELIBEXP=$RPM_BUILD_ROOT%{perl_sitelib} SITEARCHEXP=$RPM_BUILD_ROOT%{perl_sitearch}
+%{__make} OPTIMIZE="$RPM_OPT_FLAGS"
+cd ..
+%{__make} perl-modules VERSION='%{version}-%{release}' 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
+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}
+%{__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
+       %{__make} create-config DB_TYPE=$DBTYPE 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}
+       %{__mv} $RPM_BUILD_ROOT/tmp/* $RPM_BUILD_ROOT%{freeside_conf}
+       /bin/rmdir $RPM_BUILD_ROOT/tmp
+done
+%{__rm} install-perl-modules perl-modules $RPM_BUILD_ROOT%{freeside_conf}/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}
+%{__install} init.d/freeside-init $RPM_BUILD_ROOT%{_initrddir}/%{name}
+#%{__make} install-init INSTALLGROUP=root INIT_FILE=$RPM_BUILD_ROOT%{_initrddir}/%{name}
+%{__perl} -pi -e "\
+         s/%%%%%%QUEUED_USER%%%%%%/%{fs_queue_user}/g;\
+         s/%%%%%%SELFSERVICE_USER%%%%%%/%{fs_selfservice_user}/g;\
+         s/%%%%%%SELFSERVICE_MACHINES%%%%%%//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} MASON_HANDLER=%{freeside_conf}/handler.pl
+%{__perl} -pi -e "s|%%%%%%FREESIDE_DOCUMENT_ROOT%%%%%%|%{freeside_document_root}|g" $RPM_BUILD_ROOT%{apache_confdir}/freeside-*.conf
+%{__perl} -pi -e "s|%%%%%%MASON_HANDLER%%%%%%|%{freeside_conf}/handler.pl|g" $RPM_BUILD_ROOT%{apache_confdir}/freeside-*.conf
+%{__perl} -pi -e "s|/usr/local/etc/freeside|%{freeside_conf}|g" $RPM_BUILD_ROOT%{apache_confdir}/freeside-*.conf
+%{__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} rpm/freeside.sysconfig $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/%{name}
+
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_document_root}/selfservice
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_document_root}/selfservice/cgi
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_document_root}/selfservice/php
+%{__mkdir_p} $RPM_BUILD_ROOT%{freeside_document_root}/selfservice/templates
+%{__install} fs_selfservice/FS-SelfService/cgi/* $RPM_BUILD_ROOT%{freeside_document_root}/selfservice/cgi
+%{__install} fs_selfservice/php/* $RPM_BUILD_ROOT%{freeside_document_root}/selfservice/php
+%{__install} fs_selfservice/FS-SelfService/*.template $RPM_BUILD_ROOT%{freeside_document_root}/selfservice/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 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}-temp-filelist
+cat ../../FS/%{name}-%{version}-%{release}-filelist %{name}-%{version}-%{release}-temp-filelist | sort | uniq -u >  %{name}-%{version}-%{release}-selfservice-filelist
+if [ "$(cat %{name}-%{version}-%{release}-selfservice-filelist)X" = "X" ] ; then
+    echo "ERROR: EMPTY FILE LIST"
+    exit 1
+fi
+cd ../..
+
+%pre
+if ! %{__id} freeside &>/dev/null; then
+       /usr/sbin/useradd freeside
+fi
+
+%pre mason
+if ! %{__id} freeside &>/dev/null; then
+       /usr/sbin/useradd freeside
+fi
+
+%pre postgresql
+if ! %{__id} freeside &>/dev/null; then
+       /usr/sbin/useradd freeside
+fi
+
+%pre mysql
+if ! %{__id} freeside &>/dev/null; then
+       /usr/sbin/useradd freeside
+fi
+
+%pre selfservice
+if ! %{__id} freeside &>/dev/null; then
+       /usr/sbin/useradd 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
+       perl -p -i.fsbackup -e 's/^(User|Group) .*/$1 freeside/' %{apache_conffile}
+fi
+
+%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}
+
+%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 -f fs_selfservice/FS-SelfService/%{name}-%{version}-%{release}-selfservice-filelist
+%defattr(-, freeside, freeside, 0644)
+%attr(0755,freeside,freeside) %{freeside_document_root}/selfservice/cgi
+%attr(0755,freeside,freeside) %{freeside_document_root}/selfservice/php
+%attr(0644,freeside,freeside) %{freeside_document_root}/selfservice/templates
+
+%changelog
+* 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 (file)
index 0000000..06f9e30
--- /dev/null
@@ -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 (executable)
index 0000000..38a7ac2
--- /dev/null
@@ -0,0 +1,98 @@
+#!/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 FS/) {
+       $suppress{$_} = $_;
+}
+
+## 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\.]+))?$/) {
+                       next if exists($suppress{$1});
+                       my @parts = split /::/, $1;
+                       if (scalar @parts > 1) {
+                               next if exists($suppress{$parts[0]});
+                       }
+                       if ($verbose) {
+                               print STDERR "$1";
+                               print STDERR " >= $3" if $3;
+                               print STDERR "\n";
+                       }
+                       $mods{$1} = $3 ? $3 : undef;
+               }
+       }
+
+       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/FREESIDE_MODIFIED b/rt/FREESIDE_MODIFIED
new file mode 100644 (file)
index 0000000..6691779
--- /dev/null
@@ -0,0 +1,34 @@
+ sbin/rt-setup-database.in
+config.layout
+config.layout.in
+ etc/RT_SiteConfig.pm
+lib/RT/Interface/Web_Vendor.pm
+lib/RT/SearchBuilder.pm #need DBIx::SearchBuilder >= 1.36 for Pg 8.1+
+lib/RT/URI/freeside.pm
+lib/RT/URI/freeside/Internal.pm
+lib/RT/URI/freeside/XMLRPC.pm
+ html/Elements/Header
+ html/Elements/Menu
+ html/Elements/PageLayout
+ html/Elements/QuickCreate
+ html/Elements/SimpleSearch
+ html/Elements/Tabs
+ html/Elements/Footer
+ html/Elements/CollectionAsTable/Row #backport from 3.3-TESTING
+html/Ticket/Elements/AddCustomers
+html/Ticket/Elements/EditCustomers
+html/Ticket/Elements/ShowCustomers
+ html/Ticket/Elements/ShowSummary
+ html/Ticket/Elements/Tabs
+html/Ticket/ModifyCustomers.html
+html/NoAuth/images/small-logo.png
+ html/NoAuth/css/3.5-default/main.css
+html/NoAuth/css/3.5-default/freeside.css
+
+html/Widgets/TitleBoxStart
+
+html/Elements/FreesideNewCust
+html/Elements/FreesideSearch
+html/Elements/FreesideSvcSearch
+
+
diff --git a/rt/HOWTO/README b/rt/HOWTO/README
deleted file mode 100644 (file)
index 942096b..0000000
+++ /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 (file)
index de31645..0000000
+++ /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 (file)
index 285041c..0000000
+++ /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 (file)
index 06babfd..0000000
+++ /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.
-
-
index 0895874..e6a5dde 100644 (file)
@@ -1,8 +1,14 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# END LICENSE BLOCK
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
 #
 # DO NOT HAND-EDIT the file named 'Makefile'. This file is autogenerated.
 # Have a look at "configure" and "Makefile.in" instead
@@ -35,15 +59,15 @@ SITE_CONFIG_FILE            =       $(CONFIG_FILE_PATH)/RT_SiteConfig.pm
 
 
 RT_VERSION_MAJOR       =       3
-RT_VERSION_MINOR       =       0
-RT_VERSION_PATCH       =       9
+RT_VERSION_MINOR       =       6
+RT_VERSION_PATCH       =       4
 
 RT_VERSION =   $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH)
 TAG       =    rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH)
 
 
 # This is the group that all of the installed files will be chgrp'ed to.
-RTGROUP                        =       rt
+RTGROUP                        =       freeside
 
 
 # User which should own rt binaries.
@@ -55,8 +79,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
 
 # {{{ Files and directories 
 
@@ -76,10 +103,11 @@ RT_VAR_PATH                =       /opt/rt3/var
 RT_DOC_PATH            =       /opt/rt3/share/doc
 RT_LOCAL_PATH          =       /opt/rt3/local
 LOCAL_ETC_PATH         =       /opt/rt3/local/etc
+LOCAL_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
 
@@ -94,6 +122,10 @@ RT_READABLE_DIR_MODE        =       0755
 
 # RT_MODPERL_HANDLER is the mason handler script for mod_perl
 RT_MODPERL_HANDLER     =       $(RT_BIN_PATH)/webmux.pl
+# RT_STANDALONE_SERVER is a stand-alone HTTP server
+RT_STANDALONE_SERVER   =       $(RT_BIN_PATH)/standalone_httpd
+# RT_SPEEDYCGI_HANDLER is the mason handler script for SpeedyCGI
+RT_SPEEDYCGI_HANDLER   =       $(RT_BIN_PATH)/mason_handler.scgi
 # RT_FASTCGI_HANDLER is the mason handler script for FastCGI
 RT_FASTCGI_HANDLER     =       $(RT_BIN_PATH)/mason_handler.fcgi
 # RT_WIN32_FASTCGI_HANDLER is the mason handler script for FastCGI
@@ -107,17 +139,17 @@ RT_CRON_BIN               =       $(RT_BIN_PATH)/rt-crontool
 
 # }}}
 
-SETGID_BINARIES                =       $(DESTDIR)/$(RT_FASTCGI_HANDLER) \
-                               $(DESTDIR)/$(RT_WIN32_FASTCGI_HANDLER)
 
 BINARIES               =       $(DESTDIR)/$(RT_MODPERL_HANDLER) \
                                $(DESTDIR)/$(RT_MAILGATE_BIN) \
                                $(DESTDIR)/$(RT_CLI_BIN) \
                                $(DESTDIR)/$(RT_CRON_BIN) \
-                               $(SETGID_BINARIES)
+                               $(DESTDIR)/$(RT_STANDALONE_SERVER) \
+                               $(DESTDIR)/$(RT_SPEEDYCGI_HANDLER) \
+                               $(DESTDIR)/$(RT_FASTCGI_HANDLER) \
+                               $(DESTDIR)/$(RT_WIN32_FASTCGI_HANDLER)
 SYSTEM_BINARIES                =       $(DESTDIR)/$(RT_SBIN_PATH)/
 
-
 # }}}
 
 # {{{ Database setup
@@ -128,7 +160,7 @@ SYSTEM_BINARIES             =       $(DESTDIR)/$(RT_SBIN_PATH)/
 # "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
@@ -140,7 +172,7 @@ DB_TYPE                     =       mysql
 # For Oracle, you want 'system'
 # For Informix, you want 'informix'
 
-DB_DBA                 =       root
+DB_DBA                 =       freeside
 
 DB_HOST                        =       localhost
 
@@ -166,9 +198,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             =       
 
 # }}}
 
@@ -189,8 +221,11 @@ instruct:
        @echo ""
        @echo "You must now configure RT by editing $(SITE_CONFIG_FILE)."
        @echo ""
-       @echo "(You will definitely need to set RT's database password before continuing."
-       @echo " Not doing so could be very dangerous)"
+       @echo "(You will definitely need to set RT's database password in "
+       @echo "$(SITE_CONFIG_FILE) before continuing. Not doing so could be "
+       @echo "very dangerous.  Note that you do not have to manually add a "
+       @echo "database user or set up a database for RT.  These actions will be "
+       @echo "taken care of in the next step.)"
        @echo ""
        @echo "After that, you need to initialize RT's database by running" 
        @echo " 'make initialize-database'"
@@ -206,9 +241,12 @@ upgrade-instruct:
        @echo "$(CONFIG_FILE) for any necessary site customization. Additionally,"
        @echo "you should update RT's system database objects by running "
        @echo "   ls etc/upgrade"
-       @echo "For each file in that directory whose name is greater than"
+       @echo ""
+       @echo "For each item in that directory whose name is greater than"
        @echo "your previously installed RT version, run:"
-       @echo "    $(RT_SBIN_PATH)/rt-setup-database --action insert --datafile etc/upgrade/<version>"
+       @echo "    $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action schema --datadir etc/upgrade/<version>"
+       @echo "    $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action acl --datadir etc/upgrade/<version>"
+       @echo "    $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action insert --datadir etc/upgrade/<version>"
 
 
 upgrade: config-install dirs files-install fixperms upgrade-instruct
@@ -218,10 +256,12 @@ upgrade-noclobber: config-install libs-install html-install bin-install local-in
 
 # {{{ dependencies
 testdeps:
-       $(PERL) ./sbin/rt-test-dependencies --with-$(DB_TYPE)
+       $(PERL) ./sbin/rt-test-dependencies --verbose --with-$(DB_TYPE)
+
+depends: fixdeps
 
 fixdeps:
-       $(PERL) ./sbin/rt-test-dependencies --install --with-$(DB_TYPE)
+       $(PERL) ./sbin/rt-test-dependencies --verbose --install --with-$(DB_TYPE)
 
 #}}}
 
@@ -241,18 +281,17 @@ fixperms:
        chmod 0500 $(DESTDIR)/$(RT_ETC_PATH)/*
 
        #TODO: the config file should probably be able to have its
-       # owner set seperately from the binaries.
+       # owner set separately from the binaries.
        chown -R $(BIN_OWNER) $(DESTDIR)/$(RT_ETC_PATH)
        chgrp -R $(RTGROUP) $(DESTDIR)/$(RT_ETC_PATH)
 
        chmod 0550 $(DESTDIR)/$(CONFIG_FILE)
        chmod 0550 $(DESTDIR)/$(SITE_CONFIG_FILE)
 
-       # Make the interfaces executable and setgid rt
+       # Make the interfaces executable
        chown $(BIN_OWNER) $(BINARIES)
        chgrp $(RTGROUP) $(BINARIES)
        chmod 0755  $(BINARIES)
-       chmod g+s $(SETGID_BINARIES)
 
        # Make the web ui readable by all. 
        chmod -R  u+rwX,go-w,go+rX      $(DESTDIR)/$(MASON_HTML_PATH) \
@@ -272,12 +311,6 @@ fixperms:
                                $(DESTDIR)/$(MASON_SESSION_PATH)
 # }}}
 
-fixperms-nosetgid: fixperms
-       @echo "You should never be running RT this way. it's unsafe"
-       chmod 0555 $(SETGID_BINARIES)
-       chmod 0555 $(DESTDIR)/$(CONFIG_FILE)
-       chmod 0555 $(DESTDIR)/$(SITE_CONFIG_FILE)
-
 # {{{ dirs
 dirs:
        mkdir -p $(DESTDIR)/$(RT_LOG_PATH)
@@ -289,6 +322,7 @@ dirs:
        mkdir -p $(DESTDIR)/$(MASON_HTML_PATH)
        mkdir -p $(DESTDIR)/$(MASON_LOCAL_HTML_PATH)
        mkdir -p $(DESTDIR)/$(LOCAL_ETC_PATH)
+       mkdir -p $(DESTDIR)/$(LOCAL_LIB_PATH)
        mkdir -p $(DESTDIR)/$(LOCAL_LEXICON_PATH)
 # }}}
 
@@ -298,7 +332,7 @@ files-install: libs-install etc-install bin-install sbin-install html-install lo
 
 config-install:
        mkdir -p $(DESTDIR)/$(CONFIG_FILE_PATH) 
-       cp etc/RT_Config.pm $(DESTDIR)/$(CONFIG_FILE)
+       -cp etc/RT_Config.pm $(DESTDIR)/$(CONFIG_FILE)
        [ -f $(DESTDIR)/$(SITE_CONFIG_FILE) ] || cp etc/RT_SiteConfig.pm $(DESTDIR)/$(SITE_CONFIG_FILE) 
 
        chgrp $(RTGROUP) $(DESTDIR)/$(CONFIG_FILE)
@@ -315,14 +349,13 @@ test:
 regression-install: config-install
        $(PERL) -pi -e 's/Set\(\$$DatabaseName.*\);/Set\(\$$DatabaseName, "rt3regression"\);/' $(DESTDIR)/$(CONFIG_FILE)
 
-regression-nosetgid-quiet: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db  testify-pods fixperms-nosetgid apachectl
-       $(PERL) sbin/regression_harness
+regression: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db  testify-pods fixperms apachectl run-regression
 
-regression-nosetgid: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db  testify-pods fixperms-nosetgid apachectl
-       $(PERL) lib/t/02regression.t
+run-regression:
+       prove -Ilib lib/t/setup_regression.t  lib/t/autogen/ lib/t/regression/
 
-regression: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db  testify-pods fixperms apachectl
-       $(PERL) lib/t/02regression.t
+
+regression-noapache: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db  testify-pods fixperms start-httpd  run-regression
 
 regression-quiet:
        $(PERL) sbin/regression_harness
@@ -334,9 +367,11 @@ regression-instruct:
 # {{{ database-installation
 
 regression-reset-db:
-       $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --dba-password ''
+       $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --dba-password '' --force
        $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --dba-password ''
 
+initdb :: initialize-database
+
 initialize-database: 
        $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --prompt-for-dba-password
 
@@ -349,13 +384,13 @@ insert-approval-data:
 
 # {{{ libs-install
 libs-install: 
-       [ -d $(DESTDIR)/$(RT_LIB_PATH) ] || mkdir $(DESTDIR)/$(RT_LIB_PATH)
+       [ -d $(DESTDIR)/$(RT_LIB_PATH) ] || mkdir -p $(DESTDIR)/$(RT_LIB_PATH)
        -cp -rp lib/* $(DESTDIR)/$(RT_LIB_PATH)
 # }}}
 
 # {{{ html-install
 html-install:
-       [ -d $(DESTDIR)/$(MASON_HTML_PATH) ] || mkdir $(DESTDIR)/$(MASON_HTML_PATH)
+       [ -d $(DESTDIR)/$(MASON_HTML_PATH) ] || mkdir -p $(DESTDIR)/$(MASON_HTML_PATH)
        -cp -rp ./html/* $(DESTDIR)/$(MASON_HTML_PATH)
 # }}}
 
@@ -363,7 +398,7 @@ html-install:
 doc-install:
        # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir
        -[ -f $(DESTDIR)/$(RT_DOC_PATH) ] && rm $(DESTDIR)/$(RT_DOC_PATH) 
-       [ -d $(DESTDIR)/$(RT_DOC_PATH) ] || mkdir $(DESTDIR)/$(RT_DOC_PATH)
+       [ -d $(DESTDIR)/$(RT_DOC_PATH) ] || mkdir -p $(DESTDIR)/$(RT_DOC_PATH)
        -cp -rp ./README $(DESTDIR)/$(RT_DOC_PATH)
 # }}}
 
@@ -382,9 +417,12 @@ etc-install:
 
 sbin-install:
        mkdir -p $(DESTDIR)/$(RT_SBIN_PATH)
-       chmod +x sbin/rt-setup-database \
+       chmod +x \
+               sbin/rt-dump-database \
+               sbin/rt-setup-database \
                sbin/rt-test-dependencies
        -cp -rp \
+               sbin/rt-dump-database \
                sbin/rt-setup-database \
                sbin/rt-test-dependencies \
                $(DESTDIR)/$(RT_SBIN_PATH)
@@ -401,6 +439,7 @@ bin-install:
                bin/rt-mailgate \
                bin/mason_handler.fcgi \
                bin/mason_handler.scgi \
+               bin/standalone_httpd \
                bin/mason_handler.svc \
                bin/rt \
                bin/webmux.pl \
@@ -422,8 +461,10 @@ POD2TEST_EXE = sbin/extract_pod_tests
 
 testify-pods:
        [ -d lib/t/autogen ] || mkdir lib/t/autogen
-       find lib -name \*pm |grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE)
-       find bin -type f |grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE)
+       find lib -name \*pm |grep -v .svn | grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE)
+       find bin -type f |grep -v .svn | grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE)
+       find lib -name \*pm |grep -v .svn | grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE)
+       find bin -type f |grep -v .svn | grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE)
 
 
 
@@ -436,55 +477,18 @@ license-tag:
 factory: initialize-database
        cd lib; $(PERL) ../sbin/factory  $(DB_DATABASE) RT
 
-commit:
-       aegis -build ; aegis -diff ; aegis -test; aegis -develop_end
-
-integrate:
-       aegis -integrate_begin; aegis -build; aegis -diff; aegis -test ; aegis -integrate_pass
-
-predist: commit tag-and-tar
-
-tag-and-release-baseline:
-       aegis -cp -ind Makefile -output /tmp/Makefile.tagandrelease; \
-       $(MAKE) -f /tmp/Makefile.tagandrelease tag-and-release-never-by-hand
-
-
-# Running this target in a working directory is 
-# WRONG WRONG WRONG.
-# it will tag the current baseline with the version of RT defined 
-# in the currently-being-worked-on makefile. which is wrong.
-#  you want tag-and-release-baseline
-
-tag-and-release-never-by-hand:
-       aegis --delta-name $(TAG)
-       rm -rf /tmp/$(TAG)
-       mkdir /tmp/$(TAG)
-       cd /tmp/$(TAG); \
-               aegis -cp -ind -delta $(TAG) . ;\
-               make reconfigure;\
-               chmod 600 Makefile;\
-               aegis --report --project rt.$(RT_VERSION_MAJOR) \
-                     --page_width 80 \
-                     --page_length 9999 \
-                     --change $(RT_VERSION_MINOR) --output Changelog Change_Log
-
-       cd /tmp; tar czvf /home/ftp/pub/rt/devel/$(TAG).tar.gz $(TAG)/
-       chmod 644 /home/ftp/pub/rt/devel/$(TAG).tar.gz
-
-
 reconfigure:
        aclocal -I m4
        autoconf
        chmod 755 ./configure
        ./configure
 
-rpm:
-       (cd ..; tar czvf /usr/src/redhat/SOURCES/rt.tar.gz rt)
-       rpm -ba etc/rt.spec
-
+start-httpd:
+       $(PERL) bin/standalone_httpd &
 
 apachectl:
-       apachectl stop
-       sleep 3
-       apachectl start
+       $(APACHECTL) stop
+       sleep 10
+       $(APACHECTL) start
+       sleep 5
 # }}}
index ca88d2e..7c5e4d4 100755 (executable)
--- a/rt/README
+++ b/rt/README
@@ -1,46 +1,88 @@
-RT is an enterprise-grade issue tracking system. It allows organizations
-to keep track of what needs to get done, who is working on which tasks,
-what's already been done, and when tasks were (or weren't) completed.
-
-RT doesn't cost anything to use, no matter how much you use it; it
-is freely available under the terms of Version 2 of the GNU General
-Public License.
-
-RT is commercially-supported software. To purchase support, training,
-custom development, or professional services, please get in touch with
-us at sales@bestpractical.com.
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+RT is an enterprise-grade issue tracking system. It allows
+organizations to keep track of their to-do lists, who is working
+on which tasks, what's already been done, and when tasks were
+completed. It is available under the terms of version 2 of the GNU
+General Public License (GPL), so it doesn't cost anything to set
+up and use.
 
-     Jesse Vincent
-     Best Practical Solutions, LLC
-     March, 2005
 
+        Jesse Vincent
+        Best Practical Solutions, LLC
+        March 2003
 
 REQUIRED PACKAGES:
 ------------------
 
-o   Perl 5.8.3 or later (http://www.perl.com).
+o   Perl 5.8.0 or later (http://www.perl.com).
 
-       Perl versions prior to 5.8.3 contain bugs that could result
-       in data corruption. We recommend strongly that you use 5.8.3
-       or newer.
+       (If you intend to use the FastCGI or SpeedyCGI support, you 
+        need to make sure that perl has been built with support for 
+        setgid perl scripts.)`
 
-o   A supported SQL database
+    Perl 5.6.1 is currently deprecated and will be officially desupported
+    in a future release
 
-        Currently supported:  Mysql 4.0.13 or later with InnoDB support.
+o   A DB backend; MySQL is recommended ( http://www.mysql.com ) 
+        Currently supported:  Mysql 4.0.13 or later. 
                               Postgres 7.2 or later.
-                              Oracle 9iR2 or later.
-                              SQLite 3.0. (Not recommended for production)
+
+                              Mysql 3.23.46 or newer with support for InnoDB 
+                             is currently deprecated and will be officially
+                             desupported in a future release.
 
 o   Apache version 1.3.x or 2.x (http://httpd.apache.org) 
-        with mod_perl -- (http://perl.apache.org ) 
-        or a webserver with FastCGI support (www.fastcgi.com)
+    with mod_perl -- (http://perl.apache.org ) 
+    or a webserver with FastCGI support (www.fastcgi.com)
+
+        mod_perl 2.0 isn't quite ready for prime_time just yet;
+        Best Practical Solutions strongly recommends that sites use 
+        Apache 1.3 or FastCGI.
 
         Compiling mod_perl on Apache 1.3.x as a DSO has been known 
-        to have massive stability problems and is not recommended.
+         to have massive stability problems and is not recommended.
+
+        mod_perl 1.x must be build with EVERYTHING=1
+
+        RT's FastCGI handler runs setgid to the 'rt' group to
+        protect RT's database password.  You may need to install
+        a special  "suidperl" package or reconfigure your perl
+        setup to support "setuid scripts" if you intend to use RT
+        with FastCGI.
+
+        Debian GNU/* 3.0+: the package which installs suidperl is
+         called perl-suid, and should work without any tweaking.
+
+        FreeBSD 4.2+: the package is called sperl, and should
+         install a suidperl that just works 
+
+        Conectiva Linux 6.0+: suidperl is installed by default when 
+         perl is installed, but the program /bin/suidperl is not setuid. 
+         You must use chmod to make it setuid.
 
-        mod_perl 1.x must be built with EVERYTHING=1
 
-        RT's FastCGI handler needs to access RT's configuration file.
 
 o    Various and sundry perl modules
        A tool included with RT takes care of the installation of
@@ -48,190 +90,133 @@ o    Various and sundry perl modules
 
        The tool supplied with RT uses Perl's CPAN system
        (http://www.cpan.org) to install modules. Some operating
-       systems package all or some of the modules required, and
+       systems package all or some of the modules required and
        you may be better off installing the modules that way.
 
 
 GENERAL INSTALLATION
 --------------------
 
-This is a rough guide to installing RT. For more detail, you'll
-want to read a more comprehensive installation guide at:
+This is a rough guide to installing RT. For more detail, you'll want 
+to read 'Chapter 2: Installing' in RT's manual, available at
+http://www.bestpractical.com/rt 
 
-    http://wiki.bestpractical.com/index.cgi?InstallationGuides
+1   Unpack this distribution SOMWHERE OTHER THAN where you want to install RT
 
-1   Unpack this distribution other than where you want to install RT
+        Granted, you've already got it open. To do this cleanly:
 
-     To do this cleanly, run the following command:
-
-       tar xzvf rt.tar.gz -C /tmp
+                tar xzvf rt.tar.gz -C /tmp
 
 2   Run the "configure" script. 
 
-       ./configure --help to see the list of options
-       ./configure (with the flags you want)
-
-    RT defaults to installing in /opt/rt3 with MySQL as its database. It
-    tries to guess which of www-data, www, apache or nobody your webserver
-    will run as, but you can override that behavior.
-
-3   Make sure that RT has everything it needs to run.
-
-    Check for missing dependencies by running:
-
-       make testdeps        
-
-4   If the script reports any missing dependencies, install them by hand
-    or run the following command as a user who has permission to install perl
-    modules on your system:
-
-     make fixdeps
-
-5   Check to make sure everything was installed properly.
-     
-       make testdeps
-
-     It might sometimes be necessary to run "make fixdeps" several times
-     to install all necessary perl modules.
-
-6   If this is a new installation:
-     
-     As a user with permission to install RT in your chosen directory, type:
-
-       make install   
-                    
-     Set up etc/RT_SiteConfig.pm in your RT installation directory.
-     You'll need to add any values you need to change from the defaults 
-     in etc/RT_Config.pm
-
-     As a user with permission to read RT's configuration file, type:
-     
-       make initialize-database 
+        ./configure --help to see the list of options
+        ./configure (with the flags you want)
 
-     If the make fails, type:
-     
-       make dropdb 
+3   Satisfy RT's myriad dependencies. 
 
-     and start over from step 6
+3.1   Check for compliance:
+        
+   perl sbin/rt-test-dependencies \ 
+                --with-<databasename> --with-<web-environment>
 
-7   If you're upgrading from RT 3.0 or newer:
+        databasename is one of: mysql, postgres
+        web-environment is one of: fastcgi, modperl1, modperl2
 
-     Read through the UPGRADING document included in this distribution.
-     
-     It includes special upgrade instructions that will help you get this
-     new version of RT up and running smoothly.
+3.2   If there are unsatisfied dependencies, install them by hand or run:
 
-     As a user with permission to install RT in your chosen installation
-     directory, type: 
+        perl sbin/rt-test-dependencies \
+                --with-<databasename> --with-<web-environment> --install
+        
 
-       make upgrade    
+3.3   Check to make sure everything was installed properly:
 
-     This will install new binaries, config files and libraries without
-     overwriting your RT database. 
+        perl sbin/rt-test-dependencies \
+                --with-<databasename> --with-<web-environment>
 
-     Update etc/RT_SiteConfig.pm in your RT installation directory.
-     You'll need to add any new values you need to change from the defaults 
-     in etc/RT_Config.pm
+4   Create a group called 'rt'
 
-     You may also need to update RT's database.  To find out, type:
+5a  FOR A NEW INSTALLATION: 
+        
+        As root, type:
+                 make install        (replace "make" with the local name for 
+                                 Make, if you need to)
 
-       ls etc/upgrade
+                       
+                 make initialize-database 
 
-     For each item in that directory whose name is greater than
-     your previously installed RT version, run:
 
-       /opt/rt3/sbin/rt-setup-database --action schema \
-           --datadir etc/upgrade/<version>
-       /opt/rt3/sbin/rt-setup-database --action acl \
-           --datadir etc/upgrade/<version>
-       /opt/rt3/sbin/rt-setup-database --action insert \
-            --datadir etc/upgrade/<version>
+        If the make fails, type:
+                make dropdb 
+        and start over from step 5a
 
-     Clear mason cache dir:
+5b  FOR UPGRADING: (Within the RT 3.0.x series)
 
-       rm -fr /opt/rt3/var/mason_data/obj
+        As root, type: 
+                make upgrade     (replace "make" with the local name for 
+                                  Make, if you need to)
 
-     Stop and start web-server.
+        This will build new binaries, config files and libraries without
+        overwriting your RT database. 
+        
+        It may then instruct you to update your RT system database objects 
 
+6   Edit etc/RT_SiteConfig.pm in your RT installation directory, by specifying
+    any values you need to change from the defaults in etc/RT_Config.pm
 
-8  If you're upgrading from RT 2.0:
+7   Configure the email and web gateways, as described below. 
 
-    Please upgrade from RT 2.0 to RT 3.2 and then follow the instructions
-    for section 7.
-
-9   Configure the email and web gateways, as described below. 
+8   Stop and start your webserver, so it picks up your configuration changes.
 
     NOTE: root's password for the web interface is "password" 
-    (without the quotes).  Not changing this is a SECURITY risk!
+    (without the quotes.)  Not changing this is a SECURITY risk
     
-10   Set up users, groups, queues, scrips and access control.
+9   Configure RT per the instructions in RT's manual.
 
     Until you do this, RT will not be able to send or receive email,
     nor will it be more than marginally functional.  This is not an
     optional step.
 
 
-SETTING UP THE WEB INTERFACE
-----------------------------
-
-RT's web interface is based around HTML::Mason, which works well with
-the mod_perl perl interpreter within Apache httpd and FastCGI
-
-mod_perl
---------
+THE WEB INTERFACE
+-----------------
 
-To install RT with mod_perl, you'll need to install the
-apache database connection cache.  To make sure it's installed, run
-the following command:
+RT's web interface is based around HTML::Mason, which works best with the mod_perl
+perl interpreter within Apache httpd.  Alternatively, support for the FastCGI
+(and plain CGI) interface is also provided as 'bin/mason_handler.fcgi'.
 
-    perl -MCPAN -e'install Apache::DBI'
-
-Next, add a few lines to your Apache configuration file, so that
-it knows where to find RT:
+Apache 
+        You'll need to add a few lines to your httpd.conf telling it about RT:
 
 <VirtualHost your.ip.address>
     ServerName your.rt.server.hostname
     DocumentRoot /opt/rt3/share/html
     AddDefaultCharset UTF-8
 
+    # this line applies to Apache2+mod_perl2 only
+    PerlModule Apache2 Apache::compat
+
     PerlModule Apache::DBI
     PerlRequire /opt/rt3/bin/webmux.pl
 
+    # this section applies to Apache 1 only
     <Location />
-     SetHandler perl-script
-     PerlHandler RT::Mason
+        SetHandler perl-script
+        PerlHandler RT::Mason
     </Location>
-</VirtualHost>
-
-FastCGI
--------
-
-Installation with FastCGI is a little bit more complex and is documented 
-in detail at http://wiki.bestpractical.com/index.cgi?FastCGIConfiguration
-
-In the most basic configuration, you can set up your webserver to run
-as a user who is a member of the "rt" unix group so that the FastCGI script
-can read RT's configuration file.  It's important to understand the security
-implications of this configuration, which are discussed in the document
-mentioned above.
-
-To install RT with FastCGI, you'll need to add a few lines to your 
-Apache configuration file telling it about RT:
-
-
-# Tell FastCGI to put its temporary files somewhere sane.
-FastCgiIpcDir /tmp
 
-FastCgiServer /opt/rt3/bin/mason_handler.fcgi -idle-timeout 120
-
-<VirtualHost rt.example.com>
-
-   # Pass through requests to display images
-   Alias /NoAuth/images/ /opt/rt3/share/html/NoAuth/images/
-
-   AddHandler fastcgi-script fcgi
-   ScriptAlias / /opt/rt3/bin/mason_handler.fcgi/
-   
+    # this section applies to Apache2+mod_perl2 only
+    <FilesMatch "\.html$">
+        SetHandler perl-script
+        PerlHandler RT::Mason
+    </FilesMatch>
+    <LocationMatch "/Attachment/">
+        SetHandler perl-script
+        PerlHandler RT::Mason
+    </LocationMatch>
+    <LocationMatch "/REST/">
+        SetHandler perl-script
+        PerlHandler RT::Mason
+    </LocationMatch>
 </VirtualHost>
 
 
@@ -239,63 +224,55 @@ FastCgiServer /opt/rt3/bin/mason_handler.fcgi -idle-timeout 120
 SETTING UP THE MAIL GATEWAY 
 ---------------------------
 
-To let email flow to your RT server, you need to add a few lines of
-configuration to your mail server's "aliases" file. These lines "pipe"
-incoming email messages from your mail server to RT.
+An alias for the initial queue will need to be made in either your
+global mail aliases file (if you are using NIS) or locally on your
+machine.
+Add the following lines to /etc/aliases (or your local equivalent) :
 
-Add the following lines to /etc/aliases (or your local equivalent) on your mail server:
+rt:         "|/opt/rt3/bin/rt-mailgate --queue general --action correspond --url http://localhost/"
+rt-comment: "|/opt/rt3/bin/rt-mailgate --queue general --action comment --url http://localhost/"
+                                            |                |             |
+                            <queue-name>----/                |             |
+                                                             |             |
+               <correspond or comment depending on whether   |             |
+               the mail should be resent to the requestor>---/             |
+                                                                           |
+                                            <URL for RT's web interface>---/
 
-rt:         "|/opt/rt3/bin/rt-mailgate --queue general --action correspond --url http://rt.example.com/"
-rt-comment: "|/opt/rt3/bin/rt-mailgate --queue general --action comment --url http://rt.example.com/"
-
-You'll need to add similar lines for each queue you want to be able
-to send email to. To find out more about how to configure RT's email
-gateway, type:
-
-       perldoc /opt/rt3/bin/rt-mailgate
 
+BUGS
+----
 
+To report a bug, send email to rt-3.0-bugs@fsck.com.
 
 GETTING HELP
 ------------
 
 If RT is mission-critical for you or if you use it heavily, we recommend that
 you purchase a commercial support contract.  Details on support contracts
-are available at http://www.bestpractical.com or by writing to
-<sales@bestpractical.com>.
+are available at http://www.bestpractical.com.
 
 If you're interested in having RT extended or customized or would like more
 information about commercial support options, please send email to 
 <sales@bestpractical.com> to discuss rates and availability.
 
 
-
-RT WEBSITE
-----------
-
-For current information about RT, check out the RT website at 
-     http://www.bestpractical.com/  
-
-You'll find screenshots, a pointer to the current version of RT, contributed 
-patches, and lots of other great stuff.
-
-
-
-RT-USERS MAILING LIST
+RT-USERS MAILINGLIST
 --------------------
 
 To keep up to date on the latest RT tips, techniques and extensions,
 you probably want to join the rt-users mailing list.  Send a message to:
 
-      rt-users-request@lists.bestpractical.com 
+         rt-users-request@lists.fsck.com 
 
-with the body of the message consisting of only the word:
+With the body of the message consisting of only the word:
 
-     subscribe
+        subscribe
 
 If you're interested in hacking on RT, you'll want to subscribe to
-<rt-devel@lists.bestpractical.com>.  Subscribe to it with instructions
-similar to those above.
+rt-devel@lists.fsck.com.  Subscribe to it with instructions similar to
+those above.
 
 Address questions about the stable release to the rt-users list, and
 questions about the development version to the rt-devel list.  If you feel
@@ -303,63 +280,21 @@ your questions are best not asked publicly, send them personally to
 <jesse@bestpractical.com>.
 
 
+RT WEBSITE
+----------
 
-BUGS
-----
+For current information about RT, check out the RT website at 
+        http://www.bestpractical.com/  
 
-RT's a pretty complex application, and as you get up to speed, you might
-run into some trouble. Generally, it's best to ask about things you
-run into on the rt-users mailinglist (or pick up a commercial support
-contract from Best Practical). But, sometimes people do run into bugs. In
-the exceedingly unlikely event that you hit a bug in RT, please report
-it! We'd love to hear about problems you have with RT, so we can fix them.
-To report a bug, send email to rt-bugs@fsck.com.
+You'll find screenshots, a pointer to the current version of RT, contributed 
+patches, and lots of other great stuff.
 
 
-# BEGIN BPS TAGGED BLOCK {{{
-# 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
-# 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
-# 
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-# 
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-# 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-# 
-# END BPS TAGGED BLOCK }}}
+TROUBLESHOOTING
+---------------
+
+If the solution to the problem you're running into isn't obvious and you've 
+checked the FAQ, feel free to send mail to rt-users@fsck.com (for released 
+versions of RT) or rt-devel@fsck.com (for development versions).
+
+Thanks!
index 93d1f88..38f5901 100755 (executable)
@@ -1,9 +1,15 @@
 #!/usr/bin/perl
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
 # 
-# END LICENSE BLOCK
+# END BPS TAGGED BLOCK }}}
+package RT::Mason;
 
 use strict;
+use vars '$Handler';
 use File::Basename;
 require ('/opt/rt3/bin/webmux.pl');
 
-my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
-
 # Enter CGI::Fast mode, which should also work as a vanilla CGI script.
 require CGI::Fast;
 
 RT::Init();
 
-# Response loop
 while ( my $cgi = CGI::Fast->new ) {
     # the whole point of fastcgi requires the env to get reset here..
     # So we must squash it again
@@ -44,24 +67,19 @@ while ( my $cgi = CGI::Fast->new ) {
     $ENV{'ENV'}    = '' if defined $ENV{'ENV'};
     $ENV{'IFS'}    = '' if defined $ENV{'IFS'};
 
+    Module::Refresh->refresh if $RT::DevelMode;
     RT::ConnectToDatabase();
 
-    if ( ( !$h->interp->comp_exists( $cgi->path_info ) )
-        && ( $h->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) {
+    if ( ( !$Handler->interp->comp_exists( $cgi->path_info ) )
+        && ( $Handler->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) {
         $cgi->path_info( $cgi->path_info . "/index.html" );
     }
 
-    eval { $h->handle_cgi_object($cgi); };
+    eval { $Handler->handle_cgi_object($cgi); };
     if ($@) {
         $RT::Logger->crit($@);
     }
-
-
-    if ($RT::Handle->TransactionDepth) {
-        $RT::Handle->ForceRollback;
-        $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ;
-    }
-
+    RT::Interface::Web::Handler->CleanupRequest(); 
 
 }
 
index 7774189..faff8a5 100755 (executable)
@@ -1,9 +1,15 @@
 #!/usr/local/bin/speedy
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# END LICENSE BLOCK
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+package RT::Mason;
 
 use strict;
+use vars '$Handler';
 require ('/opt/rt3/bin/webmux.pl');
 
-my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
-
 require CGI;
 
 RT::Init();
 
 my $cgi = CGI->new;
-if ( ( !$h->interp->comp_exists( $cgi->path_info ) )
-    && ( $h->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) {
+if ( ( !$Handler->interp->comp_exists( $cgi->path_info ) )
+    && ( $Handler->interp->comp_exists( $cgi->path_info . "/index.html" ) ) ) {
     $cgi->path_info( $cgi->path_info . "/index.html" );
 }
 
-$h->handle_cgi_object($cgi);
-
+$Handler->handle_cgi_object($cgi);
+RT::Interface::Web::Handler->CleanupRequest();
 1;
index c05d21e..fc97da9 100644 (file)
@@ -1,9 +1,15 @@
 #!/usr/bin/perl
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
 # 
 # 
-# END LICENSE BLOCK
-
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license 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
 
 mason_handler.svc - Win32 IIS Service handler for RT
@@ -55,8 +78,11 @@ registry setting will also be automatically populated.
 
 =cut
 
+package RT::Mason;
+
 use strict;
 use File::Basename;
+use vars '$Handler';
 require (dirname(__FILE__) . '/webmux.pl');
 
 use Cwd;
@@ -197,7 +223,6 @@ BEGIN {
 warn "Begin listening on $ENV{'FCGI_SOCKET_PATH'}\n";
 
 require CGI::Fast;
-my $h = &RT::Interface::Web::NewCGIHandler(@RT::MasonParameters);
 
 RT::Init();
 
@@ -212,7 +237,8 @@ while( my $cgi = CGI::Fast->new ) {
     
     warn "Serving $comp\n";
 
-    $h->handle_cgi($comp);
+    $Handler->handle_cgi($comp);
+    RT::Interface::Web::Handler->CleanupRequest();
     # _should_ always be tied
 }
 
diff --git a/rt/bin/rt b/rt/bin/rt
deleted file mode 100755 (executable)
index d9f8a3f..0000000
--- a/rt/bin/rt
+++ /dev/null
@@ -1,1816 +0,0 @@
-#!/usr/bin/perl -w
-# BEGIN LICENSE BLOCK
-# 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-# 
-# (Except where explictly superceded by other copyright notices)
-# 
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-# 
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
-# 
-# 
-# END LICENSE BLOCK
-
-use strict;
-
-# This program is intentionally written to have as few non-core module
-# dependencies as possible. It should stay that way.
-
-use Cwd;
-use LWP;
-use HTTP::Request::Common;
-
-# We derive configuration information from hardwired defaults, dotfiles,
-# and the RT* environment variables (in increasing order of precedence).
-# Session information is stored in ~/.rt_sessions.
-
-my $VERSION = 0.02;
-my $HOME = eval{(getpwuid($<))[7]}
-           || $ENV{HOME} || $ENV{LOGDIR} || $ENV{HOMEPATH}
-           || ".";
-my %config = (
-    (
-        debug   => 0,
-        user    => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME},
-        passwd  => undef,
-        server  => 'http://localhost/rt/',
-    ),
-    config_from_file($ENV{RTCONFIG} || ".rtrc"),
-    config_from_env()
-);
-my $session = new Session("$HOME/.rt_sessions");
-my $REST = "$config{server}/REST/1.0";
-
-sub whine;
-sub DEBUG { warn @_ if $config{debug} >= shift }
-
-# These regexes are used by command handlers to parse arguments.
-# (XXX: Ask Autrijus how i18n changes these definitions.)
-
-my $name   = '[\w.-]+';
-my $field  = '[a-zA-Z][a-zA-Z0-9_-]*';
-my $label  = '[a-zA-Z0-9@_.+-]+';
-my $labels = "(?:$label,)*$label";
-my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+';
-
-# Our command line looks like this:
-#
-#     rt <action> [options] [arguments]
-#
-# We'll parse just enough of it to decide upon an action to perform, and
-# leave the rest to per-action handlers to interpret appropriately.
-
-my %handlers = (
-#   handler     => [ ...aliases... ],
-    version     => ["version", "ver"],
-    logout      => ["logout"],
-    help        => ["help", "man"],
-    show        => ["show", "cat"],
-    edit        => ["create", "edit", "new", "ed"],
-    list        => ["search", "list", "ls"],
-    comment     => ["comment", "correspond"],
-    link        => ["link", "ln"],
-    merge       => ["merge"],
-    grant       => ["grant", "revoke"],
-);
-
-# Once we find and call an appropriate handler, we're done.
-
-my (%actions, $action);
-foreach my $fn (keys %handlers) {
-    foreach my $alias (@{ $handlers{$fn} }) {
-        $actions{$alias} = \&{"$fn"};
-    }
-}
-if (@ARGV && exists $actions{$ARGV[0]}) {
-    $action = shift @ARGV;
-}
-$actions{$action || "help"}->($action || ());
-exit;
-
-# Handler functions.
-# ------------------
-#
-# The following subs are handlers for each entry in %actions.
-
-sub version {
-    print "rt $VERSION\n";
-}
-
-sub logout {
-    submit("$REST/logout") if defined $session->cookie;
-}
-
-sub help {
-    my ($action, $type) = @_;
-    my (%help, $key);
-
-    # What help topics do we know about?
-    local $/ = undef;
-    foreach my $item (@{ Form::parse(<DATA>) }) {
-        my $title = $item->[2]{Title};
-        my @titles = ref $title eq 'ARRAY' ? @$title : $title;
-
-        foreach $title (grep $_, @titles) {
-            $help{$title} = $item->[2]{Text};
-        }
-    }
-
-    # What does the user want help with?
-    undef $action if ($action && $actions{$action} eq \&help);
-    unless ($action || $type) {
-        # If we don't know, we'll look for clues in @ARGV.
-        foreach (@ARGV) {
-            if (exists $help{$_}) { $key = $_; last; }
-        }
-        unless ($key) {
-            # Tolerate possibly plural words.
-            foreach (@ARGV) {
-                if ($_ =~ s/s$// && exists $help{$_}) { $key = $_; last; }
-            }
-        }
-    }
-
-    if ($type && $action) {
-        $key = "$type.$action";
-    }
-    $key ||= $type || $action || "introduction";
-
-    # Find a suitable topic to display.
-    while (!exists $help{$key}) {
-        if ($type && $action) {
-            if ($key eq "$type.$action") { $key = $action;        }
-            elsif ($key eq $action)      { $key = $type;          }
-            else                         { $key = "introduction"; }
-        }
-        else {
-            $key = "introduction";
-        }
-    }
-
-    print STDERR $help{$key}, "\n\n";
-}
-
-# Displays a list of objects that match some specified condition.
-
-sub list {
-    my ($q, $type, %data, $orderby);
-    my $bad = 0;
-
-    while (@ARGV) {
-        $_ = shift @ARGV;
-
-        if (/^-t$/) {
-            $bad = 1, last unless defined($type = get_type_argument());
-        }
-        elsif (/^-S$/) {
-            $bad = 1, last unless get_var_argument(\%data);
-        }
-        elsif (/^-o$/) {
-            $orderby = shift @ARGV;
-        }
-        elsif (/^-([isl])$/) {
-            $data{format} = $1;
-        }
-        elsif (/^-f$/) {
-            if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
-                whine "No valid field list in '-f $ARGV[0]'.";
-                $bad = 1; last;
-            }
-            $data{fields} = shift @ARGV;
-        }
-        elsif (!defined $q && !/^-/) {
-            $q = $_;
-        }
-        else {
-            my $datum = /^-/ ? "option" : "argument";
-            whine "Unrecognised $datum '$_'.";
-            $bad = 1; last;
-        }
-    }
-
-    $type ||= "ticket";
-    unless ($type && defined $q) {
-        my $item = $type ? "query string" : "object type";
-        whine "No $item specified.";
-        $bad = 1;
-    }
-    return help("list", $type) if $bad;
-
-    my $r = submit("$REST/search/$type", { query => $q, %data, orderby => $orderby || "" });
-    print $r->content;
-}
-
-# Displays selected information about a single object.
-
-sub show {
-    my ($type, @objects, %data);
-    my $slurped = 0;
-    my $bad = 0;
-
-    while (@ARGV) {
-        $_ = shift @ARGV;
-
-        if (/^-t$/) {
-            $bad = 1, last unless defined($type = get_type_argument());
-        }
-        elsif (/^-S$/) {
-            $bad = 1, last unless get_var_argument(\%data);
-        }
-        elsif (/^-([isl])$/) {
-            $data{format} = $1;
-        }
-        elsif (/^-$/ && !$slurped) {
-            chomp(my @lines = <STDIN>);
-            foreach (@lines) {
-                unless (is_object_spec($_, $type)) {
-                    whine "Invalid object on STDIN: '$_'.";
-                    $bad = 1; last;
-                }
-                push @objects, $_;
-            }
-            $slurped = 1;
-        }
-        elsif (/^-f$/) {
-            if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) {
-                whine "No valid field list in '-f $ARGV[0]'.";
-                $bad = 1; last;
-            }
-            $data{fields} = shift @ARGV;
-        }
-        elsif (my $spec = is_object_spec($_, $type)) {
-            push @objects, $spec;
-        }
-        else {
-            my $datum = /^-/ ? "option" : "argument";
-            whine "Unrecognised $datum '$_'.";
-            $bad = 1; last;
-        }
-    }
-
-    unless (@objects) {
-        whine "No objects specified.";
-        $bad = 1;
-    }
-    return help("show", $type) if $bad;
-
-    my $r = submit("$REST/show", { id => \@objects, %data });
-    print $r->content;
-}
-
-# To create a new object, we ask the server for a form with the defaults
-# filled in, allow the user to edit it, and send the form back.
-#
-# To edit an object, we must ask the server for a form representing that
-# object, make changes requested by the user (either on the command line
-# or interactively via $EDITOR), and send the form back.
-
-sub edit {
-    my ($action) = @_;
-    my (%data, $type, @objects);
-    my ($cl, $text, $edit, $input, $output);
-
-    use vars qw(%set %add %del);
-    %set = %add = %del = ();
-    my $slurped = 0;
-    my $bad = 0;
-    
-    while (@ARGV) {
-        $_ = shift @ARGV;
-
-        if    (/^-e$/) { $edit = 1 }
-        elsif (/^-i$/) { $input = 1 }
-        elsif (/^-o$/) { $output = 1 }
-        elsif (/^-t$/) {
-            $bad = 1, last unless defined($type = get_type_argument());
-        }
-        elsif (/^-S$/) {
-            $bad = 1, last unless get_var_argument(\%data);
-        }
-        elsif (/^-$/ && !($slurped || $input)) {
-            chomp(my @lines = <STDIN>);
-            foreach (@lines) {
-                unless (is_object_spec($_, $type)) {
-                    whine "Invalid object on STDIN: '$_'.";
-                    $bad = 1; last;
-                }
-                push @objects, $_;
-            }
-            $slurped = 1;
-        }
-        elsif (/^set$/i) {
-            my $vars = 0;
-
-            while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/) {
-                my ($key, $op, $val) = ($1, $2, $3);
-                my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del;
-
-                vpush($hash, lc $key, $val);
-                shift @ARGV;
-                $vars++;
-            }
-            unless ($vars) {
-                whine "No variables to set.";
-                $bad = 1; last;
-            }
-            $cl = $vars;
-        }
-        elsif (/^(?:add|del)$/i) {
-            my $vars = 0;
-            my $hash = ($_ eq "add") ? \%add : \%del;
-
-            while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/) {
-                my ($key, $val) = ($1, $2);
-
-                vpush($hash, lc $key, $val);
-                shift @ARGV;
-                $vars++;
-            }
-            unless ($vars) {
-                whine "No variables to set.";
-                $bad = 1; last;
-            }
-            $cl = $vars;
-        }
-        elsif (my $spec = is_object_spec($_, $type)) {
-            push @objects, $spec;
-        }
-        else {
-            my $datum = /^-/ ? "option" : "argument";
-            whine "Unrecognised $datum '$_'.";
-            $bad = 1; last;
-        }
-    }
-
-    if ($action =~ /^ed(?:it)?$/) {
-        unless (@objects) {
-            whine "No objects specified.";
-            $bad = 1;
-        }
-    }
-    else {
-        if (@objects) {
-            whine "You shouldn't specify objects as arguments to $action.";
-            $bad = 1;
-        }
-        unless ($type) {
-            whine "What type of object do you want to create?";
-            $bad = 1;
-        }
-        @objects = ("$type/new");
-    }
-    return help($action, $type) if $bad;
-
-    # We need a form to make changes to. We usually ask the server for
-    # one, but we can avoid that if we are fed one on STDIN, or if the
-    # user doesn't want to edit the form by hand, and the command line
-    # specifies only simple variable assignments.
-
-    if ($input) {
-        local $/ = undef;
-        $text = <STDIN>;
-    }
-    elsif ($edit || %add || %del || !$cl) {
-        my $r = submit("$REST/show", { id => \@objects, format => 'l' });
-        $text = $r->content;
-    }
-
-    # If any changes were specified on the command line, apply them.
-    if ($cl) {
-        if ($text) {
-            # We're updating forms from the server.
-            my $forms = Form::parse($text);
-
-            foreach my $form (@$forms) {
-                my ($c, $o, $k, $e) = @$form;
-                my ($key, $val);
-
-                next if ($e || !@$o);
-
-                local %add = %add;
-                local %del = %del;
-                local %set = %set;
-
-                # Make changes to existing fields.
-                foreach $key (@$o) {
-                    if (exists $add{lc $key}) {
-                        $val = delete $add{lc $key};
-                        vpush($k, $key, $val);
-                        $k->{$key} = vsplit($k->{$key}) if $val =~ /[,\n]/;
-                    }
-                    if (exists $del{lc $key}) {
-                        $val = delete $del{lc $key};
-                        my %val = map {$_=>1} @{ vsplit($val) };
-                        $k->{$key} = vsplit($k->{$key});
-                        @{$k->{$key}} = grep {!exists $val{$_}} @{$k->{$key}};
-                    }
-                    if (exists $set{lc $key}) {
-                        $k->{$key} = delete $set{lc $key};
-                    }
-                }
-                
-                # Then update the others.
-                foreach $key (keys %set) { vpush($k, $key, $set{$key}) }
-                foreach $key (keys %add) {
-                    vpush($k, $key, $add{$key});
-                    $k->{$key} = vsplit($k->{$key});
-                }
-                push @$o, (keys %add, keys %set);
-            }
-
-            $text = Form::compose($forms);
-        }
-        else {
-            # We're rolling our own set of forms.
-            my @forms;
-            foreach (@objects) {
-                my ($type, $ids, $args) =
-                    m{^($name)/($idlist|$labels)(?:(/.*))?$}o;
-
-                $args ||= "";
-                foreach my $obj (expand_list($ids)) {
-                    my %set = (%set, id => "$type/$obj$args");
-                    push @forms, ["", [keys %set], \%set];
-                }
-            }
-            $text = Form::compose(\@forms);
-        }
-    }
-
-    if ($output) {
-        print $text;
-        exit;
-    }
-
-    my $synerr = 0;
-
-EDIT:
-    # We'll let the user edit the form before sending it to the server,
-    # unless we have enough information to submit it non-interactively.
-    if ($edit || (!$input && !$cl)) {
-        my $newtext = vi($text);
-        # We won't resubmit a bad form unless it was changed.
-        $text = ($synerr && $newtext eq $text) ? undef : $newtext;
-    }
-
-    if ($text) {
-        my $r = submit("$REST/edit", {content => $text, %data});
-        if ($r->code == 409) {
-            # If we submitted a bad form, we'll give the user a chance
-            # to correct it and resubmit.
-            if ($edit || (!$input && !$cl)) {
-                $text = $r->content;
-                $synerr = 1;
-                goto EDIT;
-            }
-            else {
-                print $r->content;
-                exit -1;
-            }
-        }
-        print $r->content;
-    }
-}
-
-# We roll "comment" and "correspond" into the same handler.
-
-sub comment {
-    my ($action) = @_;
-    my (%data, $id, @files, @bcc, @cc, $msg, $wtime, $edit);
-    my $bad = 0;
-
-    while (@ARGV) {
-        $_ = shift @ARGV;
-
-        if (/^-e$/) {
-            $edit = 1;
-        }
-        elsif (/^-[abcmw]$/) {
-            unless (@ARGV) {
-                whine "No argument specified with $_.";
-                $bad = 1; last;
-            }
-
-            if (/-a/) {
-                unless (-f $ARGV[0] && -r $ARGV[0]) {
-                    whine "Cannot read attachment: '$ARGV[0]'.";
-                    exit -1;
-                }
-                push @files, shift @ARGV;
-            }
-            elsif (/-([bc])/) {
-                my $a = $_ eq "-b" ? \@bcc : \@cc;
-                @$a = split /\s*,\s*/, shift @ARGV;
-            }
-            elsif (/-m/) { $msg = shift @ARGV }
-            elsif (/-w/) { $wtime = shift @ARGV }
-        }
-        elsif (!$id && m|^(?:ticket/)?($idlist)$|) {
-            $id = $1;
-        }
-        else {
-            my $datum = /^-/ ? "option" : "argument";
-            whine "Unrecognised $datum '$_'.";
-            $bad = 1; last;
-        }
-    }
-
-    unless ($id) {
-        whine "No object specified.";
-        $bad = 1;
-    }
-    return help($action, "ticket") if $bad;
-
-    my $form = [
-        "",
-        [ "Ticket", "Action", "Cc", "Bcc", "Attachment", "TimeWorked", "Text" ],
-        {
-            Ticket     => $id,
-            Action     => $action,
-            Cc         => [ @cc ],
-            Bcc        => [ @bcc ],
-            Attachment => [ @files ],
-            TimeWorked => $wtime || '',
-            Text       => $msg || '',
-        }
-    ];
-
-    my $text = Form::compose([ $form ]);
-
-    if ($edit || !$msg) {
-        my $error = 0;
-        my ($c, $o, $k, $e);
-
-        do {
-            my $ntext = vi($text);
-            exit if ($error && $ntext eq $text);
-            $text = $ntext;
-            $form = Form::parse($text);
-            $error = 0;
-
-            ($c, $o, $k, $e) = @{ $form->[0] };
-            if ($e) {
-                $error = 1;
-                $c = "# Syntax error.";
-                goto NEXT;
-            }
-            elsif (!@$o) {
-                exit;
-            }
-            @files = @{ vsplit($k->{Attachment}) };
-
-        NEXT:
-            $text = Form::compose([[$c, $o, $k, $e]]);
-        } while ($error);
-    }
-
-    my $i = 1;
-    foreach my $file (@files) {
-        $data{"attachment_$i"} = bless([ $file ], "Attachment");
-        $i++;
-    }
-    $data{content} = $text;
-
-    my $r = submit("$REST/ticket/comment/$id", \%data);
-    print $r->content;
-}
-
-# Merge one ticket into another.
-
-sub merge {
-    my @id;
-    my $bad = 0;
-
-    while (@ARGV) {
-        $_ = shift @ARGV;
-
-        if (/^\d+$/) {
-            push @id, $_;
-        }
-        else {
-            whine "Unrecognised argument: '$_'.";
-            $bad = 1; last;
-        }
-    }
-
-    unless (@id == 2) {
-        my $evil = @id > 2 ? "many" : "few";
-        whine "Too $evil arguments specified.";
-        $bad = 1;
-    }
-    return help("merge", "ticket") if $bad;
-
-    my $r = submit("$REST/ticket/merge/$id[0]", {into => $id[1]});
-    print $r->content;
-}
-
-# Link one ticket to another.
-
-sub link {
-    my ($bad, $del, %data) = (0, 0, ());
-    my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo
-                                        ReferredToBy HasMember MemberOf);
-
-    while (@ARGV && $ARGV[0] =~ /^-/) {
-        $_ = shift @ARGV;
-
-        if (/^-d$/) {
-            $del = 1;
-        }
-        else {
-            whine "Unrecognised option: '$_'.";
-            $bad = 1; last;
-        }
-    }
-
-    if (@ARGV == 3) {
-        my ($from, $rel, $to) = @ARGV;
-        if ($from !~ /^\d+$/ || $to !~ /^\d+$/) {
-            my $bad = $from =~ /^\d+$/ ? $to : $from;
-            whine "Invalid ticket ID '$bad' specified.";
-            $bad = 1;
-        }
-        unless (exists $ltypes{lc $rel}) {
-            whine "Invalid relationship '$rel' specified.";
-            $bad = 1;
-        }
-        %data = (id => $from, rel => $rel, to => $to, del => $del);
-    }
-    else {
-        my $bad = @ARGV < 3 ? "few" : "many";
-        whine "Too $bad arguments specified.";
-        $bad = 1;
-    }
-    return help("link", "ticket") if $bad;
-
-    my $r = submit("$REST/ticket/link", \%data);
-    print $r->content;
-}
-
-# Grant/revoke a user's rights.
-
-sub grant {
-    my ($cmd) = @_;
-
-    my $revoke = 0;
-    while (@ARGV) {
-    }
-
-    $revoke = 1 if $cmd->{action} eq 'revoke';
-}
-
-# Client <-> Server communication.
-# --------------------------------
-#
-# This function composes and sends an HTTP request to the RT server, and
-# interprets the response. It takes a request URI, and optional request
-# data (a string, or a reference to a set of key-value pairs).
-
-sub submit {
-    my ($uri, $content) = @_;
-    my ($req, $data);
-    my $ua = new LWP::UserAgent(agent => "RT/3.0b", env_proxy => 1);
-
-    # Did the caller specify any data to send with the request?
-    $data = [];
-    if (defined $content) {
-        unless (ref $content) {
-            # If it's just a string, make sure LWP handles it properly.
-            # (By pretending that it's a file!)
-            $content = [ content => [undef, "", Content => $content] ];
-        }
-        elsif (ref $content eq 'HASH') {
-            my @data;
-            foreach my $k (keys %$content) {
-                if (ref $content->{$k} eq 'ARRAY') {
-                    foreach my $v (@{ $content->{$k} }) {
-                        push @data, $k, $v;
-                    }
-                }
-                else { push @data, $k, $content->{$k} }
-            }
-            $content = \@data;
-        }
-        $data = $content;
-    }
-
-    # Should we send authentication information to start a new session?
-    if (!defined $session->cookie) {
-        push @$data, ( user => $config{user} );
-        push @$data, ( pass => $config{passwd} || read_passwd() );
-    }
-
-    # Now, we construct the request.
-    if (@$data) {
-        $req = POST($uri, $data, Content_Type => 'form-data');
-    }
-    else {
-        $req = GET($uri);
-    }
-    $session->add_cookie_header($req);
-
-    # Then we send the request and parse the response.
-    DEBUG(3, $req->as_string);
-    my $res = $ua->request($req);
-    DEBUG(3, $res->as_string);
-
-    if ($res->is_success) {
-        # The content of the response we get from the RT server consists
-        # of an HTTP-like status line followed by optional header lines,
-        # a blank line, and arbitrary text.
-
-        my ($head, $text) = split /\n\n/, $res->content, 2;
-        my ($status, @headers) = split /\n/, $head;
-        $text =~ s/\n*$/\n/;
-
-        # "RT/3.0.1 401 Credentials required"
-        if ($status !~ m#^RT/\d+(?:\.\d+)+(?:-?\w+)? (\d+) ([\w\s]+)$#) {
-            warn "rt: Malformed RT response from $config{server}.\n";
-            warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3;
-            exit -1;
-        }
-
-        # Our caller can pretend that the server returned a custom HTTP
-        # response code and message. (Doing that directly is apparently
-        # not sufficiently portable and uncomplicated.)
-        $res->code($1);
-        $res->message($2);
-        $res->content($text);
-        $session->update($res) if ($res->is_success || $res->code != 401);
-
-        if (!$res->is_success) {
-            # We can deal with authentication failures ourselves. Either
-            # we sent invalid credentials, or our session has expired.
-            if ($res->code == 401) {
-                my %d = @$data;
-                if (exists $d{user}) {
-                    warn "rt: Incorrect username or password.\n";
-                    exit -1;
-                }
-                elsif ($req->header("Cookie")) {
-                    # We'll retry the request with credentials, unless
-                    # we only wanted to logout in the first place.
-                    $session->delete;
-                    return submit(@_) unless $uri eq "$REST/logout";
-                }
-            }
-            # Conflicts should be dealt with by the handler and user.
-            # For anything else, we just die.
-            elsif ($res->code != 409) {
-                warn "rt: ", $res->content;
-                exit;
-            }
-        }
-    }
-    else {
-        warn "rt: Server error: ", $res->message, " (", $res->code, ")\n";
-        exit -1;
-    }
-
-    return $res;
-}
-
-# Session management.
-# -------------------
-#
-# Maintains a list of active sessions in the ~/.rt_sessions file.
-{
-    package Session;
-    my ($s, $u);
-
-    # Initialises the session cache.
-    sub new {
-        my ($class, $file) = @_;
-        my $self = {
-            file => $file || "$HOME/.rt_sessions",
-            sids => { }
-        };
-       
-        # The current session is identified by the currently configured
-        # server and user.
-        ($s, $u) = @config{"server", "user"};
-
-        bless $self, $class;
-        $self->load();
-
-        return $self;
-    }
-
-    # Returns the current session cookie.
-    sub cookie {
-        my ($self) = @_;
-        my $cookie = $self->{sids}{$s}{$u};
-        return defined $cookie ? "RT_SID=$cookie" : undef;
-    }
-
-    # Deletes the current session cookie.
-    sub delete {
-        my ($self) = @_;
-        delete $self->{sids}{$s}{$u};
-    }
-
-    # Adds a Cookie header to an outgoing HTTP request.
-    sub add_cookie_header {
-        my ($self, $request) = @_;
-        my $cookie = $self->cookie();
-
-        $request->header(Cookie => $cookie) if defined $cookie;
-    }
-
-    # Extracts the Set-Cookie header from an HTTP response, and updates
-    # session information accordingly.
-    sub update {
-        my ($self, $response) = @_;
-        my $cookie = $response->header("Set-Cookie");
-
-        if (defined $cookie && $cookie =~ /^RT_SID=([0-9A-Fa-f]+);/) {
-            $self->{sids}{$s}{$u} = $1;
-        }
-    }
-
-    # Loads the session cache from the specified file.
-    sub load {
-        my ($self, $file) = @_;
-        $file ||= $self->{file};
-        local *F;
-
-        open(F, $file) && do {
-            $self->{file} = $file;
-            my $sids = $self->{sids} = {};
-            while (<F>) {
-                chomp;
-                next if /^$/ || /^#/;
-                next unless m#^https?://[^ ]+ \w+ [0-9A-Fa-f]+$#;
-                my ($server, $user, $cookie) = split / /, $_;
-                $sids->{$server}{$user} = $cookie;
-            }
-            return 1;
-        };
-        return 0;
-    }
-
-    # Writes the current session cache to the specified file.
-    sub save {
-        my ($self, $file) = shift;
-        $file ||= $self->{file};
-        local *F;
-
-        open(F, ">$file") && do {
-            my $sids = $self->{sids};
-            foreach my $server (keys %$sids) {
-                foreach my $user (keys %{ $sids->{$server} }) {
-                    my $sid = $sids->{$server}{$user};
-                    if (defined $sid) {
-                        print F "$server $user $sid\n";
-                    }
-                }
-            }
-            close(F);
-            chmod 0600, $file;
-            return 1;
-        };
-        return 0;
-    }
-
-    sub DESTROY {
-        my $self = shift;
-        $self->save;
-    }
-}
-
-# Form handling.
-# --------------
-#
-# Forms are RFC822-style sets of (field, value) specifications with some
-# initial comments and interspersed blank lines allowed for convenience.
-# Sets of forms are separated by --\n (in a cheap parody of MIME).
-#
-# Each form is parsed into an array with four elements: commented text
-# at the start of the form, an array with the order of keys, a hash with
-# key/value pairs, and optional error text if the form syntax was wrong.
-
-# Returns a reference to an array of parsed forms.
-sub Form::parse {
-    my $state = 0;
-    my @forms = ();
-    my @lines = split /\n/, $_[0];
-    my ($c, $o, $k, $e) = ("", [], {}, "");
-
-    LINE:
-    while (@lines) {
-        my $line = shift @lines;
-
-        next LINE if $line eq '';
-
-        if ($line eq '--') {
-            # We reached the end of one form. We'll ignore it if it was
-            # empty, and store it otherwise, errors and all.
-            if ($e || $c || @$o) {
-                push @forms, [ $c, $o, $k, $e ];
-                $c = ""; $o = []; $k = {}; $e = "";
-            }
-            $state = 0;
-        }
-        elsif ($state != -1) {
-            if ($state == 0 && $line =~ /^#/) {
-                # Read an optional block of comments (only) at the start
-                # of the form.
-                $state = 1;
-                $c = $line;
-                while (@lines && $lines[0] =~ /^#/) {
-                    $c .= "\n".shift @lines;
-                }
-                $c .= "\n";
-            }
-            elsif ($state <= 1 && $line =~ /^($field):(?:\s+(.*))?$/) {
-                # Read a field: value specification.
-                my $f  = $1;
-                my @v  = ($2 || ());
-
-                # Read continuation lines, if any.
-                while (@lines && ($lines[0] eq '' || $lines[0] =~ /^\s+/)) {
-                    push @v, shift @lines;
-                }
-                pop @v while (@v && $v[-1] eq '');
-
-                # Strip longest common leading indent from text.
-                my $ws = "";
-                foreach my $ls (map {/^(\s+)/} @v[1..$#v]) {
-                    $ws = $ls if (!$ws || length($ls) < length($ws));
-                }
-                s/^$ws// foreach @v;
-
-                push(@$o, $f) unless exists $k->{$f};
-                vpush($k, $f, join("\n", @v));
-
-                $state = 1;
-            }
-            elsif ($line !~ /^#/) {
-                # We've found a syntax error, so we'll reconstruct the
-                # form parsed thus far, and add an error marker. (>>)
-                $state = -1;
-                $e = Form::compose([[ "", $o, $k, "" ]]);
-                $e.= $line =~ /^>>/ ? "$line\n" : ">> $line\n";
-            }
-        }
-        else {
-            # We saw a syntax error earlier, so we'll accumulate the
-            # contents of this form until the end.
-            $e .= "$line\n";
-        }
-    }
-    push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o);
-
-    foreach my $l (keys %$k) {
-        $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY');
-    }
-
-    return \@forms;
-}
-
-# Returns text representing a set of forms.
-sub Form::compose {
-    my ($forms) = @_;
-    my @text;
-
-    foreach my $form (@$forms) {
-        my ($c, $o, $k, $e) = @$form;
-        my $text = "";
-
-        if ($c) {
-            $c =~ s/\n*$/\n/;
-            $text = "$c\n";
-        }
-        if ($e) {
-            $text .= $e;
-        }
-        elsif ($o) {
-            my @lines;
-
-            foreach my $key (@$o) {
-                my ($line, $sp);
-                my $v = $k->{$key};
-                my @values = ref $v eq 'ARRAY' ? @$v : $v;
-
-                $sp = " "x(length("$key: "));
-                $sp = " "x4 if length($sp) > 16;
-
-                foreach $v (@values) {
-                    if ($v =~ /\n/) {
-                        $v =~ s/^/$sp/gm;
-                        $v =~ s/^$sp//;
-
-                        if ($line) {
-                            push @lines, "$line\n\n";
-                            $line = "";
-                        }
-                        elsif (@lines && $lines[-1] !~ /\n\n$/) {
-                            $lines[-1] .= "\n";
-                        }
-                        push @lines, "$key: $v\n\n";
-                    }
-                    elsif ($line &&
-                           length($line)+length($v)-rindex($line, "\n") >= 70)
-                    {
-                        $line .= ",\n$sp$v";
-                    }
-                    else {
-                        $line = $line ? "$line, $v" : "$key: $v";
-                    }
-                }
-
-                $line = "$key:" unless @values;
-                if ($line) {
-                    if ($line =~ /\n/) {
-                        if (@lines && $lines[-1] !~ /\n\n$/) {
-                            $lines[-1] .= "\n";
-                        }
-                        $line .= "\n";
-                    }
-                    push @lines, "$line\n";
-                }
-            }
-
-            $text .= join "", @lines;
-        }
-        else {
-            chomp $text;
-        }
-        push @text, $text;
-    }
-
-    return join "\n--\n\n", @text;
-}
-
-# Configuration.
-# --------------
-
-# Returns configuration information from the environment.
-sub config_from_env {
-    my %env;
-
-    foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER") {
-        if (exists $ENV{"RT$k"}) {
-            $env{lc $k} = $ENV{"RT$k"};
-        }
-    }
-
-    return %env;
-}
-
-# Finds a suitable configuration file and returns information from it.
-sub config_from_file {
-    my ($rc) = @_;
-
-    if ($rc =~ m#^/#) {
-        # We'll use an absolute path if we were given one.
-        return parse_config_file($rc);
-    }
-    else {
-        # Otherwise we'll use the first file we can find in the current
-        # directory, or in one of its (increasingly distant) ancestors.
-
-        my @dirs = split /\//, cwd;
-        while (@dirs) {
-            my $file = join('/', @dirs, $rc);
-            if (-r $file) {
-                return parse_config_file($file);
-            }
-
-            # Remove the last directory component each time.
-            pop @dirs;
-        }
-
-        # Still nothing? We'll fall back to some likely defaults.
-        for ("$HOME/$rc", "/etc/rt.conf") {
-            return parse_config_file($_) if (-r $_);
-        }
-    }
-
-    return ();
-}
-
-# Makes a hash of the specified configuration file.
-sub parse_config_file {
-    my %cfg;
-    my ($file) = @_;
-
-    open(CFG, $file) && do {
-        while (<CFG>) {
-            chomp;
-            next if (/^#/ || /^\s*$/);
-
-            if (/^(user|passwd|server)\s+([^ ]+)$/) {
-                $cfg{$1} = $2;
-            }
-            else {
-                die "rt: $file:$.: unknown configuration directive.\n";
-            }
-        }
-    };
-
-    return %cfg;
-}
-
-# Helper functions.
-# -----------------
-
-sub whine {
-    my $sub = (caller(1))[3];
-    $sub =~ s/^main:://;
-    warn "rt: $sub: @_\n";
-    return;
-}
-
-sub read_passwd {
-    eval 'require Term::ReadKey';
-    if ($@) {
-        die "No password specified (and Term::ReadKey not installed).\n";
-    }
-
-    print "Password: ";
-    Term::ReadKey::ReadMode('noecho');
-    chomp(my $passwd = Term::ReadKey::ReadLine(0));
-    Term::ReadKey::ReadMode('restore');
-    print "\n";
-
-    return $passwd;
-}
-
-sub vi {
-    my ($text) = @_;
-    my $file = "/tmp/rt.form.$$";
-    my $editor = $ENV{EDITOR} || $ENV{VISUAL} || "vi";
-
-    local *F;
-    local $/ = undef;
-
-    open(F, ">$file") || die "$file: $!\n"; print F $text; close(F);
-    system($editor, $file) && die "Couldn't run $editor.\n";
-    open(F, $file) || die "$file: $!\n"; $text = <F>; close(F);
-    unlink($file);
-
-    return $text;
-}
-
-# Add a value to a (possibly multi-valued) hash key.
-sub vpush {
-    my ($hash, $key, $val) = @_;
-    my @val = ref $val eq 'ARRAY' ? @$val : $val;
-
-    if (exists $hash->{$key}) {
-        unless (ref $hash->{$key} eq 'ARRAY') {
-            my @v = $hash->{$key} ne '' ? $hash->{$key} : ();
-            $hash->{$key} = \@v;
-        }
-        push @{ $hash->{$key} }, @val;
-    }
-    else {
-        $hash->{$key} = $val;
-    }
-}
-
-# "Normalise" a hash key that's known to be multi-valued.
-sub vsplit {
-    my ($val) = @_;
-    my ($word, @words);
-    my @values = ref $val eq 'ARRAY' ? @$val : $val;
-
-    foreach my $line (map {split /\n/} @values) {
-        # XXX: This should become a real parser, à la Text::ParseWords.
-        $line =~ s/^\s+//;
-        $line =~ s/\s+$//;
-        push @words, split /\s*,\s*/, $line;
-    }
-
-    return \@words;
-}
-
-sub expand_list {
-    my ($list) = @_;
-    my ($elt, @elts, %elts);
-
-    foreach $elt (split /,/, $list) {
-        if ($elt =~ /^(\d+)-(\d+)$/) { push @elts, ($1..$2) }
-        else                         { push @elts, $elt }
-    }
-
-    @elts{@elts}=();
-    return sort {$a<=>$b} keys %elts;
-}
-
-sub get_type_argument {
-    my $type;
-
-    if (@ARGV) {
-        $type = shift @ARGV;
-        unless ($type =~ /^[A-Za-z0-9_.-]+$/) {
-            # We want whine to mention our caller, not us.
-            @_ = ("Invalid type '$type' specified.");
-            goto &whine;
-        }
-    }
-    else {
-        @_ = ("No type argument specified with -t.");
-        goto &whine;
-    }
-
-    $type =~ s/s$//; # "Plural". Ugh.
-    return $type;
-}
-
-sub get_var_argument {
-    my ($data) = @_;
-
-    if (@ARGV) {
-        my $kv = shift @ARGV;
-        if (my ($k, $v) = $kv =~ /^($field)=(.*)$/) {
-            push @{ $data->{$k} }, $v;
-        }
-        else {
-            @_ = ("Invalid variable specification: '$kv'.");
-            goto &whine;
-        }
-    }
-    else {
-        @_ = ("No variable argument specified with -S.");
-        goto &whine;
-    }
-}
-
-sub is_object_spec {
-    my ($spec, $type) = @_;
-
-    $spec =~ s|^(?:$type/)?|$type/| if defined $type;
-    return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o);
-    return;
-}
-
-__DATA__
-
-Title: intro
-Title: introduction
-Text:
-
-    ** THIS IS AN UNSUPPORTED PREVIEW RELEASE **
-    ** PLEASE REPORT BUGS TO rt-bugs@fsck.com **
-
-    This is a command-line interface to RT 3.
-
-    It allows you to interact with an RT server over HTTP, and offers an
-    interface to RT's functionality that is better-suited to automation
-    and integration with other tools.
-
-    In general, each invocation of this program should specify an action
-    to perform on one or more objects, and any other arguments required
-    to complete the desired action.
-
-    For more information:
-
-        - rt help actions       (a list of possible actions)
-        - rt help objects       (how to specify objects)
-        - rt help usage         (syntax information)
-
-        - rt help config        (configuration details)
-        - rt help examples      (a few useful examples)
-        - rt help topics        (a list of help topics)
-
---
-
-Title: usage
-Title: syntax
-Text:
-
-    Syntax:
-
-        rt <action> [options] [arguments]
-
-    Each invocation of this program must specify an action (e.g. "edit",
-    "create"), options to modify behaviour, and other arguments required
-    by the specified action. (For example, most actions expect a list of
-    numeric object IDs to act upon.)
-
-    The details of the syntax and arguments for each action are given by
-    "rt help <action>". Some actions may be referred to by more than one
-    name ("create" is the same as "new", for example).  
-
-    Objects are identified by a type and an ID (which can be a name or a
-    number, depending on the type). For some actions, the object type is
-    implied (you can only comment on tickets); for others, the user must
-    specify it explicitly. See "rt help objects" for details.
-
-    In syntax descriptions, mandatory arguments that must be replaced by
-    appropriate value are enclosed in <>, and optional arguments are
-    indicated by [] (for example, <action> and [options] above).
-
-    For more information:
-
-        - rt help objects       (how to specify objects)
-        - rt help actions       (a list of actions)
-        - rt help types         (a list of object types)
-
---
-
-Title: conf
-Title: config
-Title: configuration
-Text:
-
-    This program has two major sources of configuration information: its
-    configuration files, and the environment.
-
-    The program looks for configuration directives in a file named .rtrc
-    (or $RTCONFIG; see below) in the current directory, and then in more
-    distant ancestors, until it reaches /. If no suitable configuration
-    files are found, it will also check for ~/.rtrc and /etc/rt.conf.
-
-    Configuration directives:
-
-        The following directives may occur, one per line:
-
-        - server <URL>          URL to RT server.
-        - user <username>       RT username.
-        - passwd <passwd>       RT user's password.
-
-        Blank and #-commented lines are ignored.
-
-    Environment variables:
-
-        The following environment variables override any corresponding
-        values defined in configuration files:
-
-        - RTUSER
-        - RTPASSWD
-        - RTSERVER
-        - RTDEBUG       Numeric debug level. (Set to 3 for full logs.)
-        - RTCONFIG      Specifies a name other than ".rtrc" for the
-                        configuration file.
-
---
-
-Title: objects
-Text:
-
-    Syntax:
-
-        <type>/<id>[/<attributes>]
-
-    Every object in RT has a type (e.g. "ticket", "queue") and a numeric
-    ID. Some types of objects can also be identified by name (like users
-    and queues). Furthermore, objects may have named attributes (such as
-    "ticket/1/history").
-
-    An object specification is like a path in a virtual filesystem, with
-    object types as top-level directories, object IDs as subdirectories,
-    and named attributes as further subdirectories.
-
-    A comma-separated list of names, numeric IDs, or numeric ranges can
-    be used to specify more than one object of the same type. Note that
-    the list must be a single argument (i.e., no spaces). For example,
-    "user/root,1-3,5,7-10,ams" is a list of ten users; the same list
-    can also be written as "user/ams,root,1,2,3,5,7,8-20".
-    
-    Examples:
-
-        ticket/1
-        ticket/1/attachments
-        ticket/1/attachments/3
-        ticket/1/attachments/3/content
-        ticket/1-3/links
-        ticket/1-3,5-7/history
-
-        user/ams
-        user/ams/rights
-        user/ams,rai,1/rights
-
-    For more information:
-
-        - rt help <action>      (action-specific details)
-        - rt help <type>        (type-specific details)
-
---
-
-Title: actions
-Title: commands
-Text:
-
-    You can currently perform the following actions on all objects:
-
-        - list          (list objects matching some condition)
-        - show          (display object details)
-        - edit          (edit object details)
-        - create        (create a new object)
-
-    Each type may define actions specific to itself; these are listed in
-    the help item about that type.
-
-    For more information:
-
-        - rt help <action>      (action-specific details)
-        - rt help types         (a list of possible types)
-
---
-
-Title: types
-Text:
-
-    You can currently operate on the following types of objects:
-
-        - tickets
-        - users
-        - groups
-        - queues
-
-    For more information:
-
-        - rt help <type>        (type-specific details)
-        - rt help objects       (how to specify objects)
-        - rt help actions       (a list of possible actions)
-
---
-
-Title: ticket
-Text:
-
-    Tickets are identified by a numeric ID.
-
-    The following generic operations may be performed upon tickets:
-
-        - list
-        - show
-        - edit
-        - create
-
-    In addition, the following ticket-specific actions exist:
-
-        - link
-        - merge
-        - comment
-        - correspond
-
-    Attributes:
-
-        The following attributes can be used with "rt show" or "rt edit"
-        to retrieve or edit other information associated with tickets:
-
-        links                      A ticket's relationships with others.
-        history                    All of a ticket's transactions.
-        history/type/<type>        Only a particular type of transaction.
-        history/id/<id>            Only the transaction of the specified id.
-        attachments                A list of attachments.
-        attachments/<id>           The metadata for an individual attachment.
-        attachments/<id>/content   The content of an individual attachment.
-
---
-
-Title: user
-Title: group
-Text:
-
-    Users and groups are identified by name or numeric ID.
-
-    The following generic operations may be performed upon them:
-
-        - list
-        - show
-        - edit
-        - create
-
-    In addition, the following type-specific actions exist:
-
-        - grant
-        - revoke
-
-    Attributes:
-
-        The following attributes can be used with "rt show" or "rt edit"
-        to retrieve or edit other information associated with users and
-        groups:
-
-        rights                  Global rights granted to this user.
-        rights/<queue>          Queue rights for this user.
-
---
-
-Title: queue
-Text:
-
-    Queues are identified by name or numeric ID.
-
-    Currently, they can be subjected to the following actions:
-
-        - show
-        - edit
-        - create
-
---
-
-Title: logout
-Text:
-
-    Syntax:
-
-        rt logout
-
-    Terminates the currently established login session. You will need to
-    provide authentication credentials before you can continue using the
-    server. (See "rt help config" for details about authentication.)
-
---
-
-Title: ls
-Title: list
-Title: search
-Text:
-
-    Syntax:
-
-        rt <ls|list|search> [options] "query string"
-
-    Displays a list of objects matching the specified conditions.
-    ("ls", "list", and "search" are synonyms.)
-
-    Conditions are expressed in the SQL-like syntax used internally by
-    RT3. (For more information, see "rt help query".) The query string
-    must be supplied as one argument.
-
-    (Right now, the server doesn't support listing anything but tickets.
-    Other types will be supported in future; this client will be able to
-    take advantage of that support without any changes.)
-
-    Options:
-
-        The following options control how much information is displayed
-        about each matching object:
-
-        -i      Numeric IDs only. (Useful for |rt edit -; see examples.)
-        -s      Short description.
-        -l      Longer description.
-
-        In addition,
-        
-        -o +/-<field>   Orders the returned list by the specified field.
-        -S var=val      Submits the specified variable with the request.
-        -t type         Specifies the type of object to look for. (The
-                        default is "ticket".)
-
-    Examples:
-
-        rt ls "Priority > 5 and Status='new'"
-        rt ls -o +Subject "Priority > 5 and Status='new'"
-        rt ls -o -Created "Priority > 5 and Status='new'"
-        rt ls -i "Priority > 5"|rt edit - set status=resolved
-        rt ls -t ticket "Subject like '[PATCH]%'"
-
---
-
-Title: show
-Text:
-
-    Syntax:
-
-        rt show [options] <object-ids>
-
-    Displays details of the specified objects.
-
-    For some types, object information is further classified into named
-    attributes (for example, "1-3/links" is a valid ticket specification
-    that refers to the links for tickets 1-3). Consult "rt help <type>"
-    and "rt help objects" for further details.
-
-    This command writes a set of forms representing the requested object
-    data to STDOUT.
-
-    Options:
-
-        -               Read IDs from STDIN instead of the command-line.
-        -t type         Specifies object type.
-        -f a,b,c        Restrict the display to the specified fields.
-        -S var=val      Submits the specified variable with the request.
-
-    Examples:
-
-        rt show -t ticket -f id,subject,status 1-3
-        rt show ticket/3/attachments/29
-        rt show ticket/3/attachments/29/content
-        rt show ticket/1-3/links
-        rt show -t user 2
-
---
-
-Title: new
-Title: edit
-Title: create
-Text:
-
-    Syntax:
-
-        rt edit [options] <object-ids> set field=value [field=value] ...
-                                       add field=value [field=value] ...
-                                       del field=value [field=value] ...
-
-    Edits information corresponding to the specified objects.
-
-    If, instead of "edit", an action of "new" or "create" is specified,
-    then a new object is created. In this case, no numeric object IDs
-    may be specified, but the syntax and behaviour remain otherwise
-    unchanged.
-
-    This command typically starts an editor to allow you to edit object
-    data in a form for submission. If you specified enough information
-    on the command-line, however, it will make the submission directly.
-
-    The command line may specify field-values in three different ways.
-    "set" sets the named field to the given value, "add" adds a value
-    to a multi-valued field, and "del" deletes the corresponding value.
-    Each "field=value" specification must be given as a single argument.
-
-    For some types, object information is further classified into named
-    attributes (for example, "1-3/links" is a valid ticket specification
-    that refers to the links for tickets 1-3). These attributes may also
-    be edited. Consult "rt help <type>" and "rt help object" for further
-    details.
-
-    Options:
-
-        -       Read numeric IDs from STDIN instead of the command-line.
-                (Useful with rt ls ... | rt edit -; see examples below.)
-        -i      Read a completed form from STDIN before submitting.
-        -o      Dump the completed form to STDOUT instead of submitting.
-        -e      Allows you to edit the form even if the command-line has
-                enough information to make a submission directly.
-        -S var=val
-                Submits the specified variable with the request.
-        -t type Specifies object type.
-
-    Examples:
-
-        # Interactive (starts $EDITOR with a form).
-        rt edit ticket/3
-        rt create -t ticket
-
-        # Non-interactive.
-        rt edit ticket/1-3 add cc=foo@example.com set priority=3
-        rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved
-        rt edit ticket/4 set priority=3 owner=bar@example.com \
-                         add cc=foo@example.com bcc=quux@example.net
-        rt create -t ticket subject='new ticket' priority=10 \
-                            add cc=foo@example.com
-
---
-
-Title: comment
-Title: correspond
-Text:
-
-    Syntax:
-
-        rt <comment|correspond> [options] <ticket-id>
-
-    Adds a comment (or correspondence) to the specified ticket (the only
-    difference being that comments aren't sent to the requestors.)
-
-    This command will typically start an editor and allow you to type a
-    comment into a form. If, however, you specified all the necessary
-    information on the command line, it submits the comment directly.
-
-    (See "rt help forms" for more information about forms.)
-
-    Options:
-
-        -m <text>       Specify comment text.
-        -a <file>       Attach a file to the comment. (May be used more
-                        than once to attach multiple files.)
-        -c <addrs>      A comma-separated list of Cc addresses.
-        -b <addrs>      A comma-separated list of Bcc addresses.
-        -w <time>       Specify the time spent working on this ticket.
-        -e              Starts an editor before the submission, even if
-                        arguments from the command line were sufficient.
-
-    Examples:
-
-        rt comment -t 'Not worth fixing.' -a stddisclaimer.h 23
-
---
-
-Title: merge
-Text:
-
-    Syntax:
-
-        rt merge <from-id> <to-id>
-
-    Merges the two specified tickets.
-
---
-
-Title: link
-Text:
-
-    Syntax:
-
-        rt link [-d] <id-A> <relationship> <id-B>
-
-    Creates (or, with -d, deletes) a link between the specified tickets.
-    The relationship can (irrespective of case) be any of:
-
-        DependsOn/DependedOnBy:     A depends upon B (or vice versa).
-        RefersTo/ReferredToBy:      A refers to B (or vice versa).
-        MemberOf/HasMember:         A is a member of B (or vice versa).
-
-    To view a ticket's relationships, use "rt show ticket/3/links". (See
-    "rt help ticket" and "rt help show".)
-
-    Options:
-
-        -d      Deletes the specified link.
-
-    Examples:
-
-        rt link 2 dependson 3
-        rt link -d 4 referredtoby 6     # 6 no longer refers to 4
-
---
-
-Title: grant
-Title: revoke
-Text:
-
---
-
-Title: query
-Text:
-
-    RT3 uses an SQL-like syntax to specify object selection constraints.
-    See the <RT:...> documentation for details.
-    
-    (XXX: I'm going to have to write it, aren't I?)
-
---
-
-Title: form
-Title: forms
-Text:
-
-    This program uses RFC822 header-style forms to represent object data
-    in a form that's suitable for processing both by humans and scripts.
-
-    A form is a set of (field, value) specifications, with some initial
-    commented text and interspersed blank lines allowed for convenience.
-    Field names may appear more than once in a form; a comma-separated
-    list of multiple field values may also be specified directly.
-    
-    Field values can be wrapped as in RFC822, with leading whitespace.
-    The longest sequence of leading whitespace common to all the lines
-    is removed (preserving further indentation). There is no limit on
-    the length of a value.
-
-    Multiple forms are separated by a line containing only "--\n".
-
-    (XXX: A more detailed specification will be provided soon. For now,
-    the server-side syntax checking will suffice.)
-
---
-
-Title: topics
-Text:
-
-    Use "rt help <topic>" for help on any of the following subjects:
-
-        - tickets, users, groups, queues.
-        - show, edit, ls/list/search, new/create.
-
-        - query                                 (search query syntax)
-        - forms                                 (form specification)
-
-        - objects                               (how to specify objects)
-        - types                                 (a list of object types)
-        - actions/commands                      (a list of actions)
-        - usage/syntax                          (syntax details)
-        - conf/config/configuration             (configuration details)
-        - examples                              (a few useful examples)
-
---
-
-Title: example
-Title: examples
-Text:
-
-    This section will be filled in with useful examples, once it becomes
-    more clear what examples may be useful.
-
-    For the moment, please consult examples provided with each action.
-
---
index 29e443e..bf23a6c 100644 (file)
@@ -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 (file)
index 02b01ab..0000000
+++ /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
-
index cdbc3cb..8fcd631 100644 (file)
@@ -1,9 +1,15 @@
 #!/usr/bin/perl
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
 # 
 # 
-# END LICENSE BLOCK
-
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license 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 Carp;
 
-use lib ("/opt/rt3/lib", "/opt/rt3/local/lib");
+use lib ("/opt/rt3/local/lib", "/opt/rt3/lib");
 
 package RT;
 
@@ -45,9 +68,6 @@ RT::LoadConfig();
 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
 RT::Init();
 
-#Drop setgid permissions
-RT::DropSetGIDPermissions();
-
 #Get the current user all loaded
 my $CurrentUser = GetCurrentUser();
 
@@ -57,18 +77,27 @@ unless ( $CurrentUser->Id ) {
 }
 
 my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
-     $template_id, $help, $verbose );
-GetOptions( "search=s"        => \$search,
-            "search-arg=s"    => \$search_arg,
-            "condition=s"     => \$condition,
-            "condition-arg=s" => \$condition_arg,
-            "action-arg=s"    => \$action_arg,
-            "action=s"        => \$action,
-           "template-id=s"   => \$template_id,
-            "help"            => \$help,
-            "verbose|v"       => \$verbose );
-
-help() if $help;
+     $template_id, $transaction, $transaction_type, $help, $verbose );
+GetOptions( "search=s"           => \$search,
+            "search-arg=s"       => \$search_arg,
+            "condition=s"        => \$condition,
+            "condition-arg=s"    => \$condition_arg,
+            "action-arg=s"       => \$action_arg,
+            "action=s"           => \$action,
+            "template-id=s"      => \$template_id,
+            "transaction=s"      => \$transaction,
+            "transaction-type=s" => \$transaction_type,
+            "help"               => \$help,
+            "verbose|v"          => \$verbose );
+
+help() if $help or not $search or not $action;
+
+$transaction ||= 'first';
+unless ( $transaction =~ /^(first|last)$/i ) {
+    print STDERR loc("--transaction argument could be only 'first' or 'last'");
+    exit 1;
+}
+$transaction = lc($transaction) eq 'first'? 'ASC': 'DESC';
 
 # We _must_ have a search object
 load_module($search);
@@ -78,15 +107,21 @@ load_module($condition) if ($condition);
 # load template if specified
 my $template_obj;
 if ($template_id) {
-    $template_obj = RT::Template->new($RT::Nobody);
-    $template_obj->LoadById($template_id);
+    $template_obj = RT::Template->new($CurrentUser);
+    $template_obj->Load($template_id);
 }
+my $void_scrip = RT::Scrip->new( $CurrentUser );
+my $void_scrip_action = RT::ScripAction->new( $CurrentUser );
 
 #At the appointed time:
 
 #find a bunch of tickets
 my $tickets = RT::Tickets->new($CurrentUser);
-my $search  = $search->new( TicketsObj => $tickets, Argument => $search_arg );
+my $search  = $search->new(
+    TicketsObj  => $tickets,
+    Argument    => $search_arg,
+    CurrentUser => $CurrentUser
+);
 
 $search->Prepare();
 
@@ -95,12 +130,22 @@ my $tickets = $search->TicketsObj;
 
 #for each ticket we've found
 while ( my $ticket = $tickets->Next() ) {
-    print "\n" . $ticket->Id() . ": " if ($verbose);
+    print $ticket->Id() . ": " if ($verbose);
+
+    my $transaction = get_transaction($ticket);
+    print loc("Using transaction #[_1]...", $transaction->id)
+        if $verbose && $transaction;
 
     # perform some more advanced check
     if ($condition) {
-        my $condition_obj = $condition->new( TicketObj => $ticket,
-                                             Argument  => $condition_arg );
+        my $condition_obj = $condition->new(
+            TransactionObj => $transaction,
+            TicketObj      => $ticket,
+            ScripObj       => $void_scrip,
+            TemplateObj    => $template_obj,
+            Argument       => $condition_arg,
+            CurrentUser    => $CurrentUser,
+        );
 
         # if the condition doesn't apply, get out of here
 
@@ -109,9 +154,15 @@ while ( my $ticket = $tickets->Next() ) {
     }
 
     #prepare our action
-    my $action_obj = $action->new( TicketObj => $ticket,
-                                  TemplateObj => $template_obj,
-                                   Argument  => $action_arg );
+    my $action_obj = $action->new(
+        TicketObj      => $ticket,
+        TransactionObj => $transaction,
+        TemplateObj    => $template_obj,
+        Argument       => $action_arg,
+        ScripObj       => $void_scrip,
+        ScripActionObj => $void_scrip_action,
+        CurrentUser    => $CurrentUser,
+    );
 
     #if our preparation, move onto the next ticket
     next unless ( $action_obj->Prepare );
@@ -119,7 +170,27 @@ while ( my $ticket = $tickets->Next() ) {
 
     #commit our action.
     next unless ( $action_obj->Commit );
-    print loc("Action committed.") if ($verbose);
+    print loc("Action committed.\n") if ($verbose);
+}
+
+=head2 get_transaction
+
+Takes ticket and returns its transaction acording to command
+line arguments C<--transaction> and <--transaction-type>.
+
+=cut
+
+sub get_transaction {
+    my $ticket = shift;
+    my $txns = $ticket->Transactions;
+    $txns->OrderByCols(
+        { FIELD => 'Created', ORDER => $transaction },
+        { FIELD => 'id', ORDER => $transaction },
+    );
+    $txns->Limit( FIELD => 'Type', VALUE => $transaction_type )
+        if $transaction_type;
+    $txns->RowsPerPage(1);
+    return $txns->First;
 }
 
 # {{{ load_module 
@@ -181,6 +252,15 @@ sub help {
       . loc( "[_1] - An argument to pass to [_2]", "--action-argument", "--action" )
       . "\n";
     print "    "
+      . loc( "[_1] - Specify id of the template you want to use", "--template-id" )
+      . "\n";
+    print "    "
+      . loc( "[_1] - Specify if you want to use either 'first' or 'last' transaction", "--transaction" )
+      . "\n";
+    print "    "
+      . loc( "[_1] - Specify the type of a transaction you want to use", "--transaction-type" )
+      . "\n";
+    print "    "
       . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
     print "\n";
     print "\n";
@@ -197,19 +277,17 @@ sub help {
       )
       . "\n\n";
 
-    print " bin/rt-cron-tool \\\n";
-    print
-      "  --search RT::Search::ActiveTicketsInQueue  --search-arg general \\\n";
-    print
-      "  --condition RT::Condition::UntouchedInHours --condition-arg 4 \\\n";
+    print " bin/rt-crontool \\\n";
+    print "  --search RT::Search::ActiveTicketsInQueue  --search-arg general \\\n";
+    print "  --condition RT::Condition::UntouchedInHours --condition-arg 4 \\\n";
     print "  --action RT::Action::SetPriority --action-arg 99 \\\n";
     print "  --verbose\n";
 
     print "\n";
-    print loc("Escalate tickets");
-    print "rt-crontool \\\n";
-    print " --search RT::Search::ActiveTicketsInQueue  --search-arg thequeuename \\\n";
-    print " --action RT::Action::EscalatePriority \\\n";
+    print loc("Escalate tickets"). "\n";
+    print " bin/rt-crontool \\\n";
+    print "  --search RT::Search::ActiveTicketsInQueue  --search-arg general \\\n";
+    print "  --action RT::Action::EscalatePriority\n";
  
  
  
index 8af8002..8db26db 100755 (executable)
@@ -1,9 +1,15 @@
 #!/usr/bin/perl -w
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
 # 
 # 
-# END LICENSE BLOCK
-
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license 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.
 
-=begin testing
-
-use RT::I18N;
-
-# Make sure that when we call the mailgate wrong, it tempfails
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://bad.address"), "Opened the mailgate - The error below is expected - $@");
-print MAIL <<EOF;
-From: root\@localhost
-To: rt\@example.com
-Subject: This is a test of new ticket creation
-
-Foob!
-EOF
-close (MAIL);
-
-# Check the return value
-is ( $? >> 8, 75, "The error message above is expected The mail gateway exited with a failure. yay");
-
-
-# {{{ Test new ticket creation by root who is privileged and superuser
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
-print MAIL <<EOF;
-From: root\@localhost
-To: rt\@example.com
-Subject: This is a test of new ticket creation
-
-Blah!
-Foob!
-EOF
-close (MAIL);
-
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-use RT::Tickets;
-my $tickets = RT::Tickets->new($RT::SystemUser);
-$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
-$tickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
-my $tick = $tickets->First();
-ok (UNIVERSAL::isa($tick,'RT::Ticket'));
-ok ($tick->Id, "found ticket ".$tick->Id);
-ok ($tick->Subject eq 'This is a test of new ticket creation', "Created the ticket");
-
-# }}}
-
-
-# {{{This is a test of new ticket creation as an unknown user
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
-print MAIL <<EOF;
-From: doesnotexist\@example.com
-To: rt\@example.com
-Subject: This is a test of new ticket creation as an unknown user
-
-Blah!
-Foob!
-EOF
-close (MAIL);
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-$tickets = RT::Tickets->new($RT::SystemUser);
-$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
-$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
-$tick = $tickets->First();
-ok ($tick->Id, "found ticket ".$tick->Id);
-ok ($tick->Subject ne 'This is a test of new ticket creation as an unknown user', "failed to create the new ticket from an unprivileged account");
-my $u = RT::User->new($RT::SystemUser);
-$u->Load('doesnotexist@example.com');
-ok( $u->Id == 0, " user does not exist and was not created by failed ticket submission");
-
-
-# }}}
-
-# {{{ now everybody can create tickets.  can a random unkown user create tickets?
-
-my $g = RT::Group->new($RT::SystemUser);
-$g->LoadSystemInternalGroup('Everyone');
-ok( $g->Id, "Found 'everybody'");
-
-my ($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
-ok ($val, "Granted everybody the right to create tickets - $msg");
-
-sleep(60); # gotta sleep so the remote process' ACL cache times out
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
-print MAIL <<EOF;
-From: doesnotexist\@example.com
-To: rt\@example.com
-Subject: This is a test of new ticket creation as an unknown user
-
-Blah!
-Foob!
-EOF
-close (MAIL);
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-
-$tickets = RT::Tickets->new($RT::SystemUser);
-$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
-$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
-$tick = $tickets->First();
-ok ($tick->Id, "found ticket ".$tick->Id);
-ok ($tick->Subject eq 'This is a test of new ticket creation as an unknown user', "failed to create the new ticket from an unprivileged account");
-my $u = RT::User->new($RT::SystemUser);
-$u->Load('doesnotexist@example.com');
-ok( $u->Id != 0, " user does not exist and was created by ticket submission");
-
-# }}}
-
-
-# {{{  can another random reply to a ticket without being granted privs? answer should be no.
-
-
-#($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
-#ok ($val, "Granted everybody the right to create tickets - $msg");
-#sleep(60); # gotta sleep so the remote process' ACL cache times out
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
-print MAIL <<EOF;
-From: doesnotexist-2\@example.com
-To: rt\@example.com
-Subject: [example.com #@{[$tick->Id]}] This is a test of a reply as an unknown user
-
-Blah!
-Foob!
-EOF
-close (MAIL);
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-$u = RT::User->new($RT::SystemUser);
-$u->Load('doesnotexist-2@example.com');
-ok( $u->Id == 0, " user does not exist and was not created by ticket correspondence submission");
-# }}}
-# {{{  can another random reply to a ticket after being granted privs? answer should be yes
-
-
-($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'ReplyToTicket');
-ok ($val, "Granted everybody the right to reply to  tickets - $msg");
-sleep(60); # gotta sleep so the remote process' ACL cache times out
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
-print MAIL <<EOF;
-From: doesnotexist-2\@example.com
-To: rt\@example.com
-Subject: [example.com #@{[$tick->Id]}] This is a test of a reply as an unknown user
-
-Blah!
-Foob!
-EOF
-close (MAIL);
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-
-$u = RT::User->new($RT::SystemUser);
-$u->Load('doesnotexist-2@example.com');
-ok( $u->Id != 0, " user exists and was created by ticket correspondence submission");
-
-# }}}
-
-# {{{  can another random comment on a ticket without being granted privs? answer should be no.
-
-
-#($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CreateTicket');
-#ok ($val, "Granted everybody the right to create tickets - $msg");
-#sleep(60); # gotta sleep so the remote process' ACL cache times out
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action comment"), "Opened the mailgate - $@");
-print MAIL <<EOF;
-From: doesnotexist-3\@example.com
-To: rt\@example.com
-Subject: [example.com #@{[$tick->Id]}] This is a test of a comment as an unknown user
-
-Blah!
-Foob!
-EOF
-close (MAIL);
-
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-$u = RT::User->new($RT::SystemUser);
-$u->Load('doesnotexist-3@example.com');
-ok( $u->Id == 0, " user does not exist and was not created by ticket comment submission");
-
-# }}}
-# {{{  can another random reply to a ticket after being granted privs? answer should be yes
-
-
-($val,$msg) = $g->PrincipalObj->GrantRight(Right => 'CommentOnTicket');
-ok ($val, "Granted everybody the right to reply to  tickets - $msg");
-sleep(60); # gotta sleep so the remote process' ACL cache times out
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action comment"), "Opened the mailgate - $@");
-print MAIL <<EOF;
-From: doesnotexist-3\@example.com
-To: rt\@example.com
-Subject: [example.com #@{[$tick->Id]}] This is a test of a comment as an unknown user
-
-Blah!
-Foob!
-EOF
-close (MAIL);
-
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-$u = RT::User->new($RT::SystemUser);
-$u->Load('doesnotexist-3@example.com');
-ok( $u->Id != 0, " user exists and was created by ticket comment submission");
-
-# }}}
-
-# {{{ Testing preservation of binary attachments
-
-# Get a binary blob (Best Practical logo) 
-
-# Create a mime entity with an attachment
-
-use MIME::Entity;
-my $entity = MIME::Entity->build( From => 'root@localhost',
-                                 To => 'rt@localhost',
-                                Subject => 'binary attachment test',
-                                Data => ['This is a test of a binary attachment']);
-
-# currently in lib/t/autogen
-$entity->attach(Path => '/opt/rt3/share/html/NoAuth/images/spacer.gif', 
-                Type => 'image/gif',
-                Encoding => 'base64');
-
-# Create a ticket with a binary attachment
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
-
-$entity->print(\*MAIL);
-
-close (MAIL);
-
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-my $tickets = RT::Tickets->new($RT::SystemUser);
-$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
-$tickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
- $tick = $tickets->First();
-ok (UNIVERSAL::isa($tick,'RT::Ticket'));
-ok ($tick->Id, "found ticket ".$tick->Id);
-ok ($tick->Subject eq 'binary attachment test', "Created the ticket - ".$tick->Id);
-
-my $file = `cat ../../../html/NoAuth/images/spacer.gif`;
-ok ($file, "Read in the logo image");
-
-
-        use Digest::MD5;
-warn "for the raw file the content is ".Digest::MD5::md5_base64($file);
-
-
-
-# Verify that the binary attachment is valid in the database
-my $attachments = RT::Attachments->new($RT::SystemUser);
-$attachments->Limit(FIELD => 'ContentType', VALUE => 'image/gif');
-ok ($attachments->Count == 1, 'Found only one gif in the database');
-my $attachment = $attachments->First;
-my $acontent = $attachment->Content;
-
-        warn "coming from the  database, the content is ".Digest::MD5::md5_base64($acontent);
-
-is( $acontent, $file, 'The attachment isn\'t screwed up in the database.');
-# Log in as root
-use Getopt::Long;
-use LWP::UserAgent;
-
-
-# Grab the binary attachment via the web ui
-my $ua      = LWP::UserAgent->new();
-
-my $full_url = "http://localhost".$RT::WebPath."/Ticket/Attachment/".$attachment->TransactionId."/".$attachment->id."/spacer.gif?&user=root&pass=password";
-my $r = $ua->get( $full_url);
-
-
-# Verify that the downloaded attachment is the same as what we uploaded.
-is($file, $r->content, 'The attachment isn\'t screwed up in download');
-
-
-
-# }}}
-
-# {{{ Simple I18N testing
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
-                                                                         
-print MAIL <<EOF;
-From: root\@localhost
-To: rtemail\@example.com
-Subject: This is a test of I18N ticket creation
-Content-Type: text/plain; charset="utf-8"
-
-2 accented lines
-\303\242\303\252\303\256\303\264\303\273
-\303\241\303\251\303\255\303\263\303\272
-bye
-EOF
-close (MAIL);
-
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-my $unitickets = RT::Tickets->new($RT::SystemUser);
-$unitickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
-$unitickets->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
-my $unitick = $unitickets->First();
-ok (UNIVERSAL::isa($unitick,'RT::Ticket'));
-ok ($unitick->Id, "found ticket ".$unitick->Id);
-ok ($unitick->Subject eq 'This is a test of I18N ticket creation', "Created the ticket - ". $unitick->Subject);
-
-
-
-my $unistring = "\303\241\303\251\303\255\303\263\303\272";
-Encode::_utf8_on($unistring);
-is ($unitick->Transactions->First->Content, $unitick->Transactions->First->Attachments->First->Content, "Content is ". $unitick->Transactions->First->Attachments->First->Content);
-ok($unitick->Transactions->First->Attachments->First->Content =~ /$unistring/i, $unitick->Id." appears to be unicode ". $unitick->Transactions->First->Attachments->First->Id);
-# supposedly I18N fails on the second message sent in.
-
-ok(open(MAIL, "|/opt/rt3/bin/rt-mailgate --url http://localhost".$RT::WebPath."/ --queue general --action correspond"), "Opened the mailgate - $@");
-                                                                         
-print MAIL <<EOF;
-From: root\@localhost
-To: rtemail\@example.com
-Subject: This is a test of I18N ticket creation
-Content-Type: text/plain; charset="utf-8"
-
-2 accented lines
-\303\242\303\252\303\256\303\264\303\273
-\303\241\303\251\303\255\303\263\303\272
-bye
-EOF
-close (MAIL);
-
-#Check the return value
-is ($? >> 8, 0, "The mail gateway exited normally. yay");
-
-my $tickets2 = RT::Tickets->new($RT::SystemUser);
-$tickets2->OrderBy(FIELD => 'id', ORDER => 'DESC');
-$tickets2->Limit(FIELD => 'id', OPERATOR => '>', VALUE => '0');
-my $tick2 = $tickets2->First();
-ok (UNIVERSAL::isa($tick2,'RT::Ticket'));
-ok ($tick2->Id, "found ticket ".$tick2->Id);
-ok ($tick2->Subject eq 'This is a test of I18N ticket creation', "Created the ticket");
-
-
-
-my $unistring = "\303\241\303\251\303\255\303\263\303\272";
-Encode::_utf8_on($unistring);
-
-ok ($tick2->Transactions->First->Content =~ $unistring, "It appears to be unicode - ".$tick2->Transactions->First->Content);
-
-# }}}
-
-
-($val,$msg) = $g->PrincipalObj->RevokeRight(Right => 'CreateTicket');
-ok ($val, $msg);
-
-
-
-=end testing
-
 =cut
 
 
 use strict;
+use warnings;
 use Getopt::Long;
 use LWP::UserAgent;
 
@@ -420,19 +74,23 @@ for (qw(url)) {
     die "$0 invoked improperly\n\nNo $_ provided to mail gateway!\n" unless $opts{$_};
 }
 
-undef $/;
 my $ua      = LWP::UserAgent->new();
 $ua->cookie_jar( { file => $opts{jar} } );
 
 my %args = (
-    queue   => $opts{queue},
-    action  => $opts{action},
-    SessionType => 'REST',    # Surpress login box
+    SessionType => 'REST', # Surpress login box
 );
+foreach ( qw(queue action) ) {
+    $args{$_} = $opts{$_} if defined $opts{$_};
+};
 
 # Read the message in from STDIN
-$args{'message'} = <>;
+$args{'message'} = do { local (@ARGV, $/); <> };
 
+unless ( $args{message} =~ /\S/ ) {
+    print STDERR "$0: no message passed on STDIN!\n";
+    exit 0;
+}
 
 if ($opts{'extension'}) {
         $args{$opts{'extension'}} = $ENV{'EXTENSION'};
@@ -500,7 +158,7 @@ sub check_failure {
 
 Usual invocation (from MTA):
 
-    rt-mailgate --action (correspond|comment) --queue queuename
+    rt-mailgate --action (correspond|comment|...) --queue queuename
                 --url http://your.rt.server/
                 [ --debug ]
                 [ --extension (queue|action|ticket) ]
@@ -516,15 +174,31 @@ See C<man rt-mailgate> for more.
 
 =item C<--action>
 
-Specifies whether this is a correspondence or comment address.
+Specifies what happens to email sent to this alias.  The avaliable
+basic actions are: C<correspond>, C<comment>.
+
+
+If you've set the RT configuration variable B<$RT::UnsafeEmailCommands>,
+C<take> and C<resolve> are also available.  You can execute two or more
+actions on a single message using a C<-> separated list.  RT will execute
+the actions in the listed order.  For example you can use C<take-comment>,
+C<correspond-resolve> or C<take-comment-resolve> as actions.
+
+Note that C<take> and C<resolve> actions ignore message text if used
+alone.  Include a  C<comment> or C<correspond> action if you want RT
+to record the incoming message.
+
+The default action is C<correspond>.
 
 =item C<--queue>
 
-Reflects which queue this address handles.
+This flag determines which queue this alias should create a ticket in if no ticket identifier
+is found.
 
 =item C<--url>
 
-The location of the web server for your RT instance.
+This flag tells the mail gateway where it can find your RT server. You should 
+probably use the same URL that users use to log into RT.
 
 
 =item C<--extension> OPTIONAL
@@ -615,6 +289,7 @@ several parameters:
 =item Message
 
 A C<MIME::Entity> object representing the email
+
 =item CurrentUser
 
 An C<RT::CurrentUser> object
diff --git a/rt/bin/webmux.pl b/rt/bin/webmux.pl
deleted file mode 100755 (executable)
index 96e7ebf..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/usr/bin/perl
-# BEGIN LICENSE BLOCK
-# 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-# 
-# (Except where explictly superceded by other copyright notices)
-# 
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-# 
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
-# 
-# 
-# END LICENSE BLOCK
-
-use strict;
-
-BEGIN {
-    $ENV{'PATH'}   = '/bin:/usr/bin';                      # or whatever you need
-    $ENV{'CDPATH'} = '' if defined $ENV{'CDPATH'};
-    $ENV{'SHELL'}  = '/bin/sh' if defined $ENV{'SHELL'};
-    $ENV{'ENV'}    = '' if defined $ENV{'ENV'};
-    $ENV{'IFS'}    = '' if defined $ENV{'IFS'};
-    
-}
-
-use lib ("/opt/rt3/local/lib", "/opt/rt3/lib");
-use RT;
-
-package RT::Mason;
-
-use CGI qw(-private_tempfiles);    #bring this in before mason, to make sure we
-                                   #set private_tempfiles
-
-BEGIN {
-    if ($mod_perl::VERSION >= 1.9908) {
-       require Apache::RequestUtil;
-       no warnings 'redefine';
-       my $sub = *Apache::request{CODE};
-       *Apache::request = sub {
-           my $r;
-           eval { $r = $sub->('Apache'); };
-           # warn $@ if $@;
-           return $r;
-       };
-    }
-    if ($CGI::MOD_PERL) {
-       require HTML::Mason::ApacheHandler;
-    }
-    else {
-       require HTML::Mason::CGIHandler;
-    }
-}
-
-use HTML::Mason;                   # brings in subpackages: Parser, Interp, etc.
-
-use vars qw($Nobody $SystemUser $r);
-
-#This drags in RT's config.pm
-RT::LoadConfig();
-
-use Carp;
-
-{
-    package HTML::Mason::Commands;
-    use vars qw(%session);
-
-    use RT::Tickets;
-    use RT::Transactions;
-    use RT::Users;
-    use RT::CurrentUser;
-    use RT::Templates;
-    use RT::Queues;
-    use RT::ScripActions;
-    use RT::ScripConditions;
-    use RT::Scrips;
-    use RT::Groups;
-    use RT::GroupMembers;
-    use RT::CustomFields;
-    use RT::CustomFieldValues;
-    use RT::TicketCustomFieldValues;
-
-    use RT::Interface::Web;
-    use MIME::Entity;
-    use Text::Wrapper;
-    use CGI::Cookie;
-    use Time::ParseDate;
-    use HTML::Entities;
-}
-
-
-# Activate the following if running httpd as root (the normal case).
-# Resets ownership of all files created by Mason at startup.
-# Note that mysql uses DB for sessions, so there's no need to do this.
-unless ($RT::DatabaseType =~ /(mysql|Pg)/) {
-    # Clean up our umask to protect session files
-    umask(0077);
-
-if ( $CGI::MOD_PERL)  {
-    chown( Apache->server->uid, Apache->server->gid, [$RT::MasonSessionDir] )
-       if Apache->server->can('uid');
-        }
-    # Die if WebSessionDir doesn't exist or we can't write to it
-    stat($RT::MasonSessionDir);
-    die "Can't read and write $RT::MasonSessionDir"
-       unless ( ( -d _ ) and ( -r _ ) and ( -w _ ) );
-}
-
-my $ah = &RT::Interface::Web::NewApacheHandler(@RT::MasonParameters) if $CGI::MOD_PERL;
-
-sub handler {
-    ($r) = @_;
-
-    local $SIG{__WARN__};
-    local $SIG{__DIE__};
-
-    RT::Init();
-
-    # We don't need to handle non-text items
-    return -1 if defined( $r->content_type ) && $r->content_type !~ m|^text/|io;
-
-    my %session;
-    my $status;
-    eval { $status = $ah->handle_request($r) };
-    if ($@) {
-       $RT::Logger->crit($@);
-    }
-
-    undef (%session);
-
-    if ($RT::Handle->TransactionDepth) {
-       $RT::Handle->ForceRollback;
-       $RT::Logger->crit("Transaction not committed. Usually indicates a software fault. Data loss may have occurred") ;
-    }
-    return $status;
-}
-
-1;
diff --git a/rt/config b/rt/config
deleted file mode 100644 (file)
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 (file)
index 1550111..0000000
+++ /dev/null
@@ -1,152 +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
-##     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 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/"?
-  htmldir:             ${datadir}/html
-  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
-  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>
-
-#   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
-  manualdir:           ${datadir}/doc
-  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>
diff --git a/rt/config.layout.in b/rt/config.layout.in
new file mode 100644 (file)
index 0000000..a08f489
--- /dev/null
@@ -0,0 +1,127 @@
+##
+##  config.layout -- Pre-defined Installation Path Layouts
+##
+##  Hints:
+##  - layouts can be loaded with configure's --enable-layout=ID option
+##  - when no --enable-layout option is given, the default layout is `RT'
+##  - a trailing plus character (`+') on paths is replaced with a
+##    `/<target>' suffix where <target> is currently hardcoded to 'rt3'.
+##    (This may become a configurable parameter at some point.)
+##
+##  The following variables must _all_ be set:
+##     prefix exec_prefix bindir sbindir sysconfdir mandir libdir
+##     datadir htmldir localstatedir logfiledir masonstatedir
+##     sessionstatedir customdir customhtmldir customlexdir
+##  (This can be seen in m4/rt_layout.m4.)
+##
+
+#   Default RT3 path layout.
+<Layout RT3>
+  prefix:              /opt/rt3
+  exec_prefix:         ${prefix}
+  bindir:              ${exec_prefix}/bin
+  sbindir:             ${exec_prefix}/sbin
+  sysconfdir:          ${prefix}/etc
+  mandir:              ${prefix}/man
+  libdir:              ${prefix}/lib
+  datadir:             ${prefix}/share
+  htmldir:             ${datadir}/html
+  manualdir:           ${datadir}/doc
+  localstatedir:       ${prefix}/var
+  logfiledir:          ${localstatedir}/log
+  masonstatedir:       ${localstatedir}/mason_data
+  sessionstatedir:     ${localstatedir}/session_data
+  customdir:           ${prefix}/local
+  custometcdir:                ${customdir}/etc
+  customhtmldir:       ${customdir}/html
+  customlexdir:                ${customdir}/po
+  customlibdir:                ${customdir}/lib
+</Layout>
+<Layout inplace>
+  prefix:              `pwd`
+  exec_prefix:         ${prefix}
+  bindir:              ${exec_prefix}/bin
+  sbindir:             ${exec_prefix}/sbin
+  sysconfdir:          ${prefix}/etc
+  mandir:              ${prefix}/man
+  libdir:              ${prefix}/lib
+  datadir:             ${prefix}/share
+  htmldir:             ${prefix}/html
+  manualdir:           ${datadir}/doc
+  localstatedir:       ${prefix}/var
+  logfiledir:          ${localstatedir}/log
+  masonstatedir:       ${localstatedir}/mason_data
+  sessionstatedir:     ${localstatedir}/session_data
+  customdir:           ${prefix}/local
+  custometcdir:                ${customdir}/etc
+  customhtmldir:       ${customdir}/html
+  customlexdir:                ${customdir}/po
+  customlibdir:                ${customdir}/lib
+</Layout>
+
+<Layout FreeBSD>
+  prefix:              /usr/local
+  exec_prefix:         ${prefix}
+  bindir:              ${exec_prefix}/bin
+  sbindir:             ${exec_prefix}/sbin
+  sysconfdir:          ${prefix}/etc+
+  mandir:              ${prefix}/man
+  libdir:              ${prefix}/lib+
+  datadir:             ${prefix}/share+
+  htmldir:             ${datadir}/html
+  manualdir:           ${prefix}/share/doc+
+  logfiledir:          /var/log
+  localstatedir:       /var/run+
+  masonstatedir:       ${localstatedir}/mason_data
+  sessionstatedir:     ${localstatedir}/session_data
+  customdir:           ${prefix}/share+
+  custometcdir:                ${customdir}/local/etc
+  customhtmldir:       ${customdir}/local/html
+  customlexdir:                ${customdir}/local/po
+  customlibdir:                ${customdir}/local/lib
+</Layout>
+
+<Layout Win32>
+  prefix:              C:/Program Files/Request Tracker
+  exec_prefix:         ${prefix}
+  bindir:              ${exec_prefix}/bin
+  sbindir:             ${exec_prefix}/sbin
+  sysconfdir:          ${prefix}/etc
+  mandir:              ${prefix}/man
+  libdir:              ${prefix}/lib
+  datadir:             ${prefix}
+  htmldir:             ${datadir}/html
+  manualdir:           ${datadir}/doc
+  localstatedir:       ${prefix}/var
+  logfiledir:          ${localstatedir}/log
+  masonstatedir:       ${localstatedir}/mason_data
+  sessionstatedir:     ${localstatedir}/session_data
+  customdir:           ${prefix}/local
+  custometcdir:                ${customdir}/etc
+  customhtmldir:       ${customdir}/html
+  customlexdir:                ${customdir}/po
+  customlibdir:                ${customdir}/lib
+</Layout>
+
+<Layout Freeside>
+  prefix:              /opt/rt3
+  exec_prefix:         ${prefix}
+  bindir:              ${exec_prefix}/bin
+  sbindir:             ${exec_prefix}/sbin
+  sysconfdir:          ${prefix}/etc
+  mandir:              ${prefix}/man
+  libdir:              ${prefix}/lib
+  datadir:             ${prefix}/share
+  htmldir:             %%%FREESIDE_DOCUMENT_ROOT%%%/rt
+  manualdir:           ${datadir}/doc
+  localstatedir:       ${prefix}/var
+  logfiledir:          ${localstatedir}/log
+  masonstatedir:       %%%MASONDATA%%%
+  sessionstatedir:     ${localstatedir}/session_data
+  customdir:           ${prefix}/local
+  custometcdir:                ${customdir}/etc
+  customhtmldir:       ${customdir}/html
+  customlexdir:                ${customdir}/po
+  customlibdir:                ${customdir}/lib
+</Layout>
+
index 24e15e3..ab4b65c 100644 (file)
@@ -1,25 +1,25 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by RT configure 3.0.9, which was
-generated by GNU Autoconf 2.53.  Invocation command line was
+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 = pallas
-uname -m = i686
-uname -r = 2.4.18-686
+hostname = rootwood
+uname -m = x86_64
+uname -r = 2.6.21-1-amd64
 uname -s = Linux
-uname -v = #1 Sun Apr 14 11:32:47 EST 2002
+uname -v = #1 SMP Sat May 26 17:22:54 CEST 2007
 
 /usr/bin/uname -p = unknown
 /bin/uname -X     = unknown
 
-/bin/arch              = i686
+/bin/arch              = unknown
 /usr/bin/arch -k       = unknown
 /usr/convex/getsysinfo = unknown
 hostinfo               = unknown
@@ -27,41 +27,36 @@ hostinfo               = unknown
 /usr/bin/oslevel       = unknown
 /bin/universe          = unknown
 
-PATH: /usr/X11R6/bin/
-PATH: /opt/rt/bin
-PATH: /usr/athena/bin
+PATH: /usr/local/sbin
 PATH: /usr/local/bin
-PATH: /bin
-PATH: /usr/bin
 PATH: /usr/sbin
 PATH: /usr/bin
-PATH: /usr/games
-PATH: $HOME/bin
-PATH: /opt/kerberos/bin
-PATH: /opt/StarOffice-4.0/bin
-PATH: /opt/mysql/bin/
-PATH: .
+PATH: /sbin
+PATH: /bin
 
 
 ## ----------- ##
 ## Core tests. ##
 ## ----------- ##
 
-configure:1218: checking for a BSD-compatible install
-configure:1272: result: /usr/bin/install -c
-configure:1286: checking for perl
-configure:1304: found /usr/bin/perl
-configure:1317: result: /usr/bin/perl
-configure:1639: checking for chosen layout
-configure:1654: result: RT3
-configure:1986: creating ./config.status
+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.0.9, which was
-generated by GNU Autoconf 2.53.  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  = 
@@ -69,26 +64,22 @@ generated by GNU Autoconf 2.53.  Invocation command line was
   CONFIG_COMMANDS = 
   $ ./config.status 
 
-on pallas
-
-config.status:639: creating sbin/rt-setup-database
-config.status:639: creating sbin/rt-test-dependencies
-config.status:639: creating Makefile
-config.status:639: creating etc/RT_Config.pm
-config.status:639: creating lib/RT.pm
-config.status:639: creating lib/t/00smoke.t
-config.status:639: creating lib/t/01harness.t
-config.status:639: creating lib/t/02regression.t
-config.status:639: creating lib/t/03web.pl
-config.status:639: creating lib/t/04_send_email.pl
-config.status:639: creating bin/mason_handler.fcgi
-config.status:639: creating bin/mason_handler.scgi
-config.status:639: creating bin/mason_handler.svc
-config.status:639: creating bin/rt-commit-handler
-config.status:639: creating bin/rt-crontool
-config.status:639: creating bin/rt-mailgate
-config.status:639: creating bin/rt
-config.status:639: creating bin/webmux.pl
+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. ##
@@ -104,15 +95,132 @@ ac_cv_env_target_alias_set=
 ac_cv_env_target_alias_value=
 ac_cv_path_PERL=/usr/bin/perl
 ac_cv_path_install='/usr/bin/install -c'
+ac_cv_prog_AWK=gawk
+
+## ----------------- ##
+## Output variables. ##
+## ----------------- ##
+
+APACHECTL='/usr/sbin/apachectl'
+AWK='gawk'
+BIN_OWNER='root'
+CONFIG_FILE_PATH='/opt/rt3/etc'
+DATABASE_ENV_PREF=''
+DB_DATABASE='freeside'
+DB_DBA='freeside'
+DB_HOST='localhost'
+DB_PORT=''
+DB_RT_HOST='localhost'
+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=''
+INSTALL_DATA='${INSTALL} -m 644'
+INSTALL_PROGRAM='${INSTALL}'
+INSTALL_SCRIPT='${INSTALL}'
+LIBOBJS=''
+LIBS=''
+LIBS_GROUP='bin'
+LIBS_OWNER='root'
+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='/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.6.4'
+PACKAGE_TARNAME='rt'
+PACKAGE_VERSION='3.6.4'
+PATH_SEPARATOR=':'
+PERL='/usr/bin/perl'
+RTGROUP='freeside'
+RT_BIN_PATH='/opt/rt3/bin'
+RT_DEVEL_MODE='0'
+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_SBIN_PATH='/opt/rt3/sbin'
+RT_STANDALONE='0'
+RT_VAR_PATH='/opt/rt3/var'
+RT_VERSION_MAJOR='3'
+RT_VERSION_MINOR='6'
+RT_VERSION_PATCH='4'
+SHELL='/bin/sh'
+SPEEDY_BIN='/usr/local/bin/speedy'
+WEB_GROUP='freeside'
+WEB_USER='freeside'
+bindir='/opt/rt3/bin'
+build_alias=''
+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='/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_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='/opt/rt3/sbin'
+exp_sessionstatedir='/opt/rt3/var/session_data'
+exp_sysconfdir='/opt/rt3/etc'
+host_alias=''
+htmldir='/var/www/freeside/rt'
+includedir='${prefix}/include'
+infodir='${prefix}/info'
+libdir='/opt/rt3/lib'
+libexecdir='${exec_prefix}/libexec'
+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'
+prefix='/opt/rt3'
+program_transform_name='s,x,x,'
+rt_layout_name='Freeside'
+rt_version_major='3'
+rt_version_minor='6'
+rt_version_patch='4'
+sbindir='/opt/rt3/sbin'
+sessionstatedir='/opt/rt3/var/session_data'
+sharedstatedir='${prefix}/com'
+sysconfdir='/opt/rt3/etc'
+target_alias=''
 
 ## ----------- ##
 ## 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.0.9"
-#define PACKAGE_STRING "RT 3.0.9"
-#define PACKAGE_BUGREPORT "rt-3.0-bugs@fsck.com"
+#define PACKAGE_VERSION "3.6.4"
 
 configure: exit 0
diff --git a/rt/config.pld b/rt/config.pld
deleted file mode 100644 (file)
index c71c7bb..0000000
+++ /dev/null
@@ -1,19 +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=${exec_prefix}/bin
-sbindir=${exec_prefix}/sbin
-sysconfdir=${prefix}/etc
-mandir=${prefix}/man
-libdir=${prefix}/lib
-datadir=${prefix}/share
-(test "x$htmldir" = "xNONE" || test "x$htmldir" = "x") && htmldir=${datadir}/html
-(test "x$manualdir" = "xNONE" || test "x$manualdir" = "x") && manualdir=${datadir}/doc
-localstatedir=${prefix}/var
-(test "x$logfiledir" = "xNONE" || test "x$logfiledir" = "x") && logfiledir=${localstatedir}/log
-(test "x$masonstatedir" = "xNONE" || test "x$masonstatedir" = "x") && masonstatedir=${localstatedir}/mason_data
-(test "x$sessionstatedir" = "xNONE" || test "x$sessionstatedir" = "x") && sessionstatedir=${localstatedir}/session_data
-(test "x$customdir" = "xNONE" || test "x$customdir" = "x") && customdir=${prefix}/local
-(test "x$custometcdir" = "xNONE" || test "x$custometcdir" = "x") && custometcdir=${customdir}/etc
-(test "x$customhtmldir" = "xNONE" || test "x$customhtmldir" = "x") && customhtmldir=${customdir}/html
-(test "x$customlexdir" = "xNONE" || test "x$customlexdir" = "x") && customlexdir=${customdir}/po
-(test "x$customlibdir" = "xNONE" || test "x$customlibdir" = "x") && customlibdir=${customdir}/lib
index e7d81b3..06c562a 100755 (executable)
@@ -5,8 +5,9 @@
 # configure, is in config.log if it exists.
 
 debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
 SHELL=${CONFIG_SHELL-/bin/sh}
-
 ## --------------------- ##
 ## M4sh Initialization.  ##
 ## --------------------- ##
@@ -15,46 +16,57 @@ SHELL=${CONFIG_SHELL-/bin/sh}
 if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
   emulate sh
   NULLCMD=:
+  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
 elif test -n "${BASH_VERSION+set}" && (set -o posix) >/dev/null 2>&1; then
   set -o posix
 fi
+DUALCASE=1; export DUALCASE # for MKS sh
 
-# NLS nuisances.
 # Support unset when possible.
-if (FOO=FOO; unset FOO) >/dev/null 2>&1; then
+if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then
   as_unset=unset
 else
   as_unset=false
 fi
 
-(set +x; test -n "`(LANG=C; export LANG) 2>&1`") &&
-    { $as_unset LANG || test "${LANG+set}" != set; } ||
-      { LANG=C; export LANG; }
-(set +x; test -n "`(LC_ALL=C; export LC_ALL) 2>&1`") &&
-    { $as_unset LC_ALL || test "${LC_ALL+set}" != set; } ||
-      { LC_ALL=C; export LC_ALL; }
-(set +x; test -n "`(LC_TIME=C; export LC_TIME) 2>&1`") &&
-    { $as_unset LC_TIME || test "${LC_TIME+set}" != set; } ||
-      { LC_TIME=C; export LC_TIME; }
-(set +x; test -n "`(LC_CTYPE=C; export LC_CTYPE) 2>&1`") &&
-    { $as_unset LC_CTYPE || test "${LC_CTYPE+set}" != set; } ||
-      { LC_CTYPE=C; export LC_CTYPE; }
-(set +x; test -n "`(LANGUAGE=C; export LANGUAGE) 2>&1`") &&
-    { $as_unset LANGUAGE || test "${LANGUAGE+set}" != set; } ||
-      { LANGUAGE=C; export LANGUAGE; }
-(set +x; test -n "`(LC_COLLATE=C; export LC_COLLATE) 2>&1`") &&
-    { $as_unset LC_COLLATE || test "${LC_COLLATE+set}" != set; } ||
-      { LC_COLLATE=C; export LC_COLLATE; }
-(set +x; test -n "`(LC_NUMERIC=C; export LC_NUMERIC) 2>&1`") &&
-    { $as_unset LC_NUMERIC || test "${LC_NUMERIC+set}" != set; } ||
-      { LC_NUMERIC=C; export LC_NUMERIC; }
-(set +x; test -n "`(LC_MESSAGES=C; export LC_MESSAGES) 2>&1`") &&
-    { $as_unset LC_MESSAGES || test "${LC_MESSAGES+set}" != set; } ||
-      { LC_MESSAGES=C; export LC_MESSAGES; }
+
+# Work around bugs in pre-3.0 UWIN ksh.
+$as_unset ENV MAIL MAILPATH
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+for as_var in \
+  LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \
+  LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \
+  LC_TELEPHONE LC_TIME
+do
+  if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then
+    eval $as_var=C; export $as_var
+  else
+    $as_unset $as_var
+  fi
+done
+
+# Required to use basename.
+if expr a : '\(a\)' >/dev/null 2>&1; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename /) >/dev/null 2>&1 && test "X`basename / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
 
 
 # Name of the executable.
-as_me=`(basename "$0") 2>/dev/null ||
+as_me=`$as_basename "$0" ||
 $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
         X"$0" : 'X\(//\)$' \| \
         X"$0" : 'X\(/\)$' \| \
@@ -65,6 +77,7 @@ echo X/"$0" |
          /^X\/\(\/\).*/{ s//\1/; q; }
          s/.*/./; q'`
 
+
 # PATH needs CR, and LINENO needs CR and PATH.
 # Avoid depending upon Character Ranges.
 as_cr_letters='abcdefghijklmnopqrstuvwxyz'
@@ -75,15 +88,15 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits
 
 # The user is always right.
 if test "${PATH_SEPARATOR+set}" != set; then
-  echo "#! /bin/sh" >conftest.sh
-  echo  "exit 0"   >>conftest.sh
-  chmod +x conftest.sh
-  if (PATH=".;."; conftest.sh) >/dev/null 2>&1; then
+  echo "#! /bin/sh" >conf$$.sh
+  echo  "exit 0"   >>conf$$.sh
+  chmod +x conf$$.sh
+  if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then
     PATH_SEPARATOR=';'
   else
     PATH_SEPARATOR=:
   fi
-  rm -f conftest.sh
+  rm -f conf$$.sh
 fi
 
 
@@ -132,6 +145,8 @@ do
   as_lineno_3=`(expr $as_lineno_1 + 1) 2>/dev/null`
   test "x$as_lineno_1" != "x$as_lineno_2" &&
   test "x$as_lineno_3"  = "x$as_lineno_2" ') 2>/dev/null; then
+            $as_unset BASH_ENV || test "${BASH_ENV+set}" != set || { BASH_ENV=; export BASH_ENV; }
+            $as_unset ENV || test "${ENV+set}" != set || { ENV=; export ENV; }
             CONFIG_SHELL=$as_dir/$as_base
             export CONFIG_SHELL
             exec "$CONFIG_SHELL" "$0" ${1+"$@"}
@@ -205,13 +220,20 @@ else
 fi
 rm -f conf$$ conf$$.exe conf$$.file
 
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p=:
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
 as_executable_p="test -f"
 
 # Sed expression to map a string onto a valid CPP name.
-as_tr_cpp="sed y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g"
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
 
 # Sed expression to map a string onto a valid variable name.
-as_tr_sh="sed y%*+%pp%;s%[^_$as_cr_alnum]%_%g"
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
 
 
 # IFS
@@ -221,7 +243,7 @@ as_nl='
 IFS="  $as_nl"
 
 # CDPATH.
-$as_unset CDPATH || test "${CDPATH+set}" != set || { CDPATH=$PATH_SEPARATOR; export CDPATH; }
+$as_unset CDPATH
 
 exec 6>&1
 
@@ -237,8 +259,8 @@ _ASBOX
 } >&5
 cat >&5 <<_CSEOF
 
-This file was extended by RT $as_me 3.0.9, which was
-generated by GNU Autoconf 2.53.  Invocation command line was
+This file was extended by RT $as_me 3.6.4, which was
+generated by GNU Autoconf 2.59.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
   CONFIG_HEADERS  = $CONFIG_HEADERS
@@ -249,7 +271,7 @@ generated by GNU Autoconf 2.53.  Invocation command line was
 _CSEOF
 echo "on `(hostname || uname -n) 2>/dev/null | sed 1q`" >&5
 echo >&5
-config_files=" sbin/rt-setup-database sbin/rt-test-dependencies Makefile etc/RT_Config.pm lib/RT.pm lib/t/00smoke.t lib/t/01harness.t lib/t/02regression.t lib/t/03web.pl lib/t/04_send_email.pl bin/mason_handler.fcgi bin/mason_handler.scgi bin/mason_handler.svc bin/rt-commit-handler bin/rt-crontool bin/rt-mailgate bin/rt bin/webmux.pl"
+config_files=" sbin/rt-dump-database sbin/rt-setup-database sbin/rt-test-dependencies bin/mason_handler.fcgi bin/mason_handler.scgi bin/standalone_httpd bin/rt-crontool bin/rt-mailgate bin/rt Makefile etc/RT_Config.pm lib/RT.pm bin/mason_handler.svc bin/webmux.pl"
 
 ac_cs_usage="\
 \`$as_me' instantiates files from templates according to the
@@ -259,22 +281,22 @@ Usage: $0 [OPTIONS] [FILE]...
 
   -h, --help       print this help, then exit
   -V, --version    print version number, then exit
+  -q, --quiet      do not print progress messages
   -d, --debug      don't remove temporary files
       --recheck    update $as_me by reconfiguring in the same conditions
   --file=FILE[:TEMPLATE]
-                   instantiate the configuration file FILE
+                  instantiate the configuration file FILE
 
 Configuration files:
 $config_files
 
 Report bugs to <bug-autoconf@gnu.org>."
 ac_cs_version="\
-RT config.status 3.0.9
-configured by ./configure, generated by GNU Autoconf 2.53,
-  with options \"\"
+RT config.status 3.6.4
+configured by ./configure, generated by GNU Autoconf 2.59,
+  with options \"'--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'\"
 
-Copyright 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001
-Free Software Foundation, Inc.
+Copyright (C) 2003 Free Software Foundation, Inc.
 This config.status script is free software; the Free Software Foundation
 gives unlimited permission to copy, distribute and modify it."
 srcdir=.
@@ -288,21 +310,23 @@ do
   --*=*)
     ac_option=`expr "x$1" : 'x\([^=]*\)='`
     ac_optarg=`expr "x$1" : 'x[^=]*=\(.*\)'`
-    shift
-    set dummy "$ac_option" "$ac_optarg" ${1+"$@"}
-    shift
+    ac_shift=:
+    ;;
+  -*)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
     ;;
-  -*);;
   *) # This is not an option, so the user has probably given explicit
      # arguments.
+     ac_option=$1
      ac_need_defaults=false;;
   esac
 
-  case $1 in
+  case $ac_option in
   # Handling of the options.
   -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
-    echo "running /bin/sh ./configure "  " --no-create --no-recursion"
-    exec /bin/sh ./configure  --no-create --no-recursion ;;
+    ac_cs_recheck=: ;;
   --version | --vers* | -V )
     echo "$ac_cs_version"; exit 0 ;;
   --he | --h)
@@ -317,13 +341,16 @@ Try \`$0 --help' for more information." >&2;}
   --debug | --d* | -d )
     debug=: ;;
   --file | --fil | --fi | --f )
-    shift
-    CONFIG_FILES="$CONFIG_FILES $1"
+    $ac_shift
+    CONFIG_FILES="$CONFIG_FILES $ac_optarg"
     ac_need_defaults=false;;
   --header | --heade | --head | --hea )
-    shift
-    CONFIG_HEADERS="$CONFIG_HEADERS $1"
+    $ac_shift
+    CONFIG_HEADERS="$CONFIG_HEADERS $ac_optarg"
     ac_need_defaults=false;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
 
   # This is an error.
   -*) { { echo "$as_me:$LINENO: error: unrecognized option: $1
@@ -338,27 +365,35 @@ Try \`$0 --help' for more information." >&2;}
   shift
 done
 
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+if $ac_cs_recheck; then
+  echo "running /bin/sh ./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' $ac_configure_extra_args " --no-create --no-recursion" >&6
+  exec /bin/sh ./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' $ac_configure_extra_args --no-create --no-recursion
+fi
+
 for ac_config_target in $ac_config_targets
 do
   case "$ac_config_target" in
   # Handling of arguments.
+  "sbin/rt-dump-database" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-dump-database" ;;
   "sbin/rt-setup-database" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-setup-database" ;;
   "sbin/rt-test-dependencies" ) CONFIG_FILES="$CONFIG_FILES sbin/rt-test-dependencies" ;;
-  "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
-  "etc/RT_Config.pm" ) CONFIG_FILES="$CONFIG_FILES etc/RT_Config.pm" ;;
-  "lib/RT.pm" ) CONFIG_FILES="$CONFIG_FILES lib/RT.pm" ;;
-  "lib/t/00smoke.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/00smoke.t" ;;
-  "lib/t/01harness.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/01harness.t" ;;
-  "lib/t/02regression.t" ) CONFIG_FILES="$CONFIG_FILES lib/t/02regression.t" ;;
-  "lib/t/03web.pl" ) CONFIG_FILES="$CONFIG_FILES lib/t/03web.pl" ;;
-  "lib/t/04_send_email.pl" ) CONFIG_FILES="$CONFIG_FILES lib/t/04_send_email.pl" ;;
   "bin/mason_handler.fcgi" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.fcgi" ;;
   "bin/mason_handler.scgi" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.scgi" ;;
-  "bin/mason_handler.svc" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.svc" ;;
-  "bin/rt-commit-handler" ) CONFIG_FILES="$CONFIG_FILES bin/rt-commit-handler" ;;
+  "bin/standalone_httpd" ) CONFIG_FILES="$CONFIG_FILES bin/standalone_httpd" ;;
   "bin/rt-crontool" ) CONFIG_FILES="$CONFIG_FILES bin/rt-crontool" ;;
   "bin/rt-mailgate" ) CONFIG_FILES="$CONFIG_FILES bin/rt-mailgate" ;;
   "bin/rt" ) CONFIG_FILES="$CONFIG_FILES bin/rt" ;;
+  "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+  "etc/RT_Config.pm" ) CONFIG_FILES="$CONFIG_FILES etc/RT_Config.pm" ;;
+  "lib/RT.pm" ) CONFIG_FILES="$CONFIG_FILES lib/RT.pm" ;;
+  "bin/mason_handler.svc" ) CONFIG_FILES="$CONFIG_FILES bin/mason_handler.svc" ;;
   "bin/webmux.pl" ) CONFIG_FILES="$CONFIG_FILES bin/webmux.pl" ;;
   *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
 echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
@@ -374,6 +409,9 @@ if $ac_need_defaults; then
   test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
 fi
 
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason to put it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
 # Create a temporary directory, and hook for its removal unless debugging.
 $debug ||
 {
@@ -382,17 +420,17 @@ $debug ||
 }
 
 # Create a (secure) tmp directory for tmp files.
-: ${TMPDIR=/tmp}
+
 {
-  tmp=`(umask 077 && mktemp -d -q "$TMPDIR/csXXXXXX") 2>/dev/null` &&
+  tmp=`(umask 077 && mktemp -d -q "./confstatXXXXXX") 2>/dev/null` &&
   test -n "$tmp" && test -d "$tmp"
 }  ||
 {
-  tmp=$TMPDIR/cs$$-$RANDOM
+  tmp=./confstat$$-$RANDOM
   (umask 077 && mkdir $tmp)
 } ||
 {
-   echo "$me: cannot create a temporary directory in $TMPDIR" >&2
+   echo "$me: cannot create a temporary directory in ." >&2
    { (exit 1); exit 1; }
 }
 
@@ -411,9 +449,9 @@ s,@SHELL@,/bin/sh,;t t
 s,@PATH_SEPARATOR@,:,;t t
 s,@PACKAGE_NAME@,RT,;t t
 s,@PACKAGE_TARNAME@,rt,;t t
-s,@PACKAGE_VERSION@,3.0.9,;t t
-s,@PACKAGE_STRING@,RT 3.0.9,;t t
-s,@PACKAGE_BUGREPORT@,rt-3.0-bugs@fsck.com,;t t
+s,@PACKAGE_VERSION@,3.6.4,;t t
+s,@PACKAGE_STRING@,RT 3.6.4,;t t
+s,@PACKAGE_BUGREPORT@,rt-bugs@bestpractical.com,;t t
 s,@exec_prefix@,/opt/rt3,;t t
 s,@prefix@,/opt/rt3,;t t
 s,@program_transform_name@,s,x,x,,;t t
@@ -432,17 +470,18 @@ s,@mandir@,/opt/rt3/man,;t t
 s,@build_alias@,,;t t
 s,@host_alias@,,;t t
 s,@target_alias@,,;t t
-s,@DEFS@,-DPACKAGE_NAME=\"RT\" -DPACKAGE_TARNAME=\"rt\" -DPACKAGE_VERSION=\"3.0.9\" -DPACKAGE_STRING=\"RT\ 3.0.9\" -DPACKAGE_BUGREPORT=\"rt-3.0-bugs@fsck.com\" ,;t t
+s,@DEFS@,-DPACKAGE_NAME=\"RT\" -DPACKAGE_TARNAME=\"rt\" -DPACKAGE_VERSION=\"3.6.4\" -DPACKAGE_STRING=\"RT\ 3.6.4\" -DPACKAGE_BUGREPORT=\"rt-bugs@bestpractical.com\" ,;t t
 s,@ECHO_C@,,;t t
 s,@ECHO_N@,-n,;t t
 s,@ECHO_T@,,;t t
 s,@LIBS@,,;t t
 s,@rt_version_major@,3,;t t
-s,@rt_version_minor@,0,;t t
-s,@rt_version_patch@,9,;t t
+s,@rt_version_minor@,6,;t t
+s,@rt_version_patch@,4,;t t
 s,@INSTALL_PROGRAM@,${INSTALL},;t t
 s,@INSTALL_SCRIPT@,${INSTALL},;t t
 s,@INSTALL_DATA@,${INSTALL} -m 644,;t t
+s,@AWK@,gawk,;t t
 s,@PERL@,/usr/bin/perl,;t t
 s,@SPEEDY_BIN@,/usr/local/bin/speedy,;t t
 s,@exp_prefix@,/opt/rt3,;t t
@@ -453,15 +492,15 @@ s,@exp_sysconfdir@,/opt/rt3/etc,;t t
 s,@exp_mandir@,/opt/rt3/man,;t t
 s,@exp_libdir@,/opt/rt3/lib,;t t
 s,@exp_datadir@,/opt/rt3/share,;t t
-s,@htmldir@,/opt/rt3/share/html,;t t
-s,@exp_htmldir@,/opt/rt3/share/html,;t t
+s,@htmldir@,/var/www/freeside/rt,;t t
+s,@exp_htmldir@,/var/www/freeside/rt,;t t
 s,@manualdir@,/opt/rt3/share/doc,;t t
 s,@exp_manualdir@,/opt/rt3/share/doc,;t t
 s,@exp_localstatedir@,/opt/rt3/var,;t t
 s,@logfiledir@,/opt/rt3/var/log,;t t
 s,@exp_logfiledir@,/opt/rt3/var/log,;t t
-s,@masonstatedir@,/opt/rt3/var/mason_data,;t t
-s,@exp_masonstatedir@,/opt/rt3/var/mason_data,;t t
+s,@masonstatedir@,/usr/local/etc/freeside/masondata,;t t
+s,@exp_masonstatedir@,/usr/local/etc/freeside/masondata,;t t
 s,@sessionstatedir@,/opt/rt3/var/session_data,;t t
 s,@exp_sessionstatedir@,/opt/rt3/var/session_data,;t t
 s,@customdir@,/opt/rt3/local,;t t
@@ -474,25 +513,28 @@ s,@customlexdir@,/opt/rt3/local/po,;t t
 s,@exp_customlexdir@,/opt/rt3/local/po,;t t
 s,@customlibdir@,/opt/rt3/local/lib,;t t
 s,@exp_customlibdir@,/opt/rt3/local/lib,;t t
-s,@rt_layout_name@,RT3,;t t
-s,@RTGROUP@,rt,;t t
+s,@rt_layout_name@,Freeside,;t t
 s,@BIN_OWNER@,root,;t t
 s,@LIBS_OWNER@,root,;t t
 s,@LIBS_GROUP@,bin,;t t
-s,@DB_TYPE@,mysql,;t t
-s,@ORACLE_ENV_PREF@,,;t t
+s,@DB_TYPE@,Pg,;t t
+s,@DATABASE_ENV_PREF@,,;t t
 s,@DB_HOST@,localhost,;t t
 s,@DB_PORT@,,;t t
 s,@DB_RT_HOST@,localhost,;t t
-s,@DB_DBA@,root,;t t
-s,@DB_DATABASE@,rt3,;t t
-s,@DB_RT_USER@,rt_user,;t t
-s,@DB_RT_PASS@,rt_pass,;t t
-s,@WEB_USER@,www,;t t
-s,@WEB_GROUP@,www,;t t
+s,@DB_DBA@,freeside,;t t
+s,@DB_DATABASE@,freeside,;t t
+s,@DB_RT_USER@,freeside,;t t
+s,@DB_RT_PASS@,,;t t
+s,@WEB_USER@,freeside,;t t
+s,@WEB_GROUP@,freeside,;t t
+s,@RTGROUP@,freeside,;t t
+s,@APACHECTL@,/usr/sbin/apachectl,;t t
+s,@RT_STANDALONE@,0,;t t
+s,@RT_DEVEL_MODE@,0,;t t
 s,@RT_VERSION_MAJOR@,3,;t t
-s,@RT_VERSION_MINOR@,0,;t t
-s,@RT_VERSION_PATCH@,9,;t t
+s,@RT_VERSION_MINOR@,6,;t t
+s,@RT_VERSION_PATCH@,4,;t t
 s,@RT_PATH@,/opt/rt3,;t t
 s,@RT_DOC_PATH@,/opt/rt3/share/doc,;t t
 s,@RT_LOCAL_PATH@,/opt/rt3/local,;t t
@@ -503,15 +545,17 @@ s,@RT_BIN_PATH@,/opt/rt3/bin,;t t
 s,@RT_SBIN_PATH@,/opt/rt3/sbin,;t t
 s,@RT_VAR_PATH@,/opt/rt3/var,;t t
 s,@RT_MAN_PATH@,/opt/rt3/man,;t t
-s,@MASON_DATA_PATH@,/opt/rt3/var/mason_data,;t t
+s,@MASON_DATA_PATH@,/usr/local/etc/freeside/masondata,;t t
 s,@MASON_SESSION_PATH@,/opt/rt3/var/session_data,;t t
-s,@MASON_HTML_PATH@,/opt/rt3/share/html,;t t
+s,@MASON_HTML_PATH@,/var/www/freeside/rt,;t t
 s,@LOCAL_ETC_PATH@,/opt/rt3/local/etc,;t t
 s,@MASON_LOCAL_HTML_PATH@,/opt/rt3/local/html,;t t
 s,@LOCAL_LEXICON_PATH@,/opt/rt3/local/po,;t t
 s,@LOCAL_LIB_PATH@,/opt/rt3/local/lib,;t t
 s,@DESTDIR@,/opt/rt3,;t t
 s,@RT_LOG_PATH@,/opt/rt3/var/log,;t t
+s,@LIBOBJS@,,;t t
+s,@LTLIBOBJS@,,;t t
 CEOF
 
   # Split the substitutions into bite-sized pieces for seds with
@@ -538,9 +582,9 @@ CEOF
       (echo ':t
   /@[a-zA-Z_][a-zA-Z_0-9]*@/!b' && cat $tmp/subs.frag) >$tmp/subs-$ac_sed_frag.sed
       if test -z "$ac_sed_cmds"; then
-       ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed"
+       ac_sed_cmds="sed -f $tmp/subs-$ac_sed_frag.sed"
       else
-       ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed"
+       ac_sed_cmds="$ac_sed_cmds | sed -f $tmp/subs-$ac_sed_frag.sed"
       fi
       ac_sed_frag=`expr $ac_sed_frag + 1`
       ac_beg=$ac_end
@@ -556,46 +600,51 @@ for ac_file in : $CONFIG_FILES; do test "x$ac_file" = x: && continue
   # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
   case $ac_file in
   - | *:- | *:-:* ) # input from stdin
-        cat >$tmp/stdin
-        ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
-        ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+       cat >$tmp/stdin
+       ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
+       ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
   *:* ) ac_file_in=`echo "$ac_file" | sed 's,[^:]*:,,'`
-        ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
+       ac_file=`echo "$ac_file" | sed 's,:.*,,'` ;;
   * )   ac_file_in=$ac_file.in ;;
   esac
 
   # Compute @srcdir@, @top_srcdir@, and @INSTALL@ for subdirectories.
   ac_dir=`(dirname "$ac_file") 2>/dev/null ||
 $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
-         X"$ac_file" : 'X\(//\)[^/]' \| \
-         X"$ac_file" : 'X\(//\)$' \| \
-         X"$ac_file" : 'X\(/\)' \| \
-         .     : '\(.\)' 2>/dev/null ||
+        X"$ac_file" : 'X\(//\)[^/]' \| \
+        X"$ac_file" : 'X\(//\)$' \| \
+        X"$ac_file" : 'X\(/\)' \| \
+        .     : '\(.\)' 2>/dev/null ||
 echo X"$ac_file" |
     sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
          /^X\(\/\/\)[^/].*/{ s//\1/; q; }
          /^X\(\/\/\)$/{ s//\1/; q; }
          /^X\(\/\).*/{ s//\1/; q; }
          s/.*/./; q'`
-  { case "$ac_dir" in
-  [\\/]* | ?:[\\/]* ) as_incr_dir=;;
-  *)                      as_incr_dir=.;;
-esac
-as_dummy="$ac_dir"
-for as_mkdir_dir in `IFS='/\\'; set X $as_dummy; shift; echo "$@"`; do
-  case $as_mkdir_dir in
-    # Skip DOS drivespec
-    ?:) as_incr_dir=$as_mkdir_dir ;;
-    *)
-      as_incr_dir=$as_incr_dir/$as_mkdir_dir
-      test -d "$as_incr_dir" ||
-        mkdir "$as_incr_dir" ||
-       { { echo "$as_me:$LINENO: error: cannot create \"$ac_dir\"" >&5
-echo "$as_me: error: cannot create \"$ac_dir\"" >&2;}
-   { (exit 1); exit 1; }; }
-    ;;
-  esac
-done; }
+  { if $as_mkdir_p; then
+    mkdir -p "$ac_dir"
+  else
+    as_dir="$ac_dir"
+    as_dirs=
+    while test ! -d "$as_dir"; do
+      as_dirs="$as_dir $as_dirs"
+      as_dir=`(dirname "$as_dir") 2>/dev/null ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| \
+        .     : '\(.\)' 2>/dev/null ||
+echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/; q; }
+         /^X\(\/\/\)[^/].*/{ s//\1/; q; }
+         /^X\(\/\/\)$/{ s//\1/; q; }
+         /^X\(\/\).*/{ s//\1/; q; }
+         s/.*/./; q'`
+    done
+    test ! -n "$as_dirs" || mkdir $as_dirs
+  fi || { { echo "$as_me:$LINENO: error: cannot create directory \"$ac_dir\"" >&5
+echo "$as_me: error: cannot create directory \"$ac_dir\"" >&2;}
+   { (exit 1); exit 1; }; }; }
 
   ac_builddir=.
 
@@ -622,12 +671,45 @@ case $srcdir in
     ac_srcdir=$ac_top_builddir$srcdir$ac_dir_suffix
     ac_top_srcdir=$ac_top_builddir$srcdir ;;
 esac
-# Don't blindly perform a `cd "$ac_dir"/$ac_foo && pwd` since $ac_foo can be
-# absolute.
-ac_abs_builddir=`cd "$ac_dir" && cd $ac_builddir && pwd`
-ac_abs_top_builddir=`cd "$ac_dir" && cd $ac_top_builddir && pwd`
-ac_abs_srcdir=`cd "$ac_dir" && cd $ac_srcdir && pwd`
-ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd`
+
+# Do not use `cd foo && pwd` to compute absolute paths, because
+# the directories may not exist.
+case `pwd` in
+.) ac_abs_builddir="$ac_dir";;
+*)
+  case "$ac_dir" in
+  .) ac_abs_builddir=`pwd`;;
+  [\\/]* | ?:[\\/]* ) ac_abs_builddir="$ac_dir";;
+  *) ac_abs_builddir=`pwd`/"$ac_dir";;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_builddir=${ac_top_builddir}.;;
+*)
+  case ${ac_top_builddir}. in
+  .) ac_abs_top_builddir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_top_builddir=${ac_top_builddir}.;;
+  *) ac_abs_top_builddir=$ac_abs_builddir/${ac_top_builddir}.;;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_srcdir=$ac_srcdir;;
+*)
+  case $ac_srcdir in
+  .) ac_abs_srcdir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_srcdir=$ac_srcdir;;
+  *) ac_abs_srcdir=$ac_abs_builddir/$ac_srcdir;;
+  esac;;
+esac
+case $ac_abs_builddir in
+.) ac_abs_top_srcdir=$ac_top_srcdir;;
+*)
+  case $ac_top_srcdir in
+  .) ac_abs_top_srcdir=$ac_abs_builddir;;
+  [\\/]* | ?:[\\/]* ) ac_abs_top_srcdir=$ac_top_srcdir;;
+  *) ac_abs_top_srcdir=$ac_abs_builddir/$ac_top_srcdir;;
+  esac;;
+esac
 
 
   case $INSTALL in
@@ -635,11 +717,6 @@ ac_abs_top_srcdir=`cd "$ac_dir" && cd $ac_top_srcdir && pwd`
   *) ac_INSTALL=$ac_top_builddir$INSTALL ;;
   esac
 
-  if test x"$ac_file" != x-; then
-    { echo "$as_me:$LINENO: creating $ac_file" >&5
-echo "$as_me: creating $ac_file" >&6;}
-    rm -f "$ac_file"
-  fi
   # Let's still pretend it is `configure' which instantiates (i.e., don't
   # use $as_me), people would be surprised to read:
   #    /* config.h.  Generated by config.status.  */
@@ -649,7 +726,7 @@ echo "$as_me: creating $ac_file" >&6;}
     configure_input="$ac_file.  "
   fi
   configure_input=$configure_input"Generated from `echo $ac_file_in |
-                                     sed 's,.*/,,'` by configure."
+                                    sed 's,.*/,,'` by configure."
 
   # First look for the input files in the build tree, otherwise in the
   # src tree.
@@ -658,33 +735,39 @@ echo "$as_me: creating $ac_file" >&6;}
       case $f in
       -) echo $tmp/stdin ;;
       [\\/$]*)
-         # Absolute (can't be DOS-style, as IFS=:)
-         test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+        # Absolute (can't be DOS-style, as IFS=:)
+        test -f "$f" || { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
 echo "$as_me: error: cannot find input file: $f" >&2;}
    { (exit 1); exit 1; }; }
-         echo $f;;
+        echo "$f";;
       *) # Relative
-         if test -f "$f"; then
-           # Build tree
-           echo $f
-         elif test -f "$srcdir/$f"; then
-           # Source tree
-           echo $srcdir/$f
-         else
-           # /dev/null tree
-           { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
+        if test -f "$f"; then
+          # Build tree
+          echo "$f"
+        elif test -f "$srcdir/$f"; then
+          # Source tree
+          echo "$srcdir/$f"
+        else
+          # /dev/null tree
+          { { echo "$as_me:$LINENO: error: cannot find input file: $f" >&5
 echo "$as_me: error: cannot find input file: $f" >&2;}
    { (exit 1); exit 1; }; }
-         fi;;
+        fi;;
       esac
     done` || { (exit 1); exit 1; }
-  sed "/^[     ]*VPATH[        ]*=/{
+
+  if test x"$ac_file" != x-; then
+    { echo "$as_me:$LINENO: creating $ac_file" >&5
+echo "$as_me: creating $ac_file" >&6;}
+    rm -f "$ac_file"
+  fi
+  sed "/^[      ]*VPATH[        ]*=/{
 s/:*\$(srcdir):*/:/;
 s/:*\${srcdir}:*/:/;
 s/:*@srcdir@:*/:/;
-s/^\([^=]*=[   ]*\):*/\1/;
+s/^\([^=]*=[    ]*\):*/\1/;
 s/:*$//;
-s/^[^=]*=[     ]*$//;
+s/^[^=]*=[      ]*$//;
 }
 
 :t
@@ -708,6 +791,27 @@ s,@INSTALL@,$ac_INSTALL,;t t
     rm -f $tmp/out
   fi
 
+  # Run the commands associated with the file.
+  case $ac_file in
+    sbin/rt-dump-database ) chmod ug+x $ac_file
+                ;;
+    sbin/rt-setup-database ) chmod ug+x $ac_file
+                ;;
+    sbin/rt-test-dependencies ) chmod ug+x $ac_file
+                ;;
+    bin/mason_handler.fcgi ) chmod ug+x $ac_file
+                ;;
+    bin/mason_handler.scgi ) chmod ug+x $ac_file
+                ;;
+    bin/standalone_httpd ) chmod ug+x $ac_file
+                ;;
+    bin/rt-crontool ) chmod ug+x $ac_file
+                ;;
+    bin/rt-mailgate ) chmod ug+x $ac_file
+                ;;
+    bin/rt ) chmod ug+x $ac_file
+                ;;
+  esac
 done
 
 { (exit 0); exit 0; }
index 5386a8e..7f7eadc 100644 (file)
@@ -17,7 +17,7 @@ use RT::Config;
 
 # {{{ Base Configuration
 
-# $rtname the string that RT will look for in mail messages to
+# $rtname is the string that RT will look for in mail messages to
 # figure out what ticket a new piece of mail belongs to
 
 # Your domain name is recommended, so as not to pollute the namespace.
@@ -26,6 +26,28 @@ use RT::Config;
 
 Set($rtname , "example.com");
 
+
+# This regexp controls what subject tags RT recognizes as its own.
+# If you're not dealing with historical $rtname values, you'll likely
+# never have to enable this feature.
+#
+# Be VERY CAREFUL with it. Note that it overrides $rtname for subject
+# token matching and that you should use only "non-capturing" parenthesis
+# grouping. For example:
+#
+#      Set($EmailSubjectTagRegex, qr/(?:example.com|example.org)/i );
+#
+# and NOT
+# 
+#      Set($EmailSubjectTagRegex, qr/(example.com|example.org)/i );
+#
+# This setting would make RT behave exactly as it does without the 
+# setting enabled.
+#
+# Set($EmailSubjectTagRegex, qr/\Q$rtname\E/i );
+
+
+
 # You should set this to your organization's DNS domain. For example,
 # fsck.com or asylum.arkham.ma.us. It's used by the linking interface to
 # guarantee that ticket URIs are unique and easy to construct.
@@ -42,14 +64,12 @@ Set($Timezone , 'US/Eastern');
 
 # }}}
 
-# }}}
-
 # {{{ Database Configuration
 
 # Database driver beeing used. Case matters
 # Valid types are "mysql", "Oracle" and "Pg"
 
-Set($DatabaseType , 'mysql');
+Set($DatabaseType , 'Pg');
 
 # The domain name of your database server
 # If you're running mysql and it's on localhost,
@@ -62,13 +82,13 @@ Set($DatabaseRTHost , 'localhost');
 Set($DatabasePort , '');
 
 #The name of the database user (inside the database)
-Set($DatabaseUser , 'rt_user');
+Set($DatabaseUser , 'freeside');
 
 # Password the DatabaseUser should use to access the database
-Set($DatabasePassword , 'rt_pass');
+Set($DatabasePassword , '');
 
 # The name of the RT's database on your database server
-Set($DatabaseName , 'rt3');
+Set($DatabaseName , 'freeside');
 
 # If you're using Postgres and have compiled in SSL support,
 # set DatabaseRequireSSL to 1 to turn on SSL communication
@@ -89,7 +109,7 @@ Set($OwnerEmail , 'root');
 
 Set($LoopsToRTOwner , 1);
 
-# If $StoreLoopss is defined, RT will record messages that it believes
+# If $StoreLoops is defined, RT will record messages that it believes
 # to be part of mail loops.
 # As it does this, it will try to be careful not to send mail to the
 # sender of these messages
@@ -106,12 +126,12 @@ Set($StoreLoops , undef);
 Set($MaxAttachmentSize , 10000000);
 
 # $TruncateLongAttachments: if this is set to a non-undef value,
-# RT will truncate attachments longer than MaxAttachmentLength.
+# RT will truncate attachments longer than MaxAttachmentSize.
 
 Set($TruncateLongAttachments , undef);
 
 # $DropLongAttachments: if this is set to a non-undef value,
-# RT will silently drop attachments longer than MaxAttachmentLength.
+# RT will silently drop attachments longer than MaxAttachmentSize.
 
 Set($DropLongAttachments , undef);
 
@@ -135,8 +155,12 @@ Set($RTAddressRegexp , '^rt\@example.com$');
 # (These values are passed to the CanonicalizeEmailAddress subroutine in RT/User.pm)
 # By default, that routine performs a s/$Match/$Replace/gi on any address passed to it
 
-Set($CanonicalizeEmailAddressMatch   , 'subdomain.example.com$');
-Set($CanonicalizeEmailAddressReplace , 'example.com');
+#Set($CanonicalizeEmailAddressMatch , '@subdomain\.example\.com$');
+#Set($CanonicalizeEmailAddressReplace , '@example.com');
+
+# set this to true and the create new user page will use the values that you
+# enter in the form but use the function CanonicalizeUserInfo in User_Local.pm
+Set($CanonicalizeOnCreate , 0);
 
 # If $SenderMustExistInExternalDatabase is true, RT will refuse to
 # create non-privileged accounts for unknown users if you are using
@@ -175,7 +199,7 @@ Set($CommentAddress , 'RT_CommentAddressNotSet');
 # If 'sendmailpipe' doesn't work well for you, try 'sendmail'
 #
 # Note that you should remove the '-t' from $SendmailArguments
-# if you use 'sendmail rather than 'sendmailpipe'
+# if you use 'sendmail' rather than 'sendmailpipe'
 
 Set($MailCommand , 'sendmailpipe');
 
@@ -186,6 +210,11 @@ Set($MailCommand , 'sendmailpipe');
 # These options are good for most sendmail wrappers and workalikes
 Set($SendmailArguments , "-oi -t");
 
+# $SendmailBounceArguments defines what flags to pass to $Sendmail
+# assuming RT needs to send an error (ie. bounce).
+
+Set($SendmailBounceArguments , '-f "<>"');
+
 # These arguments are good for sendmail brand sendmail 8 and newer
 #Set($SendmailArguments,"-oi -t -ODeliveryMode=b -OErrorMode=m");
 
@@ -216,12 +245,23 @@ Set($UseFriendlyToLine , 0);
 # are WatcherType and TicketId.
 Set($FriendlyToLineFormat, "\"%s of $RT::rtname Ticket #%s\":;");
 
-# By default RT doesn't notify the person who performs an update, as they
+# By default, RT doesn't notify the person who performs an update, as they
 # already know what they've done. If you'd like to change this behaviour,
 # Set $NotifyActor to 1
 
 Set($NotifyActor, 0);
 
+# By default, RT records each message it sends out to its own internal database.# To change this behaviour, set $RecordOutgoingEmail to 0 
+
+Set($RecordOutgoingEmail, 1);
+
+# VERP support (http://cr.yp.to/proto/verp.txt)
+# uncomment the following two directives to generate envelope senders
+# of the form ${VERPPrefix}${originaladdress}@${VERPDomain}
+# (i.e. rt-jesse=fsck.com@rt.example.com ) This currently only works
+# with sendmail and sendmailppie.
+# Set($VERPPrefix, 'rt-');
+# Set($VERPDomain, $RT::Organization);
 
 # }}}
 
@@ -247,33 +287,78 @@ Set($LogToFile      , undef);
 Set($LogDir, '/opt/rt3/var/log');
 Set($LogToFileNamed , "rt.log");    #log to rt.log
 
+# If true generates stack traces to file log or screen
+# never generates traces to syslog
+
+Set($LogStackTraces , 0);
+
+# On Solaris or UnixWare, set to ( socket => 'inet' ).  Options here
+# override any other options RT passes to Log::Dispatch::Syslog.
+# Other interesting flags include facility and logopt.  (See the
+# Log::Dispatch::Syslog documentation for more information.)  (Maybe
+# ident too, if you have multiple RT installations.)
+
+@LogToSyslogConf = () unless (@LogToSyslogConf);
+
+# RT has rudimentary SQL statement logging support if you have
+# DBIx-SearchBuilder 1.31_1 or higher; simply set $StatementLog to be
+# the level that you wish SQL statements to be logged at.
+Set($StatementLog, undef);
+
 # }}}
 
 # {{{ Web interface configuration
 
+# This determines the default stylesheet the RT web interface will use.
+# RT ships with two valid values by default:
+#
+#   3.5-default     The totally new, default layout for RT 3.5
+#   3.4-compat      A 3.4 compatibility stylesheet to make RT 3.5 look
+#                   (mostly) like 3.4
+#
+# This value actually specifies a directory in share/html/NoAuth/css/
+# from which RT will try to load the file main.css (which should
+# @import any other files the stylesheet needs).  This allows you to
+# easily and cleanly create your own stylesheets to apply to RT.
+
+Set($WebDefaultStylesheet, '3.5-default');
+
 # Define the directory name to be used for images in rt web
 # documents.
 
 # If you're putting the web ui somewhere other than at the root of
-# your server
-# $WebPath requires a leading / but no trailing /
+# your server, you should set $WebPath to the path you'll be 
+# serving RT at.
+# $WebPath requires a leading / but no trailing /.
+#
+# In most cases, you should leave $WebPath set to '' (an empty value).
 
 Set($WebPath , "");
 
+# If we're running as a superuser, run on port 80
+# Otherwise, pick a high port for this user.
+
+Set($WebPort , 80);# + ($< * 7274) % 32766 + ($< && 1024));
+
 # This is the Scheme, server and port for constructing urls to webrt
 # $WebBaseURL doesn't need a trailing /
 
-Set($WebBaseURL , "http://RT::WebBaseURL.not.configured:80");
+Set($WebBaseURL , "http://localhost:$WebPort");
 
 Set($WebURL , $WebBaseURL . $WebPath . "/");
 
 # $WebImagesURL points to the base URL where RT can find its images.
 
-Set($WebImagesURL , $WebURL . "NoAuth/images/");
+Set($WebImagesURL , $WebPath . "/NoAuth/images/");
+
+# $LogoURL points to the URL of the RT logo displayed in the web UI
 
-# $RTLogoURL points to the URL of the RT logo displayed in the web UI
+Set($LogoURL , $WebImagesURL . "bplogo.gif");
 
-Set($LogoURL , $WebImagesURL . "rt.jpg");
+# WebNoAuthRegex - What portion of RT's URLspace should not require
+# authentication.
+Set($WebNoAuthRegex, qr!^/rt(?:/+NoAuth/|
+                            /+REST/\d+\.\d+/NoAuth/)!x );
 
 # For message boxes, set the entry box width and what type of wrapping
 # to use.
@@ -284,11 +369,31 @@ Set($MessageBoxWidth , 72);
 # Default wrapping: "HARD"  (choices "SOFT", "HARD")
 Set($MessageBoxWrap, "HARD");
 
+# Support implicit links in WikiText custom fields?  A true value
+# causes InterCapped or ALLCAPS words in WikiText fields to
+# automatically become links to searches for those words.  If used on
+# RTFM articles, it links to the RTFM article with that name.
+Set($WikiImplicitLinks, 0);
+
 # if TrustHTMLAttachments is not defined, we will display them
 # as text. This prevents malicious HTML and javascript from being
 # sent in a request (although there is probably more to it than that)
 Set($TrustHTMLAttachments , undef);
 
+# Should RT redistribute correspondence that it identifies as
+# machine generated? A true value will do so; setting this to '0'
+# will cause no such messages to be redistributed.
+# You can also use 'privileged' (the default), which will redistribute
+# only to privileged users. This helps to protect against malformed
+# bounces and loops caused by autocreated requestors with bogus addresses.
+Set($RedistributeAutoGeneratedMessages, 'privileged');
+
+# If PreferRichText is set to a true value, RT will show HTML/Rich text
+# messages in preference to their plaintext alternatives. RT "scrubs" the 
+# html to show only a minimal subset of HTML to avoid possible contamination
+# by cross-site-scripting attacks.
+Set($PreferRichText, undef);
+
 # If $WebExternalAuth is defined, RT will defer to the environment's
 # REMOTE_USER variable.
 
@@ -316,32 +421,98 @@ Set($WebExternalAuto , undef);
 
 # Set($WebSessionClass , 'Apache::Session::File');
 
+
+# By default, RT's session cookie isn't marked as "secure" Some web browsers 
+# will treat secure cookies more carefully than non-secure ones, being careful
+# not to write them to disk, only send them over an SSL secured connection 
+# and so on. To enable this behaviour, set # $WebSecureCookies to a true value. 
+# NOTE: You probably don't want to turn this on _unless_ users are only connecting
+# via SSL encrypted HTTP connections.
+
+Set($WebSecureCookies, 0);
+
+
+# By default, RT clears its database cache after every page view.
+# This ensures that you've always got the most current information 
+# when working in a multi-process (mod_perl or FastCGI) Environment
+# Setting $WebFlushDbCacheEveryRequest to '0' will turn this off,
+# which will speed RT up a bit, at the expense of a tiny bit of data 
+# accuracy.
+
+Set($WebFlushDbCacheEveryRequest, '1');
+
+
 # $MaxInlineBody is the maximum attachment size that we want to see
 # inline when viewing a transaction. 13456 is a random sane-sounding
 # default.
 
 Set($MaxInlineBody, 13456);
 
-# $MyTicketsLength is the length of the table on the front page.
-# For some people, the default of 10 isn't big enough to get a feel for
-# how much work needs to be done before you get some time off.
+# $DefaultSummaryRows is default number of rows displayed in for search
+# results on the frontpage.
+
+Set($DefaultSummaryRows, 10);
+
+# By default, RT shows newest transactions at the bottom of the ticket
+# history page, if you want see them at the top set this to '0'.
+
+Set($OldestTransactionsFirst, '1');
+
+# By default, RT shows images attached to incoming (and outgoing) ticket updates
+# inline. Set this variable to 0 if you'd like to disable that behaviour
+
+Set($ShowTransactionImages, 1);
 
-Set($MyTicketsLength, 10);
+
+# $HomepageComponents is an arrayref of allowed components on a user's
+# customized homepage ("RT at a glance").
+
+Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders  RefreshHomepage)]);
 
 # @MasonParameters is the list of parameters for the constructor of
 # HTML::Mason's Apache or CGI Handler.  This is normally only useful
-# for debugging, eg. profiling individual components with
-#     (preamble => 'my $p = MasonX::Profiler->new($m, $r);');
+# for debugging, eg. profiling individual components with:
+#     use MasonX::Profiler; # available on CPAN
+#     @MasonParameters = (preamble => 'my $p = MasonX::Profiler->new($m, $r);');
 
 @MasonParameters = () unless (@MasonParameters);
 
+# $DefaultSearchResultFormat is the default format for RT search results
+Set ($DefaultSearchResultFormat, qq{
+   '<B><A HREF="$RT::WebPath/Ticket/Display.html?id=__id__">__id__</a></B>/TITLE:#',
+   '<B><A HREF="$RT::WebPath/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>'});
+
+# If $SuppressInlineTextFiles is set to a true value, then uploaded
+# text files (text-type attachments with file names) are prevented
+# from being displayed in-line when viewing a ticket's history.
+
+Set($SuppressInlineTextFiles, undef);
+
+# If $DontSearchFileAttachments is set to a true value, then uploaded
+# files (attachments with file names) are not searched during full-content
+# ticket searches.
+
+Set($DontSearchFileAttachments, undef);
+
+
 # }}}
 
 # {{{ RT UTF-8 Settings
 
 # An array that contains languages supported by RT's internationalization
-# interface.  Defaults to all *.po lexicons; set it to qw(en ja) will make
-# RT bilingual instead of multilingual, but will save same memory.
+# interface.  Defaults to all *.po lexicons; setting it to qw(en ja) will make
+# RT bilingual instead of multilingual, but will save some memory.
 
 @LexiconLanguages = qw(*) unless (@LexiconLanguages);
 
@@ -371,4 +542,46 @@ Set($AmbiguousDayInPast , 1);
 
 # }}}
 
+# {{{ Miscellaneous RT Settings
+
+# You can define new statuses and even reorder existing statuses here.
+# WARNING. DO NOT DELETE ANY OF THE DEFAULT STATUSES. If you do, RT
+# will break horribly. The statuses you add must be no longer than
+# 10 characters.
+
+@ActiveStatus = qw(new open stalled) unless @ActiveStatus;
+@InactiveStatus = qw(resolved rejected deleted) unless @InactiveStatus;
+
+# Backward compatability setting. Add/Delete Link used to record one
+# transaction and run one scrip. Set this value to 1 if you want
+# only one of the link transactions to have scrips run.
+Set($LinkTransactionsRun1Scrip , 0);
+
+# When this feature is enabled an user need ModifyTicket right on both
+# tickets to link them together, otherwise he can have right on any of
+# two.
+Set($StrictLinkACL, 1);
+
+# }}}
+
+
+# {{{ Development Mode
+#
+# RT comes with a "Development mode" setting. 
+# This setting, as a convenience for developers, turns on 
+# all sorts of development options that you most likely don't want in 
+# production:
+#
+# * Turns off Mason's 'static_source' directive. By default, you can't 
+#   edit RT's web ui components on the fly and have RT magically pick up
+#   your changes. (It's a big performance hit)
+#
+#  * More to come
+#
+
+Set($DevelMode, '0');
+
+# }}}
+
+
 1;
index a451921..0657d3d 100644 (file)
@@ -357,7 +357,7 @@ Set($LogoURL , $WebImagesURL . "bplogo.gif");
 
 # WebNoAuthRegex - What portion of RT's URLspace should not require
 # authentication.
-Set($WebNoAuthRegex, qr!^(?:/+NoAuth/|
+Set($WebNoAuthRegex, qr!^/rt(?:/+NoAuth/|
                             /+REST/\d+\.\d+/NoAuth/)!x );
 
 # For message boxes, set the entry box width and what type of wrapping
@@ -505,13 +505,6 @@ Set($SuppressInlineTextFiles, undef);
 
 Set($DontSearchFileAttachments, undef);
 
-# The GD module (which RT uses for graphs) uses a builtin font that doesn't
-# have full Unicode support. You can use a particular TrueType font by setting
-# $ChartFont to the absolute path of that font. Your GD library must have
-# support for TrueType fonts to use this option.
-
-Set($ChartFont, undef);
-
 
 # }}}
 
index f5cc298..c3d6a66 100644 (file)
 #
 #   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.
+
+$RT::rtname = '%%%RT_DOMAIN%%%';
+$RT::Organization = '%%%RT_DOMAIN%%%';
+
+$RT::Timezone = '%%%RT_TIMEZONE%%%';
+
+$RT::WebExternalAuth = 1;
+$RT::WebFallbackToInternal = 1; #no
+$RT::WebExternalAuto = 1;
+
+$RT::URI::freeside::IntegrationType = 'Internal';
+$RT::URI::freeside::URL = '%%%FREESIDE_URL%%%';
+
+$RT::URI::freeside::URL =~ m(^(https?://[^/]+)(/.*)$)i;
+$RT::WebBaseURL = $1;
+$RT::WebPath = "$2/rt";
+
+Set($DatabaseHost   , '');
+
+# These settings are user-editable.
+
+#old, RT 3.4 style (deprecated, useless):
+#$RT::MyTicketsLength = 10;
+#NEW, RT 3.6 style (uncomment to use):
+#Set($DefaultSummaryRows, 10);
+
+$RT::QuickCreateLong = 0; #set to true to cause quick ticket creation to
+                          #redirect to the "long" ticket creation screen
+                          #instead of just creating a ticket with the subject.
+
 1;
index ac29215..c8667c0 100644 (file)
@@ -1,10 +1,10 @@
 sub acl {
 return (
-"CREATE USER ${RT::DatabaseUser} identified by ${RT::DatabasePassword} ".
-"default tablespace USERS " .
-"temporary tablespace TEMP " .
-"quota unlimited on USERS" ,
-"grant connect, resource to ${RT::DatabaseUser}"
-);
+"CREATE USER ${RT::DatabaseUser} identified by ${RT::DatabasePassword}".
+"temporary tablespace TEMP" .
+"default tablespace USERS" .
+"quota unlimited on USERS;" ,
+"grant connect, resource to ${RT::DatabaseUser};",
+"exit;");
 }
 1;
index fb62559..16ea71b 100755 (executable)
@@ -7,42 +7,38 @@ sub acl {
 
       attachments_id_seq
       Attachments
-      Attributes
-      attributes_id_seq
       queues_id_seq
- Queues 
+      Queues
       links_id_seq
- Links 
+      Links
       principals_id_seq
- Principals 
+      Principals
       groups_id_seq
- Groups 
+      Groups
       scripconditions_id_seq
- ScripConditions 
+      ScripConditions
       transactions_id_seq
- Transactions 
+      Transactions
       scrips_id_seq
- Scrips 
+      Scrips
       acl_id_seq
- ACL 
+      ACL
       groupmembers_id_seq
- GroupMembers 
+      GroupMembers
       cachedgroupmembers_id_seq
- CachedGroupMembers 
+      CachedGroupMembers
       users_id_seq
- Users 
+      Users
       tickets_id_seq
- Tickets 
+      Tickets
       scripactions_id_seq
- ScripActions 
+      ScripActions
       templates_id_seq
- Templates 
objectcustomfieldvalues_id_s
- ObjectCustomFieldValues 
+      Templates
     ticketcustomfieldvalues_id_s
+      TicketCustomFieldValues
       customfields_id_seq
- CustomFields 
- objectcustomfields_id_s
- ObjectCustomFields 
+      CustomFields
       customfieldvalues_id_seq
       CustomFieldValues
       sessions
index 621ef12..0ecaa3b 100755 (executable)
@@ -1,9 +1,8 @@
 sub acl {
-return () if !$RT::DatabaseUser or $RT::DatabaseUser eq 'root';
 return  (
 "USE mysql;",
 "DELETE FROM user WHERE user = '${RT::DatabaseUser}';",
 "DELETE FROM db where db = '${RT::DatabaseName}';",
-"GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE ON ${RT::DatabaseName}.* TO ${RT::DatabaseUser}\@'${RT::DatabaseRTHost}' IDENTIFIED BY '${RT::DatabasePassword}';");
+"GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE ON ${RT::DatabaseName}.* TO ${RT::DatabaseUser}\@${RT::DatabaseRTHost} IDENTIFIED BY '${RT::DatabasePassword}';");
 }
 1;
diff --git a/rt/etc/schema.Oracle b/rt/etc/schema.Oracle
deleted file mode 100644 (file)
index 569d80c..0000000
+++ /dev/null
@@ -1,398 +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 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         VARCHAR2(255),
-        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
-);
-
index b7d53f8..46f8ec5 100755 (executable)
@@ -16,6 +16,7 @@ CREATE TABLE Attachments (
   PRIMARY KEY (id)
 ) TYPE=InnoDB;
 
+CREATE INDEX Attachments1 ON Attachments (Parent) ;
 CREATE INDEX Attachments2 ON Attachments (TransactionId) ;
 CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId) ;
 # }}}
@@ -58,9 +59,9 @@ CREATE TABLE Links (
   PRIMARY KEY (id)
 ) TYPE=InnoDB;
 
+CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type) ;
 CREATE INDEX Links2 ON Links (Base,  Type) ;
 CREATE INDEX Links3 ON Links (Target,  Type) ;
-CREATE INDEX Links4 ON Links (Type,LocalBase);
 
 # }}}
 
@@ -86,12 +87,12 @@ CREATE TABLE Groups (
   Description varchar(255) NULL  ,
   Domain varchar(64),
   Type varchar(64),
-  Instance integer,
+  Instance varchar(64),
   PRIMARY KEY (id)
 ) TYPE=InnoDB;
 
 CREATE INDEX Groups1 ON Groups (Domain,Instance,Type,id);
-CREATE INDEX Groups2 On Groups (Type, Instance);   
+CREATE INDEX Groups2 On Groups  (Type, Instance, Domain);   
 
 # }}}
 
@@ -117,23 +118,21 @@ CREATE TABLE ScripConditions (
 # {{{ Transactions
 CREATE TABLE Transactions (
   id INTEGER NOT NULL  AUTO_INCREMENT,
-  ObjectType varchar(64) NOT NULL,
-  ObjectId integer NOT NULL DEFAULT 0  ,
+  EffectiveTicket integer NOT NULL DEFAULT 0  ,
+  Ticket integer NOT NULL DEFAULT 0  ,
   TimeTaken integer NOT NULL DEFAULT 0  ,
   Type varchar(20) NULL  ,
   Field varchar(40) NULL  ,
   OldValue varchar(255) NULL  ,
   NewValue varchar(255) NULL  ,
-  ReferenceType varchar(255) NULL,
-  OldReference integer NULL  ,
-  NewReference integer NULL  ,
-  Data varchar(255) NULL  ,
+  Data varchar(100) NULL  ,
 
   Creator integer NOT NULL DEFAULT 0  ,
   Created DATETIME NULL  ,
   PRIMARY KEY (id)
 ) TYPE=InnoDB;
-CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
+CREATE INDEX Transactions1 ON Transactions (Ticket);
+CREATE INDEX Transactions2 ON Transactions (EffectiveTicket);
 
 # }}}
 
@@ -211,6 +210,7 @@ create table CachedGroupMembers (
 ) TYPE=InnoDB;
 
 CREATE INDEX DisGrouMem  on CachedGroupMembers (GroupId,MemberId,Disabled);
+CREATE INDEX GrouMem  on CachedGroupMembers (GroupId,MemberId);
 
 # }}}
 
@@ -257,6 +257,8 @@ CREATE TABLE Users (
 
 
 CREATE UNIQUE INDEX Users1 ON Users (Name) ;
+CREATE INDEX Users2 ON Users (Name);
+CREATE INDEX Users3 ON Users (id, EmailAddress);
 CREATE INDEX Users4 ON Users (EmailAddress);
 
 
@@ -297,6 +299,9 @@ CREATE TABLE Tickets (
 
 CREATE INDEX Tickets1 ON Tickets (Queue, Status) ;
 CREATE INDEX Tickets2 ON Tickets (Owner) ;
+CREATE INDEX Tickets3 ON Tickets (EffectiveId) ;
+CREATE INDEX Tickets4 ON Tickets (id, Status) ;
+CREATE INDEX Tickets5 ON Tickets (id, EffectiveId) ;
 CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type) ;
 
 # }}}
@@ -338,31 +343,21 @@ CREATE TABLE Templates (
 
 # }}}
 
-# {{{ ObjectCustomFieldValues 
+# {{{ TicketCustomFieldValues 
 
-CREATE TABLE ObjectCustomFieldValues (
+CREATE TABLE TicketCustomFieldValues (
   id INTEGER NOT NULL  AUTO_INCREMENT,
+  Ticket int NOT NULL  ,
   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
@@ -370,13 +365,10 @@ CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,Ob
 CREATE TABLE CustomFields (
   id INTEGER NOT NULL  AUTO_INCREMENT,
   Name varchar(200) NULL  ,
-  Type varchar(200) 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
+  Type varchar(200) NULL  ,
+  Queue integer NOT NULL DEFAULT 0 ,
   Description varchar(255) NULL  ,
   SortOrder integer NOT NULL DEFAULT 0  ,
-  LookupType varchar(255) NOT NULL,
 
   Creator integer NOT NULL DEFAULT 0  ,
   Created DATETIME NULL  ,
@@ -386,22 +378,8 @@ CREATE TABLE CustomFields (
   PRIMARY KEY (id)
 ) TYPE=InnoDB;
 
-# }}}
-
-# {{{ ObjectCustomFields 
+CREATE INDEX CustomFields1 on CustomFields (Disabled, Queue);
 
-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;
 
 # }}}
 
@@ -421,31 +399,6 @@ CREATE TABLE CustomFieldValues (
   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
diff --git a/rt/etc/upgrade/2.1.71 b/rt/etc/upgrade/2.1.71
deleted file mode 100644 (file)
index cb89a3a..0000000
+++ /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/Elements/ModifyQueue b/rt/html/Admin/Elements/ModifyQueue
deleted file mode 100644 (file)
index 36f9ce1..0000000
+++ /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/ModifyUser b/rt/html/Admin/Elements/ModifyUser
deleted file mode 100644 (file)
index 2faefef..0000000
+++ /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/Global/CustomField.html b/rt/html/Admin/Global/CustomField.html
deleted file mode 100644 (file)
index 3871d89..0000000
+++ /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 (file)
index 5930402..0000000
+++ /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/Users/Prefs.html b/rt/html/Admin/Users/Prefs.html
deleted file mode 100644 (file)
index 0bba9fa..0000000
+++ /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/Callbacks/ActivityReports/Elements/Tabs/Default b/rt/html/Callbacks/ActivityReports/Elements/Tabs/Default
new file mode 100644 (file)
index 0000000..f85d2e0
--- /dev/null
@@ -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 (file)
index 0000000..30480f7
--- /dev/null
@@ -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 (file)
index 0000000..4775a9a
--- /dev/null
@@ -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 (file)
index 0000000..db74ced
--- /dev/null
@@ -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 (file)
index 0000000..d4ca2b9
--- /dev/null
@@ -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 (file)
index 0000000..7daa09e
--- /dev/null
@@ -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 (file)
index 0000000..67c9e56
--- /dev/null
@@ -0,0 +1,116 @@
+% if ($@) {\r
+<P><FONT Color="red"><% $@ %></FONT></P>\r
+% }\r
+% if (!$NoUI) {\r
+<HR>\r
+<FORM Action="index.html" Method="POST">\r
+<TABLE>\r
+% foreach my $class (qw( Search Condition Action )) {\r
+<TR><TH>\r
+<% loc($class) %>\r
+</TH><TD>\r
+<SELECT NAME="<% $class %>">\r
+%  require File::Find;\r
+%  my @modules;\r
+%  File::Find::find(sub {\r
+%    push @modules, $1 if /^(?!Generic|UserDefined)(\w+)\.pm$/i;\r
+%  }, grep -d, map "$_/RT/$class", @INC);\r
+<OPTION <% $ARGS{$class} ? '' : 'SELECTED' %>></OPTION>\r
+%  foreach my $module (sort @modules) {\r
+%    my $fullname = "RT::$class\::$module";\r
+    <OPTION VALUE="<% $fullname %>" <% ($fullname eq $ARGS{$class}) ? 'SELECTED' : '' %>><% $module %></OPTION>\r
+%  }\r
+</SELECT>\r
+</TD><TH>\r
+<&|/l&>Parameter</&>\r
+</TH><TD>\r
+<INPUT NAME="<% $class %>Arg" VALUE="<% $ARGS{$class.'Arg'} %>">\r
+</TD></TR>\r
+% }\r
+<TR>\r
+<TD COLSPAN="4" ALIGN="Right">\r
+<LABEL>\r
+<INPUT TYPE="CheckBox" NAME="Verbose" <% $Verbose ? 'CHECKED' : '' %>><&|/l&>Verbose</&>\r
+</LABEL>\r
+<INPUT TYPE="Submit" VALUE="<&|/l&>Run</&>">\r
+</TD>\r
+</TABLE>\r
+</FORM>\r
+<HR>\r
+% }\r
+<%INIT>\r
+$m->print("<H1>", loc("Web CronTool"), "</H1>");\r
+if ($Search) {\r
+    my $load_module = sub {\r
+       my $modname = $_[0];\r
+       $modname =~ s{::}{/}g;\r
+       require "$modname.pm" or die (\r
+           loc( "Failed to load module [_1]. ([_2])", $_[0], $@ ) . "\n"\r
+       );\r
+    };\r
+    $m->print(loc("Starting..."), "<UL>");\r
+    eval {\r
+       $load_module->($Search);\r
+       $load_module->($Action) if $Action;\r
+       $load_module->($Condition) if $Condition;\r
+\r
+       if ($TemplateId and !$TemplateObj) {\r
+           $TemplateObj = RT::Template->new($RT::Nobody);\r
+           $TemplateObj->LoadById($TemplateId);\r
+       }\r
+\r
+       my $tickets = RT::Tickets->new($RT::SystemUser);\r
+       my $search  = $Search->new( TicketsObj => $tickets, Argument => $SearchArg );\r
+       $search->Prepare;\r
+       my $tickets_found = $search->TicketsObj;\r
+\r
+       #for each ticket we've found\r
+       while ( my $ticket = $tickets_found->Next ) {\r
+           $m->print("<LI>" . $ticket->Id . ": ") if $Verbose;\r
+           $m->print(loc("Checking...")) if $Verbose;\r
+\r
+           # perform some more advanced check\r
+           if ($Condition) {\r
+               my $ConditionObj = $Condition->new(\r
+                   TicketObj => $ticket,\r
+                   Argument  => $ConditionArg\r
+               );\r
+\r
+               # if the condition doesn't apply, get out of here\r
+               next unless ( $ConditionObj->IsApplicable );\r
+               $m->print(loc("Condition matches...")) if $Verbose;\r
+           }\r
+\r
+           if ($Action) {\r
+               #prepare our action\r
+               my $ActionObj = $Action->new(\r
+                   TicketObj => $ticket,\r
+                   TemplateObj => $TemplateObj,\r
+                   Argument  => $ActionArg\r
+               );\r
+\r
+               #if our preparation, move onto the next ticket\r
+               next unless ( $ActionObj->Prepare );\r
+               $m->print(loc("Action prepared...")) if $Verbose;\r
+\r
+               #commit our action.\r
+               next unless ( $ActionObj->Commit );\r
+               $m->print(loc("Action committed.")) if $Verbose;\r
+           }\r
+       }\r
+    };\r
+    $m->print('</UL>', loc("Finished."));\r
+}\r
+</%INIT>\r
+<%ARGS>\r
+$Search => undef\r
+$SearchArg => undef\r
+$Condition => undef\r
+$ConditionArg => undef\r
+$Action => undef\r
+$ActionArg => undef\r
+$TemplateId => undef\r
+$TemplateObj => undef\r
+$Verbose => 1\r
+$NoUI => 0\r
+</%ARGS>\r
index d849226..64ecef4 100644 (file)
@@ -55,7 +55,6 @@ $Warning => undef
 </%ARGS>
 
 <%PERL>
-use HTML::Entities;
 $m->out('<tr class="' . ( $Warning ? 'warnline' : $i % 2 ? 'oddline' : 'evenline' ) . '" >' );
 my $item;
 foreach my $column (@Format) {
@@ -72,9 +71,7 @@ foreach my $column (@Format) {
         next;
     }
     $item++;
-    my $class = $column->{class}
-       ? encode_entities($column->{class}, q{'"&<>}) : 'collection-as-table';
-    $m->out(qq{<td class="$class" });
+    $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('>');
index 16f13f9..0cb528f 100644 (file)
 %# END BPS TAGGED BLOCK }}}
 %# End of div#body from /Elements/PageLayout
 </div>
+</td>
+</tr>
+<tr>
+<td>
 <& /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, '2006', '<a href="http://www.bestpractical.com?rt='.$RT::VERSION.'">Best Practical Solutions, LLC</a>', &>[_1] RT [_2] Copyright 1996-[_3] [_4].</&>
@@ -66,6 +71,7 @@
 % }
 
 </div>
+-->
 % if ($Debug >= 2 ) {
 % require Data::Dumper;
 % my $d = Data::Dumper->new([\%ARGS], [qw(%ARGS)]);
 </pre>
 % }
 
+</TD>
+</TR>
+</TABLE>
+
   </body>
 </html>
 % $m->abort();
diff --git a/rt/html/Elements/FreesideInvoiceSearch b/rt/html/Elements/FreesideInvoiceSearch
new file mode 100644 (file)
index 0000000..3842b2f
--- /dev/null
@@ -0,0 +1,20 @@
+% if ( $FS::CurrentUser::CurrentUser->access_right('View invoices') ) {
+
+  <form action="<% $RT::URI::freeside::URL %>/search/cust_bill.html" STYLE="margin:0">
+      <SCRIPT TYPE="text/javascript">
+        function clearhint_search_invoice (what) {
+          if ( what.value == '(inv #)' )
+            what.value = '';
+        }
+      </SCRIPT>
+  <input name="invnum" accesskey="0" VALUE="(inv #)" SIZE="4" onFocus="clearhint_search_invoice(this);" onClick="clearhint_search_invoice(this);" STYLE="text-align:right; margin-bottom:1px; font-family: Arial, Verdana, Helvetica, sans-serif;">
+  
+% if ( $FS::CurrentUser::CurrentUser->access_right('List invoices') ) {
+  <A HREF="<% $RT::URI::freeside::URL %>search/report_cust_bill.html" STYLE="color: #ffffff; font-size: 70%; font-weight:normal">Advanced</A>
+% } 
+  <BR>
+  
+  <input type="submit" value="<&|/l&>Search invoices</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%">
+  </form>
+
+% }
diff --git a/rt/html/Elements/FreesideNewCust b/rt/html/Elements/FreesideNewCust
new file mode 100644 (file)
index 0000000..f60e995
--- /dev/null
@@ -0,0 +1,3 @@
+<form action="<% $RT::URI::freeside::URL %>/edit/cust_main.cgi" STYLE="margin:0">
+<INPUT TYPE="submit" VALUE="<&|/l&>New customer</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="vertical-align:bottom; font-size:100%">&nbsp;
+</FORM>
diff --git a/rt/html/Elements/FreesideSearch b/rt/html/Elements/FreesideSearch
new file mode 100644 (file)
index 0000000..2fed8fc
--- /dev/null
@@ -0,0 +1,11 @@
+<form action="<% $RT::URI::freeside::URL %>/search/cust_main.cgi" STYLE="margin:0">
+    <SCRIPT TYPE="text/javascript">
+      function clearhint_search_cust (what) {
+        if ( what.value == '(cust #, name, company or phone)' )
+          what.value = '';
+      }
+    </SCRIPT>
+<input name="search_cust" accesskey="0" VALUE="(cust #, name, company or phone)" SIZE="28" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" STYLE="text-align:right; font-family: Arial, Verdana, Helvetica, sans-serif;"><BR>
+<A NOTYET="<% $RT::URI::freeside::URL %>/search/cust_main.html" STYLE="color: #000000; font-size: 70%; font-weight:normal">Advanced</A>
+<input type="submit" value="<&|/l&>Search customers</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%">
+</form>
diff --git a/rt/html/Elements/FreesideSvcSearch b/rt/html/Elements/FreesideSvcSearch
new file mode 100644 (file)
index 0000000..4a59424
--- /dev/null
@@ -0,0 +1,11 @@
+<form action="<% $RT::URI::freeside::URL %>/search/cust_svc.html" STYLE="margin:0">
+    <SCRIPT TYPE="text/javascript">
+      function clearhint_search_svc (what) {
+        if ( what.value == '(user, user@domain or domain)' )
+          what.value = '';
+      }
+    </SCRIPT>
+<input name="search_svc" accesskey="0" VALUE="(user, user@domain or domain)" SIZE="26" onFocus="clearhint_search_svc(this);" onClick="clearhint_search_svc(this);" STYLE="text-align:right; font-family: Arial, Verdana, Helvetica, sans-serif;"><BR>
+            <A NOTYET="<% $RT::URI::freeside::URL %>search/svc_Smarter.html" STYLE="color: #000000; font-size: 70%; font-weight:normal">Advanced</A>
+<input type="submit" value="<&|/l&>Search services</&>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%">
+</form>
index d8db26c..4ac38cd 100644 (file)
@@ -58,7 +58,7 @@
 % }
 
 <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/<% $RT::WebDefaultStylesheet %>/main.css" type="text/css" media="all" />
 <link rel="stylesheet" href="<%$RT::WebPath%>/NoAuth/css/print.css" type="text/css" media="print" />
 
 % if ( $RSSAutoDiscovery ) {
 <& /Elements/Callback, _CallbackName => 'Head', %ARGS &>
 
 </head>
-  <body<% $id && qq[ id="comp-$id"] |n %>>
+  <body NOTBACKGROUND="<% $RT::URI::freeside::URL %>/images/background-cheat.png"
+        STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0"
+        <% $id && qq[ id="comp-$id"] |n %>
+  >
 
 % if ($ShowBar) {
-<& /Elements/Logo &>
+
+<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#FFFFFF" STYLE="padding-left:0; padding-right:4">
+  <tr> 
+    <td colspan=2 rowspan=2><img border=0 alt="freeside" src="<%$RT::WebImagesURL%>/small-logo.png" width="92" height="62"></td>
+    <td align="left" rowspan=2><font size=6><% &RT::URI::freeside::FreesideGetConfig('company_name') || 'ExampleCo' %></font></td>
+    <td align="right" valign="top">
 
 <div id="quickbar">
   <div id="quick-personal">
   </div>
 % }
 
+    </td>
+
+  </tr>
+  <tr>
+
+    <td align=right valign=bottom>
+      <table>
+        <tr>
+          <td align=right>
+            <FONT SIZE="-3">
+              <A HREF="http://www.sisd.com/freeside">Freeside</A>&nbsp;v<% &RT::URI::freeside::FreesideVersion() %><BR>
+             <A HREF="<% FS::Conf->new->config('support-key') ? "http://www.sisd.com/mediawiki/index.php/Supported:Documentation" : "http://www.sisd.com/mediawiki/index.php/Freeside:1.9:Documentation" %>">Documentation</A><BR>
+            </FONT>
+          </td>
+          <td bgcolor=#000000></td>
+          <td align=left>
+            <FONT SIZE="-3">
+             <A HREF="http://www.bestpractical.com/rt">RT</A>&nbsp;v<% $RT::VERSION %><BR>
+             <A HREF="http://wiki.bestpractical.com/">Documentation</A><BR>
+            </FONT>
+          </td>
+
+        </tr>
+      </table>
+    </td>
+
+  </tr>
+</table>
+
 <%INIT>
 $r->headers_out->{'Pragma'} = 'no-cache';
 $r->headers_out->{'Cache-control'} = 'no-cache';
 
+require RT::URI::freeside;
+
 my $id = $m->request_comp->path;
 $id =~ s|^/||g;
 $id =~ s|/|-|g;
index c276581..1aa1490 100644 (file)
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
+
+<table class="black" border=0 cellspacing=0 cellpadding=0 width="100%">
+<tr>
+  <TD colspan=5 WIDTH="100%" STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<% $RT::URI::freeside::URL %>/images/black-gradient.png" HEIGHT="13" WIDTH="100%"></TD>
+</tr>
+<tr>
+
   <div id="topactions">
-% foreach my $action (reverse sort keys %{$topactions}) {
+%  my $notfirst = 0; foreach my $action (sort keys %{$topactions}) {
     <span class="topaction">
+    <td class="blackright" ALIGN="right" VALIGN="center">
 % $m->out($topactions->{"$action"}->{'html'});
+    </td>
     </span>
 % }
   </div>
 
+</tr>
+</table>
+
 %# End of div#quickbar from /Elements/Header
 </div>
 
-% if ( $show_menu ) {
+<table border=0 cellspacing=0 cellpadding=0 width="100%" height="100%">
+  <TR>
+    <TD STYLE="padding:0" WIDTH="100%"><IMG BORDER=0 ALT="" SRC="<% $RT::URI::freeside::URL %>/images/black-gray-top.png" HEIGHT="13" WIDTH="100%"></TD>
+    </TR>
+    <TR HEIGHT="100%">
+      <TD>
+
 <div id="nav">
 <& /Elements/Menu, toptabs => $toptabs, current_toptab => $current_toptab &>
 </div>
-% }
 
 <div id="header">
   <h1><%$title%></h1>
@@ -233,5 +250,4 @@ $actions => undef
 $subactions => undef
 $title => $m->callers(-1)->path
 $AppName => undef
-$show_menu => 1
 </%ARGS>
index bad7503..75b3a45 100644 (file)
 %# END BPS TAGGED BLOCK }}}
 <div class="quick-create">
 <&| /Widgets/TitleBox, title => loc('Quick ticket creation') &>
-<form method="post" action="<%$RT::WebPath%>/index.html">
+<form method="post" action="<%$RT::WebPath%>/<% $RT::QuickCreateLong ? 'Ticket/Create.html' : 'index.html' %>">
 <input type="hidden" class="hidden" name="QuickCreate" value="1" />
 <table>
 <tr><td>
-<&|/l&>Subject</&>:<br /><input size="15" name="Subject" />
+<&|/l&>Subject</&>:<br /><input size="30" name="Subject" />
 </td><td>
 <&|/l&>Queue</&>:<br /><& /Elements/SelectNewTicketQueue, Name => 'Queue', ShowNullOption => 0 &>
 </td><td>
diff --git a/rt/html/Elements/ShadedBox b/rt/html/Elements/ShadedBox
deleted file mode 100644 (file)
index 36b9cae..0000000
+++ /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 (file)
index e9fb69e..0000000
+++ /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 (file)
index 8947fcd..0000000
+++ /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>
index 78abce4..a4fd7e2 100644 (file)
 %# 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 action="<% $RT::WebPath %>/Search/Simple.html" STYLE="margin:0">
+<SCRIPT TYPE="text/javascript">
+  function clearhint_search_ticket (what) {
+    if ( what.value == '(ticket # or subject string)' )
+      what.value = '';
+  }
+</SCRIPT>
+<input name="q" autocomplete="off" accesskey="0" class="field" VALUE="(ticket # or subject string)" onFocus="clearhint_search_ticket(this);" onClick="clearhint_search_ticket(this);" STYLE="text-align:right; font-family: Arial, Verdana, Helvetica, sans-serif;"><BR>
+<A HREF="<% $RT::WebPath %>/Search/Build.html" STYLE="color: #ffffff; font-size: 70%; font-weight:normal">Advanced</A>
+<input type="submit" class="fsblackbutton" value="<&|/l&>Search tickets</&>" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:70%;padding-left:2px;padding-right:2px">
 </form>
index 863cdd8..f94711c 100644 (file)
     tabs => $tabs,
     actions => $actions,
     subactions => $subactions,
-    title => $Title,
-    show_menu => $show_menu,
+    title => $Title
 &>
 <a name="skipnav" id="skipnav" accesskey="8"></a>
 <%INIT>
 my $action;
 my $basetopactions = {
-       A => { html => $m->scomp('/Elements/CreateTicket')      
+#      A => { html => $m->scomp('/Elements/CreateTicket')      
+#              },
+       A => { html => $m->scomp('/Elements/FreesideNewCust')   
                },
-       B => { html => $m->scomp('/Elements/SimpleSearch') 
+       B => { html => $m->scomp('/Elements/FreesideSearch')    
+               },
+       C => { html => $m->scomp('/Elements/FreesideInvoiceSearch')     
+               },
+       D => { html => $m->scomp('/Elements/FreesideSvcSearch') 
+               },
+       E => { html => $m->scomp('/Elements/SimpleSearch') 
                }
        };
-my $basetabs = {     A => { title => loc('Homepage'),
+my $basetabs = {
+                  ' A'=> { title => 'Billing Main',
+                           path  => &RT::URI::freeside::FreesideURL(),
+                         },
+                    A => { #title => loc('Homepage'),
+                           title => 'Ticketing Main',
                            path => '',
                          },
-                    Ab => { title => loc('Simple Search'),
+                    Ab => { title => loc('Simple Ticket Search'),
                         path => 'Search/Simple.html'
                          },
-                    B => { title => loc('Tickets'),
+                    B => { title => loc('Adv. Ticket Search'),
                         path => 'Search/Build.html'
                       },
                     C => { title => loc('Tools'),
@@ -102,6 +114,8 @@ if (!defined $toptabs) {
 if (!defined $topactions) {
    $topactions = $basetopactions;
 }
+
+  require RT::URI::freeside;
                     
   # Now let callbacks add their extra tabs
   $m->comp('/Elements/Callback', 
@@ -118,5 +132,4 @@ $tabs => undef
 $actions => undef
 $subactions => undef
 $Title => undef
-$show_menu => 1
 </%ARGS>
index 593a77b..b36101e 100644 (file)
@@ -65,7 +65,8 @@
 %   while (my $record = $Collection->Next) {
 %   $i++;
 % # Every ten rows, flush the buffer and put something on the page.
-% $m->flush_buffer() unless ($i % 10);
+% # hun, this flushes things out out-of-order for me on "RT at a glance"...?
+% # $m->flush_buffer() unless ($i % 10);
 <&   /Elements/CollectionAsTable/Row, Format => \@Format, i => $i, record => $record, maxitems => $maxitems &>
 %   }
 
diff --git a/rt/html/Elements/ViewUser b/rt/html/Elements/ViewUser
deleted file mode 100644 (file)
index 6572724..0000000
+++ /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/NoAuth/css/3.5-default/freeside.css b/rt/html/NoAuth/css/3.5-default/freeside.css
new file mode 100644 (file)
index 0000000..a595061
--- /dev/null
@@ -0,0 +1,82 @@
+.black {
+        background-color: #000000;
+        color: #ffffff;
+        background-position: left top;
+        vertical-align: top;
+        text-align: left;
+}
+
+.blackright { 
+        background-color: #000000;
+        color: #ffffff;
+        background-position: left top;
+        vertical-align: center;
+        text-align: right;
+        font-size:16px;
+        padding-right:4px
+}
+
+input.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-family: Arial, Verdana, Helvetica, sans-serif;
+        font-weight:bold;
+        padding-left:12px;
+        padding-right:12px;
+        overflow:visible;
+        filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff333333',EndColorStr='#ff666666')
+}
+
+input.fsblackbuttonselected {
+        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-family: Arial, Verdana, Helvetica, sans-serif;
+        font-weight:bold;
+        padding-left:12px;
+        padding-right:12px;
+        overflow:visible;
+        filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff330033',EndColorStr='#ff7e0079')
+}
+
+.darkmediumgray {
+        background-color: #aaaaaa;
+        background-position: left top;
+        vertical-align: top;
+        text-align: left;
+}
+.darkmediumgrayright { 
+        background-color: #aaaaaa;
+        background-position: left top;
+        vertical-align: top;
+        text-align: right;
+}
+.bggray {
+        background-color: #e8e8e8;
+        background-position: left top;
+        vertical-align: top;
+        text-align: left;
+}
+.bggrayright { 
+        background-color: #e8e8e8;
+        background-position: left top;
+        vertical-align: top;
+        text-align: right;
+}
+
+div.titlebox {
+        background: #d4d4d4;
+}
+
+div.titlebox-title {
+        background: #e8e8e8;
+}
index 13f1ba6..7c4fa5a 100644 (file)
@@ -58,4 +58,5 @@
 @import "nav.css";
 @import "header.css";
 @import "footer.css";
+@import "freeside.css";
 
index 9e83ef4..ddb2e68 100755 (executable)
@@ -49,7 +49,8 @@ body {
     font-family: Verdana, sans-serif;
     font-size: 76%;
     margin: 0;
-    background-color: white;
+    /* background-color: white; */
+    background-color: #e8e8e8;
 }
 
 .hide, .hidden { display: none !important; }
index dfc4cb9..e9decf8 100755 (executable)
 .ticket-transaction.even {
     background: #eee;
 }
+.ticket-transaction.odd {
+    background: #fff;
+}
+
 
 .ticket-transaction .date {
     font-size: 0.9em;
diff --git a/rt/html/NoAuth/images/back_home.gif b/rt/html/NoAuth/images/back_home.gif
deleted file mode 100644 (file)
index 40b19c1..0000000
Binary files a/rt/html/NoAuth/images/back_home.gif and /dev/null differ
index 53bb2ae..49a4a97 100644 (file)
Binary files a/rt/html/NoAuth/images/css/cb.gif and b/rt/html/NoAuth/images/css/cb.gif differ
index 754cee1..eeb7ff4 100644 (file)
Binary files a/rt/html/NoAuth/images/css/cbr.gif and b/rt/html/NoAuth/images/css/cbr.gif differ
index d16a5c5..d2ae8d8 100644 (file)
Binary files a/rt/html/NoAuth/images/css/ct.gif and b/rt/html/NoAuth/images/css/ct.gif differ
index 9754e15..d17e647 100644 (file)
Binary files a/rt/html/NoAuth/images/css/ctr.gif and b/rt/html/NoAuth/images/css/ctr.gif differ
diff --git a/rt/html/NoAuth/images/head_requestracker.gif b/rt/html/NoAuth/images/head_requestracker.gif
deleted file mode 100644 (file)
index 73315e9..0000000
Binary files a/rt/html/NoAuth/images/head_requestracker.gif and /dev/null differ
diff --git a/rt/html/NoAuth/images/rt.jpg b/rt/html/NoAuth/images/rt.jpg
deleted file mode 100644 (file)
index a137a93..0000000
Binary files a/rt/html/NoAuth/images/rt.jpg and /dev/null differ
diff --git a/rt/html/NoAuth/images/small-logo.png b/rt/html/NoAuth/images/small-logo.png
new file mode 100644 (file)
index 0000000..1e415e6
Binary files /dev/null and b/rt/html/NoAuth/images/small-logo.png differ
diff --git a/rt/html/NoAuth/images/space.gif b/rt/html/NoAuth/images/space.gif
deleted file mode 100644 (file)
index 1d11fa9..0000000
Binary files a/rt/html/NoAuth/images/space.gif and /dev/null differ
diff --git a/rt/html/NoAuth/images/spacer.gif b/rt/html/NoAuth/images/spacer.gif
deleted file mode 100644 (file)
index 5bfd67a..0000000
Binary files a/rt/html/NoAuth/images/spacer.gif and /dev/null differ
diff --git a/rt/html/NoAuth/images/squares_blue.gif b/rt/html/NoAuth/images/squares_blue.gif
deleted file mode 100644 (file)
index a28da5c..0000000
Binary files a/rt/html/NoAuth/images/squares_blue.gif and /dev/null differ
diff --git a/rt/html/NoAuth/printrt.css b/rt/html/NoAuth/printrt.css
deleted file mode 100644 (file)
index 72e7e8b..0000000
+++ /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 (file)
index 7fa2f83..0000000
+++ /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/RTx/Statistics/CallsMultiQueue/Elements/Chart b/rt/html/RTx/Statistics/CallsMultiQueue/Elements/Chart
new file mode 100755 (executable)
index 0000000..02a183b
--- /dev/null
@@ -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 (executable)
index 0000000..abf8aa7
--- /dev/null
@@ -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 (executable)
index 0000000..9a3a505
--- /dev/null
@@ -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 (file)
index 0000000..23f0c69
--- /dev/null
@@ -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 (executable)
index 0000000..06fc484
--- /dev/null
@@ -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 (executable)
index 0000000..239c095
--- /dev/null
@@ -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 (executable)
index 0000000..2e82b9c
--- /dev/null
@@ -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 (executable)
index 0000000..c0b4d9a
--- /dev/null
@@ -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 (file)
index 0000000..cecb02e
--- /dev/null
@@ -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 (file)
index 0000000..a482f81
--- /dev/null
@@ -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 (file)
index 0000000..bcfabe5
--- /dev/null
@@ -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 (file)
index 0000000..ce043e2
--- /dev/null
@@ -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 (file)
index 0000000..b4ccfd5
--- /dev/null
@@ -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 (file)
index 0000000..325e168
--- /dev/null
@@ -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 (executable)
index 0000000..c0b4d9a
--- /dev/null
@@ -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 (file)
index 0000000..3dc0697
--- /dev/null
@@ -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 (executable)
index 0000000..637f6dc
--- /dev/null
@@ -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 (file)
index 0000000..aef9e2f
--- /dev/null
@@ -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 (executable)
index 0000000..4fde113
--- /dev/null
@@ -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 (file)
index 0000000..e7839ea
--- /dev/null
@@ -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 (executable)
index 0000000..9505881
--- /dev/null
@@ -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 (file)
index 0000000..2ec1e0c
--- /dev/null
@@ -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 (executable)
index 0000000..d0cd9f1
--- /dev/null
@@ -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 (executable)
index 0000000..fa0ac55
--- /dev/null
@@ -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 (file)
index 0000000..d9885b0
--- /dev/null
@@ -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 (executable)
index 0000000..a069a7b
--- /dev/null
@@ -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 (executable)
index 0000000..2124b53
--- /dev/null
@@ -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 (executable)
index 0000000..99eb2a2
--- /dev/null
@@ -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 (executable)
index 0000000..7bc25da
--- /dev/null
@@ -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 (executable)
index 0000000..41490de
--- /dev/null
@@ -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 (file)
index 0000000..ef0d830
--- /dev/null
@@ -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 (file)
index 0000000..7bb756f
--- /dev/null
@@ -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 (file)
index 0000000..7c4aac7
--- /dev/null
@@ -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 (file)
index 0000000..f920328
--- /dev/null
@@ -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 (file)
index 0000000..fa9f475
--- /dev/null
@@ -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 (file)
index 0000000..b7c4b34
--- /dev/null
@@ -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 (file)
index 0000000..235b7b3
--- /dev/null
@@ -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 (file)
index 0000000..080efc0
--- /dev/null
@@ -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 (file)
index 0000000..a949820
--- /dev/null
@@ -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 (file)
index 0000000..6f81f5f
--- /dev/null
@@ -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 (file)
index 0000000..81ca301
--- /dev/null
@@ -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 (file)
index 0000000..4ecde2c
--- /dev/null
@@ -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 (file)
index 0000000..1f6ddb0
--- /dev/null
@@ -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/Elements/PickRestriction b/rt/html/Search/Elements/PickRestriction
deleted file mode 100644 (file)
index ff9b86b..0000000
+++ /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/TicketHeader b/rt/html/Search/Elements/TicketHeader
deleted file mode 100644 (file)
index ed2f60e..0000000
+++ /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 (file)
index 5def9ea..0000000
+++ /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 (file)
index 5d1ad20..0000000
+++ /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 (file)
index 68b1fd7..0000000
+++ /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/Ticket/Elements/AddCustomers b/rt/html/Ticket/Elements/AddCustomers
new file mode 100644 (file)
index 0000000..01c7367
--- /dev/null
@@ -0,0 +1,50 @@
+%# 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.
+<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>
+% }
+
+% }
+
+<%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/html/Ticket/Elements/EditCustomers b/rt/html/Ticket/Elements/EditCustomers
new file mode 100644 (file)
index 0000000..c5a6f70
--- /dev/null
@@ -0,0 +1,67 @@
+%# 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 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">
+% #while (my $link = $Ticket->MemberOf->Next) {
+% foreach my $link (
+%   grep { $_->TargetURI->Resolver->{'fstable'} eq 'cust_main' }
+%   grep { $_->TargetURI->Scheme eq 'freeside' }
+%        @{ $Ticket->_Links('Base')->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/html/Ticket/Elements/EditLinks b/rt/html/Ticket/Elements/EditLinks
deleted file mode 100644 (file)
index bdb8a6b..0000000
+++ /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/ShowCustomers b/rt/html/Ticket/Elements/ShowCustomers
new file mode 100644 (file)
index 0000000..612727e
--- /dev/null
@@ -0,0 +1,40 @@
+%# 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 $customerURI (
+%   grep { $_->Resolver->{'fstable'} eq 'cust_main' }
+%   grep { $_->Scheme eq 'freeside' }
+%    map { $_->TargetURI }
+%        @{ $Ticket->_Links('Base')->ItemsArrayRef }
+% ) {
+%   $cust++;
+%   my $cust_main = '';
+  <tr>
+    <td class="value">
+      <A HREF="<% $customerURI->Resolver->HREF %>"><% $customerURI->Resolver->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/html/Ticket/Elements/ShowLink b/rt/html/Ticket/Elements/ShowLink
deleted file mode 100644 (file)
index 493fd95..0000000
+++ /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 (file)
index f88a600..0000000
+++ /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 (file)
index e443132..0000000
+++ /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/ShowReferences b/rt/html/Ticket/Elements/ShowReferences
deleted file mode 100644 (file)
index bb323f6..0000000
+++ /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>
index ffd71d3..e3464c7 100644 (file)
             <& /Ticket/Elements/ShowPeople, Ticket => $Ticket &>
          </&>
 
+         <&| /Widgets/TitleBox, title => loc('Customers'),
+               title_href =>"$RT::WebPath/Ticket/ModifyCustomers.html?id=".$Ticket->Id,
+               class=> 'ticket-info-customers' &>
+          <& /Ticket/Elements/ShowCustomers, Ticket => $Ticket &>
+          </&>
+
       <& /Ticket/Elements/ShowAttachments, Ticket => $Ticket, Attachments => $Attachments &>
         <br />
          <& /Ticket/Elements/ShowRequestor, Ticket => $Ticket &>
index 9581237..b014d07 100644 (file)
@@ -139,8 +139,14 @@ unless ( ($message->GetHeader('Content-Disposition')||"") =~ /attachment/i ) {
             # 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; }
+                #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 ($@) { 1; }
 
                 $m->comp(
                     'ShowMessageStanza',
@@ -176,9 +182,9 @@ $m->comp(
     ParentObj => $message
 );
 
+}
 </%PERL>
 </div>
-% }
 <%ARGS>
 $Ticket => undef
 $Transaction => undef
index 1eb2aa8..3dee8df 100644 (file)
@@ -121,6 +121,8 @@ my $ticket_page_tabs = {
       { title => loc('People'), path => "Ticket/ModifyPeople.html?id=" . $id, },
     _E => { title => loc('Links'),
             path  => "Ticket/ModifyLinks.html?id=" . $id, },
+    _Eb=> { title => loc('Customers'),
+            path  => "Ticket/ModifyCustomers.html?id=" . $id, },
     _F => { title => loc('Reminders'),
             path  => "Ticket/Reminders.html?id=" . $id,
             separator => 1, },
diff --git a/rt/html/Ticket/ModifyCustomers.html b/rt/html/Ticket/ModifyCustomers.html
new file mode 100644 (file)
index 0000000..72d103b
--- /dev/null
@@ -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>
index 1d8548d..1320b8f 100755 (executable)
@@ -64,6 +64,8 @@ $title => ''
 $title_class => ''
 $titleright_href => undef
 $titleright => undef
+$contentbg => "#d4d4d4"
+$color => "#336699"
 $id => ''
 $hideable => 1
 </%ARGS>
index 7e941a2..0d0c0f5 100644 (file)
@@ -1,29 +1,50 @@
-# BEGIN LICENSE BLOCK
+# BEGIN BPS TAGGED BLOCK {{{
 # 
-# Copyright (c) 1996-2002 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
+#                                          <jesse@bestpractical.com>
 # 
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # been provided with this software, but in any event can be snarfed
-# from www.gnu.org
+# from www.gnu.org.
 # 
 # This work is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/copyleft/gpl.html.
 # 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
 # 
+# CONTRIBUTION SUBMISSION POLICY:
 # 
-# END LICENSE BLOCK
-
-
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
 package RT;
 use strict;
 use RT::I18N;
@@ -33,7 +54,6 @@ use RT::System;
 use vars qw($VERSION $System $SystemUser $Nobody $Handle $Logger
         $CORE_CONFIG_FILE
         $SITE_CONFIG_FILE
-        $VENDOR_CONFIG_FILE
         $BasePath
         $EtcPath
         $VarPath
@@ -41,19 +61,23 @@ use vars qw($VERSION $System $SystemUser $Nobody $Handle $Logger
         $LocalEtcPath
         $LocalLexiconPath
         $LogDir
+        $BinPath
         $MasonComponentRoot
         $MasonLocalComponentRoot
         $MasonDataDir
         $MasonSessionDir
 );
 
-$VERSION = '3.0.9';
+$VERSION = '3.6.4';
 $CORE_CONFIG_FILE = "/opt/rt3/etc/RT_Config.pm";
 $SITE_CONFIG_FILE = "/opt/rt3/etc/RT_SiteConfig.pm";
 
+
+
 $BasePath = '/opt/rt3';
 
 $EtcPath = '/opt/rt3/etc';
+$BinPath = '/opt/rt3/bin';
 $VarPath = '/opt/rt3/var';
 $LocalPath = '/opt/rt3/local';
 $LocalEtcPath = '/opt/rt3/local/etc';
@@ -61,7 +85,7 @@ $LocalLexiconPath = '/opt/rt3/local/po';
 
 # $MasonComponentRoot is where your rt instance keeps its mason html files
 
-$MasonComponentRoot = '/opt/rt3/share/html';
+$MasonComponentRoot = '/var/www/freeside/rt';
 
 # $MasonLocalComponentRoot is where your rt instance keeps its site-local
 # mason html files.
@@ -70,7 +94,7 @@ $MasonLocalComponentRoot = '/opt/rt3/local/html';
 
 # $MasonDataDir Where mason keeps its datafiles
 
-$MasonDataDir = '/opt/rt3/var/mason_data';
+$MasonDataDir = '/usr/local/etc/freeside/masondata';
 
 # RT needs to put session data (for preserving state between connections
 # via the web interface)
@@ -80,44 +104,90 @@ $MasonSessionDir = '/opt/rt3/var/session_data';
 
 =head1 NAME
 
-       RT - Request Tracker
+RT - Request Tracker
 
 =head1 SYNOPSIS
 
-       A fully featured request tracker package
+A fully featured request tracker package
 
 =head1 DESCRIPTION
 
+=head2 LoadConfig
 
-=cut
-
-=item LoadConfig
+Load RT's config file.  First, the site configuration file
+(C<RT_SiteConfig.pm>) is loaded, in order to establish overall site
+settings like hostname and name of RT instance.  Then, the core
+configuration file (C<RT_Config.pm>) is loaded to set fallback values
+for all settings; it bases some values on settings from the site
+configuration file.
 
-Load RT's config file. First, go after the core config file. 
-After that, try to load the vendor config.
-After that, go after the site config.
+In order for the core configuration to not override the site's
+settings, the function C<Set> is used; it only sets values if they
+have not been set already.
 
 =cut
 
 sub LoadConfig {
      local *Set = sub { $_[0] = $_[1] unless defined $_[0] }; 
+
+    my $username = getpwuid($>);
+    my $group = getgrgid($();
+    my $message = <<EOF;
+
+RT couldn't load RT config file %s as:
+    user: $username 
+    group: $group
+
+The file is owned by user %s and group %s.  
+
+This usually means that the user/group your webserver is running
+as cannot read the file.  Be careful not to make the permissions
+on this file too liberal, because it contains database passwords.
+You may need to put the webserver user in the appropriate group
+(%s) or change permissions be able to run succesfully.
+EOF
+
+
     if ( -f "$SITE_CONFIG_FILE" ) {
-        require $SITE_CONFIG_FILE
-          || die ("Couldn't load RT config file  '$SITE_CONFIG_FILE'\n$@");
+        eval { require $SITE_CONFIG_FILE };
+        if ($@) {
+            my ($fileuid,$filegid) = (stat($SITE_CONFIG_FILE))[4,5];
+            my $fileusername = getpwuid($fileuid);
+            my $filegroup = getgrgid($filegid);
+            my $errormessage = sprintf($message, $SITE_CONFIG_FILE,
+                                       $fileusername, $filegroup, $filegroup);
+            die ("$errormessage\n$@");
+        }
     }
-    require $CORE_CONFIG_FILE
-      || die ("Couldn't load RT config file '$CORE_CONFIG_FILE'\n$@");
+    eval { require $CORE_CONFIG_FILE };
+    if ($@) {
+        my ($fileuid,$filegid) = (stat($SITE_CONFIG_FILE))[4,5];
+        my $fileusername = getpwuid($fileuid);
+        my $filegroup = getgrgid($filegid);
+        my $errormessage = sprintf($message, $SITE_CONFIG_FILE,
+                                   $fileusername, $filegroup, $filegroup);
+        die ("$errormessage '$CORE_CONFIG_FILE'\n$@") 
+    }
+
+    # RT::Essentials mistakenly recommends that WebPath be set to '/'.
+    # If the user does that, do what they mean.
+    $RT::WebPath = '' if ($RT::WebPath eq '/');
+
+    $ENV{'TZ'} = $RT::Timezone if ($RT::Timezone);
+
     RT::I18N->Init;
 }
 
-=item Init
+=head2 Init
+
+Conenct to the database, set up logging.
 
-    Conenct to the database, set up logging.
-    
 =cut
 
 sub Init {
 
+    CheckPerlRequirements();
+
     #Get a database connection
     ConnectToDatabase();
 
@@ -131,16 +201,17 @@ sub Init {
   
     $System = RT::System->new();
 
-   InitLogging(); 
+    InitClasses();
+    InitLogging(); 
 }
 
-  
+
 =head2 ConnectToDatabase
 
 Get a database connection
 
 =cut
+
 sub ConnectToDatabase {
     require RT::Handle;
     unless ($Handle && $Handle->dbh && $Handle->dbh->ping) {
@@ -148,15 +219,16 @@ sub ConnectToDatabase {
     } 
     $Handle->Connect();
 }
-    
+
 =head2 InitLogging
 
 Create the RT::Logger object. 
 
 =cut
+
 sub InitLogging {
 
-    # We have to set the record seperator ($, man perlvar)
+    # We have to set the record separator ($, man perlvar)
     # or Log::Dispatch starts getting
     # really pissy, as some other module we use unsets it.
 
@@ -165,74 +237,100 @@ sub InitLogging {
 
     unless ($RT::Logger) {
 
-    $RT::Logger=Log::Dispatch->new();
+    $RT::Logger = Log::Dispatch->new();
+
+    my $simple_cb = sub {
+        # if this code throw any warning we can get segfault
+        no warnings;
+
+        my %p = @_;
+
+        my $frame = 0; # stack frame index
+        # skip Log::* stack frames
+        $frame++ while( caller($frame) && caller($frame) =~ /^Log::/ );
+
+        my ($package, $filename, $line) = caller($frame);
+        $p{message} =~ s/(?:\r*\n)+$//;
+        my $str = "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n";
+
+        if( $RT::LogStackTraces ) {
+            $str .= "\nStack trace:\n";
+            # skip calling of the Log::* subroutins
+            $frame++ while( caller($frame) && (caller($frame))[3] =~ /^Log::/ );
+            while( my ($package, $filename, $line, $sub) = caller($frame++) ) {
+                $str .= "\t". $sub ."() called at $filename:$line\n";
+            }
+        }
+        return $str;
+    };
+
+    my $syslog_cb = sub {
+        my %p = @_;
+
+        my $frame = 0; # stack frame index
+        # skip Log::* stack frames
+        $frame++ while( caller($frame) && caller($frame) =~ /^Log::/ );
+        my ($package, $filename, $line) = caller($frame);
+
+        # syswrite() cannot take utf8; turn it off here.
+        Encode::_utf8_off($p{message});
+
+        $p{message} =~ s/(?:\r*\n)+$//;
+        if ($p{level} eq 'debug') {
+            return "$p{message}\n"
+        } else {
+            return "$p{message} ($filename:$line)\n"
+        }
+    };
     
     if ($RT::LogToFile) {
-
-    unless (-d $RT::LogDir && -w $RT::LogDir) {
-        # localizing here would be hard when we don't have a current user yet
-        # die $self->loc("Log directory [_1] not found or couldn't be written.\n RT can't run.", $RT::LogDir);
-        die ("Log directory $RT::LogDir not found or couldn't be written.\n RT can't run.");
-    }
-
-       my $filename;
-       if ($RT::LogToFileNamed =~ m![/\\]!) {
-           # looks like an absolute path.
-           $filename = $RT::LogToFileNamed;
-       }
-       else {
-           $filename = "$RT::LogDir/$RT::LogToFileNamed";
-       }
-    require Log::Dispatch::File;
-
-
-         $RT::Logger->add(Log::Dispatch::File->new
-                      ( name=>'rtlog',
-                        min_level=> $RT::LogToFile,
-                        filename=> $filename,
-                        mode=>'append',
-                        callbacks => sub { my %p = @_;
-                                my ($package, $filename, $line) = caller(5);
-                                return "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n"}
-             
-             
-             
-                      ));
+        my ($filename, $logdir);
+        if ($RT::LogToFileNamed =~ m![/\\]!) {
+            # looks like an absolute path.
+            $filename = $RT::LogToFileNamed;
+            ($logdir) = $RT::LogToFileNamed =~ m!^(.*[/\\])!;
+        }
+        else {
+            $filename = "$RT::LogDir/$RT::LogToFileNamed";
+            $logdir = $RT::LogDir;
+        }
+
+        unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
+            # localizing here would be hard when we don't have a current user yet
+            die "Log file $filename couldn't be written or created.\n RT can't run.";
+        }
+
+        package Log::Dispatch::File;
+        require Log::Dispatch::File;
+        $RT::Logger->add(Log::Dispatch::File->new
+                       ( name=>'rtlog',
+                         min_level=> $RT::LogToFile,
+                         filename=> $filename,
+                         mode=>'append',
+                         callbacks => $simple_cb,
+                       ));
     }
     if ($RT::LogToScreen) {
-       require Log::Dispatch::Screen;
-       $RT::Logger->add(Log::Dispatch::Screen->new
-                    ( name => 'screen',
-                      min_level => $RT::LogToScreen,
-                        callbacks => sub { my %p = @_;
-                                my ($package, $filename, $line) = caller(5);
-                                return "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n"
-                               },
-             
-                      stderr => 1
-                    ));
+        package Log::Dispatch::Screen;
+        require Log::Dispatch::Screen;
+        $RT::Logger->add(Log::Dispatch::Screen->new
+                     ( name => 'screen',
+                       min_level => $RT::LogToScreen,
+                       callbacks => $simple_cb,
+                       stderr => 1,
+                     ));
     }
     if ($RT::LogToSyslog) {
-       require Log::Dispatch::Syslog;
-       $RT::Logger->add(Log::Dispatch::Syslog->new
-                    ( name => 'syslog',
+        package Log::Dispatch::Syslog;
+        require Log::Dispatch::Syslog;
+        $RT::Logger->add(Log::Dispatch::Syslog->new
+                     ( name => 'syslog',
                        ident => 'RT',
-                      min_level => $RT::LogToSyslog,
-                        callbacks => sub { my %p = @_;
-                                my ($package, $filename, $line) = caller(5);
-
-                               # syswrite() cannot take utf8; turn it off here.
-                               Encode::_utf8_off($p{message});
-
-                               if ($p{level} eq 'debug') {
-
-                                return "$p{message}\n" }
-                               else {
-                                return "$p{message} ($filename:$line)\n"}
-                               },
-             
-                      stderr => 1
-                    ));
+                       min_level => $RT::LogToSyslog,
+                       callbacks => $syslog_cb,
+                       stderr => 1,
+                       @RT::LogToSyslogConf
+                     ));
     }
 
     }
@@ -244,7 +342,16 @@ sub InitLogging {
 ## Mason).  It will log all problems through the standard logging
 ## mechanism (see above).
 
-$SIG{__WARN__} = sub {$RT::Logger->warning($_[0])};
+    $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.
 
@@ -252,67 +359,102 @@ $SIG{__DIE__}  = sub {
     unless ($^S || !defined $^S ) {
         $RT::Handle->Rollback();
         $RT::Logger->crit("$_[0]");
-        exit(-1);
-    }
-    else {
-        #Get out of here if we're in an eval
-        die $_[0];
     }
+    die $_[0];
 };
 
 # }}}
 
 }
 
-# }}}
 
+sub CheckPerlRequirements {
+    if ($^V < 5.008003) {
+        die sprintf "RT requires Perl v5.8.3 or newer.  Your current Perl is v%vd\n", $^V; 
+    }
 
-sub SystemUser {
-    return($SystemUser);
-}      
+    local ($@);
+    eval { 
+        my $x = ''; 
+        my $y = \$x;
+        require Scalar::Util; Scalar::Util::weaken($y);
+    };
+    if ($@) {
+        die <<"EOF";
 
-sub Nobody {
-    return ($Nobody);
+RT requires the Scalar::Util module be built with support for  the 'weaken'
+function. 
+
+It is sometimes the case that operating system upgrades will replace 
+a working Scalar::Util with a non-working one. If your system was working
+correctly up until now, this is likely the cause of the problem.
+
+Please reinstall Scalar::Util, being careful to let it build with your C 
+compiler. Ususally this is as simple as running the following command as
+root.
+
+    perl -MCPAN -e'install Scalar::Util'
+
+EOF
+
+    }
 }
 
 
-=head2 DropSetGIDPermissions
+=head2 InitClasses
 
-Drops setgid permissions.
+Load all modules that define base classes
 
 =cut
 
-sub DropSetGIDPermissions {
-    # Now that we got the config read in, we have the database 
-    # password and don't need to be setgid
-    # make the effective group the real group
-    $) = $(;
+sub InitClasses {
+    require RT::Tickets;
+    require RT::Transactions;
+    require RT::Users;
+    require RT::CurrentUser;
+    require RT::Templates;
+    require RT::Queues;
+    require RT::ScripActions;
+    require RT::ScripConditions;
+    require RT::Scrips;
+    require RT::Groups;
+    require RT::GroupMembers;
+    require RT::CustomFields;
+    require RT::CustomFieldValues;
+    require RT::ObjectCustomFields;
+    require RT::ObjectCustomFieldValues;
 }
 
+# }}}
 
-=head1 SYNOPSIS
+
+sub SystemUser {
+    return($SystemUser);
+}      
+
+sub Nobody {
+    return ($Nobody);
+}
 
 =head1 BUGS
 
-Please report them to rt-3.0-bugs@fsck.com, if you know what's broken and have at least some idea of what needs to be fixed.
-If you're not sure what's going on, report them rt-devel@lists.fsck.com.
+Please report them to rt-bugs@fsck.com, if you know what's broken and have at least 
+some idea of what needs to be fixed.
+
+If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
 
 =head1 SEE ALSO
 
 L<RT::StyleGuide>
 L<DBIx::SearchBuilder>
 
-
-
 =begin testing
 
-
 ok ($RT::Nobody->Name() eq 'Nobody', "Nobody is nobody");
 ok ($RT::Nobody->Name() ne 'root', "Nobody isn't named root");
 ok ($RT::SystemUser->Name() eq 'RT_System', "The system user is RT_System");
 ok ($RT::SystemUser->Name() ne 'noname', "The system user isn't noname");
 
-
 =end testing
 
 =cut
index 087a7e4..1501a12 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -128,7 +104,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -137,14 +113,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 PrincipalType
+=item PrincipalType
 
 Returns the current value of PrincipalType. 
 (In the database, PrincipalType is stored as varchar(25).)
 
 
 
-=head2 SetPrincipalType VALUE
+=item SetPrincipalType VALUE
 
 
 Set PrincipalType to VALUE. 
@@ -155,14 +131,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 PrincipalId
+=item PrincipalId
 
 Returns the current value of PrincipalId. 
 (In the database, PrincipalId is stored as int(11).)
 
 
 
-=head2 SetPrincipalId VALUE
+=item SetPrincipalId VALUE
 
 
 Set PrincipalId to VALUE. 
@@ -173,14 +149,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 RightName
+=item RightName
 
 Returns the current value of RightName. 
 (In the database, RightName is stored as varchar(25).)
 
 
 
-=head2 SetRightName VALUE
+=item SetRightName VALUE
 
 
 Set RightName to VALUE. 
@@ -191,14 +167,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ObjectType
+=item ObjectType
 
 Returns the current value of ObjectType. 
 (In the database, ObjectType is stored as varchar(25).)
 
 
 
-=head2 SetObjectType VALUE
+=item SetObjectType VALUE
 
 
 Set ObjectType to VALUE. 
@@ -209,14 +185,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ObjectId
+=item ObjectId
 
 Returns the current value of ObjectId. 
 (In the database, ObjectId is stored as int(11).)
 
 
 
-=head2 SetObjectId VALUE
+=item SetObjectId VALUE
 
 
 Set ObjectId to VALUE. 
@@ -227,14 +203,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 DelegatedBy
+=item DelegatedBy
 
 Returns the current value of DelegatedBy. 
 (In the database, DelegatedBy is stored as int(11).)
 
 
 
-=head2 SetDelegatedBy VALUE
+=item SetDelegatedBy VALUE
 
 
 Set DelegatedBy to VALUE. 
@@ -245,14 +221,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 DelegatedFrom
+=item DelegatedFrom
 
 Returns the current value of DelegatedFrom. 
 (In the database, DelegatedFrom is stored as int(11).)
 
 
 
-=head2 SetDelegatedFrom VALUE
+=item SetDelegatedFrom VALUE
 
 
 Set DelegatedFrom to VALUE. 
@@ -264,25 +240,25 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         PrincipalType => 
-               {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
+               {read => 1, write => 1, type => 'varchar(25)', default => ''},
         PrincipalId => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         RightName => 
-               {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
+               {read => 1, write => 1, type => 'varchar(25)', default => ''},
         ObjectType => 
-               {read => 1, write => 1, sql_type => 12, length => 25,  is_blob => 0,  is_numeric => 0,  type => 'varchar(25)', default => ''},
+               {read => 1, write => 1, type => 'varchar(25)', default => ''},
         ObjectId => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         DelegatedBy => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         DelegatedFrom => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
 
  }
 };
@@ -314,7 +290,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index a85d8c9..81f59c6 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::ACE item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 37dda00..81f7bdd 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# CONTRIBUTION SUBMISSION POLICY:
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-# 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 package RT::Action::Autoreply;
 require RT::Action::SendEmail;
 
@@ -52,18 +28,6 @@ use strict;
 use vars qw/@ISA/;
 @ISA = qw(RT::Action::SendEmail);
 
-=head2 Prepare
-
-Set up the relevant recipients, then call our parent.
-
-=cut
-
-
-sub Prepare {
-    my $self = shift;
-    $self->SetRecipients();
-    $self->SUPER::Prepare();
-}
 
 # {{{ sub SetRecipients
 
@@ -110,18 +74,10 @@ sub SetReturnAddress {
     }
     
     unless ($self->TemplateObj->MIMEObj->head->get('From')) {
-       if ($RT::UseFriendlyFromLine) {
-           my $friendly_name = $self->TicketObj->QueueObj->Description ||
-                   $self->TicketObj->QueueObj->Name;
-           $friendly_name =~ s/"/\\"/g;
-           $self->SetHeader( 'From',
-                       sprintf($RT::FriendlyFromLineFormat, 
-                $self->MIMEEncodeString( $friendly_name, $RT::EmailOutputEncoding ), $replyto),
-           );
-       }
-       else {
-           $self->SetHeader( 'From', $replyto );
-       }
+       my $friendly_name = $self->TicketObj->QueueObj->Description ||
+               $self->TicketObj->QueueObj->Name;
+       $friendly_name =~ s/"/\\"/g;
+       $self->SetHeader('From', "\"$friendly_name\" <$replyto>");
     }
     
     unless ($self->TemplateObj->MIMEObj->head->get('Reply-To')) {
index 166e7aa..007d299 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 =head1 NAME
 
   RT::Action::Generic - a generic baseclass for RT Actions
@@ -68,9 +44,6 @@ ok (require RT::Action::Generic);
 package RT::Action::Generic;
 
 use strict;
-use Scalar::Util;
-
-use base qw/RT::Base/;
 
 # {{{ sub new 
 sub new  {
@@ -83,35 +56,31 @@ sub new  {
 }
 # }}}
 
+# {{{ sub new 
+sub loc {
+    my $self = shift;
+    return $self->{'ScripObj'}->loc(@_);
+}
+# }}}
+
 # {{{ sub _Init 
 sub _Init  {
   my $self = shift;
-  my %args = ( Argument => undef,
-               CurrentUser => undef,
-               ScripActionObj => undef,
-               ScripObj => undef,
-               TemplateObj => undef,
-               TicketObj => undef,
-               TransactionObj => undef,
-               Type => undef,
-
-               @_ );
-
+  my %args = ( TransactionObj => undef,
+              TicketObj => undef,
+              ScripObj => undef,
+              TemplateObj => undef,
+              Argument => undef,
+              Type => undef,
+              @_ );
+  
+  
   $self->{'Argument'} = $args{'Argument'};
-  $self->CurrentUser( $args{'CurrentUser'});
-  $self->{'ScripActionObj'} = $args{'ScripActionObj'};
   $self->{'ScripObj'} = $args{'ScripObj'};
-  $self->{'TemplateObj'} = $args{'TemplateObj'};
   $self->{'TicketObj'} = $args{'TicketObj'};
   $self->{'TransactionObj'} = $args{'TransactionObj'};
+  $self->{'TemplateObj'} = $args{'TemplateObj'};
   $self->{'Type'} = $args{'Type'};
-
-  Scalar::Util::weaken($self->{'ScripActionObj'});
-  Scalar::Util::weaken($self->{'ScripObj'});
-  Scalar::Util::weaken($self->{'TemplateObj'});
-  Scalar::Util::weaken($self->{'TicketObj'});
-  Scalar::Util::weaken($self->{'TransactionObj'});
-
 }
 # }}}
 
@@ -152,13 +121,6 @@ sub ScripObj  {
 }
 # }}}
 
-# {{{ sub ScripActionObj
-sub ScripActionObj  {
-  my $self = shift;
-  return($self->{'ScripActionObj'});
-}
-# }}}
-
 # {{{ sub Type
 sub Type  {
   my $self = shift;
@@ -214,11 +176,13 @@ sub DESTROY {
 
     # We need to clean up all the references that might maybe get
     # oddly circular
-    $self->{'ScripActionObj'} = undef;
-    $self->{'ScripObj'} = undef;
     $self->{'TemplateObj'} =undef
     $self->{'TicketObj'} = undef;
     $self->{'TransactionObj'} = undef;
+    $self->{'ScripObj'} = undef;
+
+
+     
 }
 
 # }}}
index 8a7d7c9..1e4e4c0 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 package RT::Action::Notify;
 require RT::Action::SendEmail;
-use Mail::Address;
+
 use strict;
 use vars qw/@ISA/;
 @ISA = qw(RT::Action::SendEmail);
 
-
-=head2 Prepare
-
-Set up the relevant recipients, then call our parent.
-
-=cut
-
-
-sub Prepare {
-    my $self = shift;
-    $self->SetRecipients();
-    $self->SUPER::Prepare();
-}
-
 # {{{ sub SetRecipients
 
 =head2 SetRecipients
@@ -86,18 +47,10 @@ sub SetRecipients {
     my ( @To, @PseudoTo, @Cc, @Bcc );
 
 
-    if ( $arg =~ /\bOtherRecipients\b/ ) {
-        if ( $self->TransactionObj->Attachments->First ) {
-            my @cc_addresses = Mail::Address->parse($self->TransactionObj->Attachments->First->GetHeader('RT-Send-Cc'));
-            foreach my $addr (@cc_addresses) {
-                  push @Cc, $addr->address;
-            }
-            my @bcc_addresses = Mail::Address->parse($self->TransactionObj->Attachments->First->GetHeader('RT-Send-Bcc'));
-
-            foreach my $addr (@bcc_addresses) {
-                  push @Bcc, $addr->address;
-            }
-
+    if ($arg =~ /\bOtherRecipients\b/) {
+        if ($self->TransactionObj->Attachments->First) {
+            push (@Cc, $self->TransactionObj->Attachments->First->GetHeader('RT-Send-Cc'));
+            push (@Bcc, $self->TransactionObj->Attachments->First->GetHeader('RT-Send-Bcc'));
         }
     }
 
@@ -160,12 +113,12 @@ sub SetRecipients {
         @{ $self->{'Bcc'} } = @Bcc;
     }
     else {
-        @{ $self->{'To'} }  = grep ( lc $_ ne lc $creator, @To );
-        @{ $self->{'Cc'} }  = grep ( lc $_ ne lc $creator, @Cc );
-        @{ $self->{'Bcc'} } = grep ( lc $_ ne lc $creator, @Bcc );
+        @{ $self->{'To'} }  = grep ( !/^$creator$/, @To );
+        @{ $self->{'Cc'} }  = grep ( !/^$creator$/, @Cc );
+        @{ $self->{'Bcc'} } = grep ( !/^$creator$/, @Bcc );
     }
     @{ $self->{'PseudoTo'} } = @PseudoTo;
-
+    return (1);
 
 }
 
index d74b21d..210e4ab 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 package RT::Action::NotifyAsComment;
 require RT::Action::Notify;
 
index a28d88d..02ff3a5 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # This Action will resolve all members of a resolved group ticket
 
 package RT::Action::ResolveMembers;
index cfa9139..dac8fc8 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Portions Copyright 2000 Tobias Brox <tobix@cpan.org>
 
 package RT::Action::SendEmail;
@@ -57,8 +33,6 @@ use vars qw/@ISA/;
 use MIME::Words qw(encode_mimeword);
 
 use RT::EmailParser;
-use Mail::Address;
-use Date::Format qw(strftime);
 
 =head1 NAME
 
@@ -77,6 +51,13 @@ RT::Action::AutoReply is a good example subclass.
 Basically, you create another module RT::Action::YourAction which ISA
 RT::Action::SendEmail.
 
+If you want to set the recipients of the mail to something other than
+the addresses mentioned in the To, Cc, Bcc and headers in
+the template, you should subclass RT::Action::SendEmail and override
+either the SetRecipients method or the SetTo, SetCc, etc methods (see
+the comments for the SetRecipients sub).
+
+
 =begin testing
 
 ok (require RT::Action::SendEmail);
@@ -96,407 +77,236 @@ perl(1).
 
 # {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable)
 
+# {{{ sub _Init
+# We use _Init from RT::Action
+# }}}
 
 # {{{ sub Commit
-
+#Do what we need to do and send it out.
 sub Commit {
-    # DO NOT SHIFT @_ in this subroutine.  It breaks Hook::LexWrap's
-    # ability to pass @_ to a 'post' routine.
-    my $self = $_[0];
-
-    my ($ret) = $self->SendMessage( $self->TemplateObj->MIMEObj );
-    if ( $ret > 0 ) {
-        $self->RecordOutgoingMailTransaction( $self->TemplateObj->MIMEObj )
-            if ($RT::RecordOutgoingEmail);
-    }
-    return (abs $ret);
-}
-
-# }}}
-
-# {{{ sub Prepare
-
-sub Prepare {
     my $self = shift;
 
-    my ( $result, $message ) = $self->TemplateObj->Parse(
-        Argument       => $self->Argument,
-        TicketObj      => $self->TicketObj,
-        TransactionObj => $self->TransactionObj
-    );
-    if ( !$result ) {
-        return (undef);
-    }
-
     my $MIMEObj = $self->TemplateObj->MIMEObj;
+    my $msgid = $MIMEObj->head->get('Message-Id');
+    chomp $msgid;
+    $RT::Logger->info($msgid." #".$self->TicketObj->id."/".$self->TransactionObj->id." - Scrip ". $self->ScripObj->id ." ".$self->ScripObj->Description);
+    #send the email
 
-    # Header
-    $self->SetRTSpecialHeaders();
+        # Weed out any RT addresses. We really don't want to talk to ourselves!
+        @{$self->{'To'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'To'}});
+        @{$self->{'Cc'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'Cc'}});
+        @{$self->{'Bcc'}} = RT::EmailParser::CullRTAddresses("", @{$self->{'Bcc'}});
+    # If there are no recipients, don't try to send the message.
+    # If the transaction has content and has the header RT-Squelch-Replies-To
+
+    if ( defined $self->TransactionObj->Attachments->First() ) {
 
-    $self->RemoveInappropriateRecipients();
+        my $squelch = $self->TransactionObj->Attachments->First->GetHeader( 'RT-Squelch-Replies-To');
 
-    my %seen;
-    foreach my $type qw(To Cc Bcc) {
-        @{ $self->{ $type } } =
-            grep defined && length && !$seen{ lc $_ }++,
-                @{ $self->{ $type } };
+        if ($squelch) {
+            my @blacklist = split ( /,/, $squelch );
+
+            # Cycle through the people we're sending to and pull out anyone on the
+            # system blacklist
+
+            foreach my $person_to_yank (@blacklist) {
+                $person_to_yank =~ s/\s//g;
+                @{ $self->{'To'} } =
+                  grep ( !/^$person_to_yank$/, @{ $self->{'To'} } );
+                @{ $self->{'Cc'} } =
+                  grep ( !/^$person_to_yank$/, @{ $self->{'Cc'} } );
+                @{ $self->{'Bcc'} } =
+                  grep ( !/^$person_to_yank$/, @{ $self->{'Bcc'} } );
+            }
+        }
     }
 
     # Go add all the Tos, Ccs and Bccs that we need to to the message to
     # make it happy, but only if we actually have values in those arrays.
 
-    # TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
+    $self->SetHeader( 'To', join ( ',', @{ $self->{'To'} } ) )
+      if ( $self->{'To'} && @{ $self->{'To'} } );
+    $self->SetHeader( 'Cc', join ( ',', @{ $self->{'Cc'} } ) )
+      if ( $self->{'Cc'} && @{ $self->{'Cc'} } );
+    $self->SetHeader( 'Bcc', join ( ',', @{ $self->{'Bcc'} } ) )
+      if ( $self->{'Cc'} && @{ $self->{'Bcc'} } );
 
-    $self->SetHeader( 'To', join ( ', ', @{ $self->{'To'} } ) )
-      if ( ! $MIMEObj->head->get('To') &&  $self->{'To'} && @{ $self->{'To'} } );
-    $self->SetHeader( 'Cc', join ( ', ', @{ $self->{'Cc'} } ) )
-      if ( !$MIMEObj->head->get('Cc') && $self->{'Cc'} && @{ $self->{'Cc'} } );
-    $self->SetHeader( 'Bcc', join ( ', ', @{ $self->{'Bcc'} } ) )
-      if ( !$MIMEObj->head->get('Bcc') && $self->{'Bcc'} && @{ $self->{'Bcc'} } );
 
-    # PseudoTo (fake to headers) shouldn't get matched for message recipients.
-    # If we don't have any 'To' header (but do have other recipients), drop in
-    # the pseudo-to header.
-    $self->SetHeader( 'To', join ( ', ', @{ $self->{'PseudoTo'} } ) )
-      if ( $self->{'PseudoTo'} && ( @{ $self->{'PseudoTo'} } )
-        and ( !$MIMEObj->head->get('To') ) ) and ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc'));
+    $self->SetHeader('MIME-Version', '1.0');
 
-    # We should never have to set the MIME-Version header
-    $self->SetHeader( 'MIME-Version', '1.0' );
+    # try to convert message body from utf-8 to $RT::EmailOutputEncoding
+    $self->SetHeader( 'Content-Type', 'text/plain; charset="utf-8"' );
 
-    # fsck.com #5959: Since RT sends 8bit mail, we should say so.
-    $self->SetHeader( 'Content-Transfer-Encoding','8bit');
+    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' );
+    $self->SetHeader( 'Content-Type', 'text/plain; charset="' . $RT::EmailOutputEncoding . '"' );
 
-    # For security reasons, we only send out textual mails.
-    my @parts = $MIMEObj;
-    while (my $part = shift @parts) {
-        if ($part->is_multipart) {
-            push @parts, $part->parts;
-        }
-        else {
-            if ( RT::I18N::IsTextualContentType( $part->mime_type ) ) {
-                $part->head->mime_attr( "Content-Type" => $part->mime_type )
-            } else {
-                $part->head->mime_attr( "Content-Type" => 'text/plain' );
-            }
-            $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
-        }
-    }
 
+    # Build up a MIME::Entity that looks like the original message.
 
-    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' );
+    my $do_attach = $self->TemplateObj->MIMEObj->head->get('RT-Attach-Message');
 
-    # Build up a MIME::Entity that looks like the original message.
-    $self->AddAttachments() if ( $MIMEObj->head->get('RT-Attach-Message') );
+    if ($do_attach) {
+        $self->TemplateObj->MIMEObj->head->delete('RT-Attach-Message');
 
-    return $result;
+        my $attachments = RT::Attachments->new($RT::SystemUser);
+        $attachments->Limit( FIELD => 'TransactionId',
+                             VALUE => $self->TransactionObj->Id );
+        $attachments->OrderBy('id');
 
-}
+        my $transaction_content_obj = $self->TransactionObj->ContentObj;
 
-# }}}
+        # attach any of this transaction's attachments
+        while ( my $attach = $attachments->Next ) {
 
-# }}}
+            # Don't attach anything blank
+            next unless ( $attach->ContentLength );
 
+            # We want to make sure that we don't include the attachment that's being sued as the "Content" of this message"
+            next
+              if (    $transaction_content_obj
+                   && $transaction_content_obj->Id == $attach->Id 
+                   && $transaction_content_obj->ContentType =~ qr{text/plain}i
+                );
+            $MIMEObj->make_multipart('mixed');
+            $MIMEObj->attach( Type => $attach->ContentType,
+                              Charset => $attach->OriginalEncoding,
+                              Data => $attach->OriginalContent,
+                              Filename => $self->MIMEEncodeString( $attach->Filename, $RT::EmailOutputEncoding ),
+                              Encoding    => '-SUGGEST');
+        }
 
+    }
 
-=head2 To
 
-Returns an array of Mail::Address objects containing all the To: recipients for this notification
+    my $retval = $self->SendMessage($MIMEObj);
 
-=cut
 
-sub To {
-    my $self = shift;
-    return ($self->_AddressesFromHeader('To'));
+    return ($retval);
 }
 
-=head2 Cc
-
-Returns an array of Mail::Address objects containing all the Cc: recipients for this notification
+# }}}
 
-=cut
+# {{{ sub Prepare
 
-sub Cc { 
+sub Prepare {
     my $self = shift;
-    return ($self->_AddressesFromHeader('Cc'));
-}
 
-=head2 Bcc
+    # This actually populates the MIME::Entity fields in the Template Object
 
-Returns an array of Mail::Address objects containing all the Bcc: recipients for this notification
+    unless ( $self->TemplateObj ) {
+        $RT::Logger->warning("No template object handed to $self\n");
+    }
 
-=cut
+    unless ( $self->TransactionObj ) {
+        $RT::Logger->warning("No transaction object handed to $self\n");
 
+    }
 
-sub Bcc {
-    my $self = shift;
-    return ($self->_AddressesFromHeader('Bcc'));
+    unless ( $self->TicketObj ) {
+        $RT::Logger->warning("No ticket object handed to $self\n");
 
-}
+    }
 
-sub _AddressesFromHeader  {
-    my $self = shift;
-    my $field = shift;
-    my $header = $self->TemplateObj->MIMEObj->head->get($field);
-    my @addresses = Mail::Address->parse($header);
+    my ( $result, $message ) = $self->TemplateObj->Parse(
+                                         Argument       => $self->Argument,
+                                         TicketObj      => $self->TicketObj,
+                                         TransactionObj => $self->TransactionObj
+    );
+    if ($result) {
+
+        # Header
+        $self->SetSubject();
+        $self->SetSubjectToken();
+        $self->SetRecipients();
+        $self->SetReturnAddress();
+        $self->SetRTSpecialHeaders();
+        if ($RT::EmailOutputEncoding) {
+
+            # l10n related header
+            $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding );
+        }
+    }
+
+    return $result;
 
-    return (@addresses);
 }
 
+# }}}
 
-# {{{ SendMessage
+# }}}
 
+# {{{ SendMessage
 =head2 SendMessage MIMEObj
 
 sends the message using RT's preferred API.
-TODO: Break this out to a separate module
+TODO: Break this out to a seperate module
 
 =cut
 
 sub SendMessage {
-    # DO NOT SHIFT @_ in this subroutine.  It breaks Hook::LexWrap's
-    # ability to pass @_ to a 'post' routine.
-    my ( $self, $MIMEObj ) = @_;
+    my $self = shift;
+    my $MIMEObj = shift;
 
-    my $msgid = $MIMEObj->head->get('Message-ID');
-    chomp $msgid;
+    my $msgid = $MIMEObj->head->get('Message-Id');
 
-    $self->ScripActionObj->{_Message_ID}++;
-    
-    $RT::Logger->info( $msgid . " #"
-        . $self->TicketObj->id . "/"
-        . $self->TransactionObj->id
-        . " - Scrip "
-        . $self->ScripObj->id . " "
-        . $self->ScripObj->Description );
 
     #If we don't have any recipients to send to, don't send a message;
-    unless ( $MIMEObj->head->get('To')
-        || $MIMEObj->head->get('Cc')
-        || $MIMEObj->head->get('Bcc') )
-    {
-        $RT::Logger->info( $msgid . " No recipients found. Not sending.\n" );
-        return (-1);
-    }
-
-    unless ($MIMEObj->head->get('Date')) {
-        # We coerce localtime into an array since strftime has a flawed prototype that only accepts
-        # a list
-      $MIMEObj->head->replace(Date => strftime('%a, %d %b %Y %H:%M:%S %z', @{[localtime()]}));
-    }
-
-    return (0) unless ($self->OutputMIMEObject($MIMEObj));
-
-    my $success = $msgid . " sent ";
-    foreach( qw(To Cc Bcc) ) {
-        my $recipients = $MIMEObj->head->get($_);
-        $success .= " $_: ". $recipients if $recipients;
-    }
-    $success =~ s/\n//g;
-
-    $RT::Logger->info($success);
-
-    return (1);
-}
-
-
-=head2 OutputMIMEObject MIME::Entity
-
-Sends C<MIME::Entity> as an email message according to RT's mailer configuration.
-
-=cut 
-
-
-
-sub OutputMIMEObject {
-    my $self = shift;
-    my $MIMEObj = shift;
-    
-    my $msgid = $MIMEObj->head->get('Message-ID');
-    chomp $msgid;
-    
-    my $SendmailArguments = $RT::SendmailArguments;
-    if (defined $RT::VERPPrefix && defined $RT::VERPDomain) {
-      my $EnvelopeFrom = $self->TransactionObj->CreatorObj->EmailAddress;
-      $EnvelopeFrom =~ s/@/=/g;
-      $EnvelopeFrom =~ s/\s//g;
-      $SendmailArguments .= " -f ${RT::VERPPrefix}${EnvelopeFrom}\@${RT::VERPDomain}";
+    unless (    $MIMEObj->head->get('To')
+             || $MIMEObj->head->get('Cc')
+             || $MIMEObj->head->get('Bcc') ) {
+        $RT::Logger->info($msgid.  " No recipients found. Not sending.\n");
+        return (1);
     }
 
+    # PseudoTo (fake to headers) shouldn't get matched for message recipients.
+    # If we don't have any 'To' header, drop in the pseudo-to header.
 
+    $self->SetHeader( 'To', join ( ',', @{ $self->{'PseudoTo'} } ) )
+      if ( $self->{'PseudoTo'} && ( @{ $self->{'PseudoTo'} } )
+           and ( !$MIMEObj->head->get('To') ) );
     if ( $RT::MailCommand eq 'sendmailpipe' ) {
         eval {
-            # don't ignore CHLD signal to get proper exit code
-            local $SIG{'CHLD'} = 'DEFAULT';
-
-            my $mail;
-            unless( open $mail, "|$RT::SendmailPath $SendmailArguments" ) {
-                die "Couldn't run $RT::SendmailPath: $!";
-            }
-
-            # if something wrong with $mail->print we will get PIPE signal, handle it
-            local $SIG{'PIPE'} = sub { die "$RT::SendmailPath closed pipe" };
-            $MIMEObj->print($mail);
-
-            unless ( close $mail ) {
-                die "Close failed: $!" if $!; # system error
-                # sendmail exit statuses mostly errors with data not software
-                # TODO: status parsing: core dump, exit on signal or EX_*
-                $RT::Logger->warning( "$RT::SendmailPath exitted with status $?" );
-            }
-        };
-        if ($@) {
-            $RT::Logger->crit( $msgid . "Could not send mail: " . $@ );
-            return 0;
+            open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" );
+            print MAIL $MIMEObj->as_string;
+            close(MAIL);
+          };
+          if ($@) {
+            $RT::Logger->crit($msgid.  "Could not send mail. -".$@ );
         }
     }
     else {
-        my @mailer_args = ($RT::MailCommand);
-
-        local $ENV{MAILADDRESS};
+       my @mailer_args = ($RT::MailCommand);
+       local $ENV{MAILADDRESS};
 
         if ( $RT::MailCommand eq 'sendmail' ) {
-            push @mailer_args, split(/\s+/, $SendmailArguments);
+           push @mailer_args, $RT::SendmailArguments;
         }
         elsif ( $RT::MailCommand eq 'smtp' ) {
-            $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From');
-            push @mailer_args, ( Server => $RT::SMTPServer );
-            push @mailer_args, ( Debug  => $RT::SMTPDebug );
-        }
-        else {
-            push @mailer_args, $RT::MailParams;
+           $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From');
+           push @mailer_args, (Server => $RT::SMTPServer);
+           push @mailer_args, (Debug => $RT::SMTPDebug);
         }
+       else {
+           push @mailer_args, $RT::MailParams;
+       }
 
-        unless ( $MIMEObj->send(@mailer_args) ) {
-            $RT::Logger->crit( $msgid . "Could not send mail." );
+        unless ( $MIMEObj->send( @mailer_args ) ) {
+            $RT::Logger->crit($msgid.  "Could not send mail." );
             return (0);
         }
     }
-    return 1;
-}
 
-# }}}
-
-# {{{ AddAttachments 
-
-=head2 AddAttachments
-
-Takes any attachments to this transaction and attaches them to the message
-we're building.
-
-=cut
-
-
-sub AddAttachments {
-    my $self = shift;
 
-    my $MIMEObj = $self->TemplateObj->MIMEObj;
-
-    $MIMEObj->head->delete('RT-Attach-Message');
-
-    my $attachments = RT::Attachments->new($RT::SystemUser);
-    $attachments->Limit(
-        FIELD => 'TransactionId',
-        VALUE => $self->TransactionObj->Id
-    );
-    $attachments->OrderBy( FIELD => 'id');
-
-    my $transaction_content_obj = $self->TransactionObj->ContentObj;
-
-    # attach any of this transaction's attachments
-    while ( my $attach = $attachments->Next ) {
-
-        # Don't attach anything blank
-        next unless ( $attach->ContentLength );
-
-# We want to make sure that we don't include the attachment that's being used as the "Content" of this message.
-        next
-          if ( $transaction_content_obj
-            && $transaction_content_obj->Id == $attach->Id
-            && $transaction_content_obj->ContentType =~ qr{text/plain}i );
-        $MIMEObj->make_multipart('mixed');
-        $MIMEObj->attach(
-            Type     => $attach->ContentType,
-            Charset  => $attach->OriginalEncoding,
-            Data     => $attach->OriginalContent,
-            Filename => $self->MIMEEncodeString( $attach->Filename,
-                $RT::EmailOutputEncoding ),
-            'RT-Attachment:' => $self->TicketObj->Id."/".$self->TransactionObj->Id."/".$attach->id,
-            Encoding => '-SUGGEST'
-        );
-    }
+     my $success = ($msgid. " sent To: ".$MIMEObj->head->get('To') . " Cc: ".$MIMEObj->head->get('Cc') . " Bcc: ".$MIMEObj->head->get('Bcc'));
+    $success =~ s/\n//gi;
+    $RT::Logger->info($success);
 
+    return (1);
 }
 
 # }}}
 
-# {{{ RecordOutgoingMailTransaction
-
-=head2 RecordOutgoingMailTransaction MIMEObj
-
-Record a transaction in RT with this outgoing message for future record-keeping purposes
-
-=cut
-
-
-
-sub RecordOutgoingMailTransaction {
-    my $self = shift;
-    my $MIMEObj = shift;
-           
-
-    my @parts = $MIMEObj->parts;
-    my @attachments;
-    my @keep;
-    foreach my $part (@parts) {
-        my $attach = $part->head->get('RT-Attachment');
-        if ($attach) {
-            $RT::Logger->debug("We found an attachment. we want to not record it.");
-            push @attachments, $attach;
-        } else {
-            $RT::Logger->debug("We found a part. we want to record it.");
-            push @keep, $part;
-        }
-    }
-    $MIMEObj->parts(\@keep);
-    foreach my $attachment (@attachments) {
-        $MIMEObj->head->add('RT-Attachment', $attachment);
-    }
-
-    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' );
-
-    my $transaction = RT::Transaction->new($self->TransactionObj->CurrentUser);
-
-    # XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
-
-    my $type;
-    if ($self->TransactionObj->Type eq 'Comment') {
-        $type = 'CommentEmailRecord';
-    } else {
-        $type = 'EmailRecord';
-    }
-
-    my $msgid = $MIMEObj->head->get('Message-ID');
-    chomp $msgid;
-
-    my ( $id, $msg ) = $transaction->Create(
-        Ticket         => $self->TicketObj->Id,
-        Type           => $type,
-        Data           => $msgid,
-        MIMEObj        => $MIMEObj,
-        ActivateScrips => 0
-    );
-
-    if( $id ) {
-       $self->{'OutgoingMailTransaction'} = $id;
-    } else {
-        $RT::Logger->warning( "Could not record outgoing message transaction: $msg" );
-    }
-    return $id;
-}
-
-# }}}
-#
+# {{{ Deal with message headers (Set* subs, designed for  easy overriding)
 
 # {{{ sub SetRTSpecialHeaders
 
@@ -510,161 +320,84 @@ that don't matter much to anybody else.
 sub SetRTSpecialHeaders {
     my $self = shift;
 
-    $self->SetSubject();
-    $self->SetSubjectToken();
-    $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding )
-      if ($RT::EmailOutputEncoding);
-    $self->SetReturnAddress();
-    $self->SetReferencesHeaders();
-
-    unless ($self->TemplateObj->MIMEObj->head->get('Message-ID')) {
-      # Get Message-ID for this txn
-      my $msgid = "";
-      $msgid = $self->TransactionObj->Message->First->GetHeader("RT-Message-ID")
-        || $self->TransactionObj->Message->First->GetHeader("Message-ID")
-        if $self->TransactionObj->Message && $self->TransactionObj->Message->First;
-
-      # If there is one, and we can parse it, then base our Message-ID on it
-      if ($msgid 
-          and $msgid =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\Q$RT::Organization\E>$/
-                         "<$1." . $self->TicketObj->id
-                          . "-" . $self->ScripObj->id
-                          . "-" . $self->ScripActionObj->{_Message_ID}
-                          . "@" . $RT::Organization . ">"/eg
-          and $2 == $self->TicketObj->id) {
-        $self->SetHeader( "Message-ID" => $msgid );
-      } else {
-        $self->SetHeader( 'Message-ID',
-            "<rt-"
-            . $RT::VERSION . "-"
-            . $$ . "-"
-            . CORE::time() . "-"
-            . int(rand(2000)) . '.'
-            . $self->TicketObj->id . "-"
-            . $self->ScripObj->id . "-"  # Scrip
-            . $self->ScripActionObj->{_Message_ID} . "@"  # Email sent
-            . $RT::Organization
-            . ">" );
-      }
-    }
+    $self->SetReferences();
+
+    $self->SetMessageID();
 
-    $self->SetHeader( 'Precedence', "bulk" )
-      unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") );
+    $self->SetPrecedence();
 
     $self->SetHeader( 'X-RT-Loop-Prevention', $RT::rtname );
     $self->SetHeader( 'RT-Ticket',
-        $RT::rtname . " #" . $self->TicketObj->id() );
+                      $RT::rtname . " #" . $self->TicketObj->id() );
     $self->SetHeader( 'Managed-by',
-        "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
+                      "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
 
     $self->SetHeader( 'RT-Originator',
-        $self->TransactionObj->CreatorObj->EmailAddress );
+                      $self->TransactionObj->CreatorObj->EmailAddress );
+    return ();
 
 }
 
-# }}}
-
-
-# }}}
-
-# {{{ RemoveInappropriateRecipients
+# {{{ sub SetReferences
 
-=head2 RemoveInappropriateRecipients
-
-Remove addresses that are RT addresses or that are on this transaction's blacklist
+=head2 SetReferences 
+  
+  # This routine will set the References: and In-Reply-To headers,
+# autopopulating it with all the correspondence on this ticket so
+# far. This should make RT responses threadable.
 
 =cut
 
-sub RemoveInappropriateRecipients {
+sub SetReferences {
     my $self = shift;
 
-    my $msgid = $self->TemplateObj->MIMEObj->head->get  ('Message-Id');
-
-
-
-    my @blacklist;
+    # TODO: this one is broken.  What is this email really a reply to?
+    # If it's a reply to an incoming message, we'll need to use the
+    # actual message-id from the appropriate Attachment object.  For
+    # incoming mails, we would like to preserve the In-Reply-To and/or
+    # References.
 
-    my @types = qw/To Cc Bcc/;
+    $self->SetHeader( 'In-Reply-To',
+                   "<rt-" . $self->TicketObj->id() . "\@" . $RT::rtname . ">" );
 
-    # Weed out any RT addresses. We really don't want to talk to ourselves!
-    foreach my $type (@types) {
-        @{ $self->{$type} } =
-          RT::EmailParser::CullRTAddresses( "", @{ $self->{$type} } );
-    }
-
-    # If there are no recipients, don't try to send the message.
-    # If the transaction has content and has the header RT-Squelch-Replies-To
-
-    if ( $self->TransactionObj->Attachments->First() ) {
-        if (
-            $self->TransactionObj->Attachments->First->GetHeader(
-                'RT-DetectedAutoGenerated')
-          )
-        {
-
-            # What do we want to do with this? It's probably (?) a bounce
-            # caused by one of the watcher addresses being broken.
-            # Default ("true") is to redistribute, for historical reasons.
-
-            if ( !$RT::RedistributeAutoGeneratedMessages ) {
-
-                # Don't send to any watchers.
-                @{ $self->{'To'} }  = ();
-                @{ $self->{'Cc'} }  = ();
-                @{ $self->{'Bcc'} } = ();
-
-                $RT::Logger->info( $msgid . " The incoming message was autogenerated. Not redistributing this message based on site configuration.\n");
-            }
-            elsif ( $RT::RedistributeAutoGeneratedMessages eq 'privileged' ) {
-
-                # Only send to "privileged" watchers.
-                #
-
-                foreach my $type (@types) {
-
-                    foreach my $addr ( @{ $self->{$type} } ) {
-                        my $user = RT::User->new($RT::SystemUser);
-                        $user->LoadByEmail($addr);
-                        @{ $self->{$type} } =
-                          grep ( !/^\Q$addr\E$/, @{ $self->{$type} } )
-                          if ( !$user->Privileged );
+    # TODO We should always add References headers for all message-ids
+    # of previous messages related to this ticket.
+}
 
-                    }
-                }
-                $RT::Logger->info( $msgid . " The incoming message was autogenerated. Not redistributing this message to unprivileged users based on site configuration.\n");
+# }}}
 
-            }
+# {{{ sub SetMessageID
 
-        }
+=head2 SetMessageID 
 
-        my $squelch =
-          $self->TransactionObj->Attachments->First->GetHeader(
-            'RT-Squelch-Replies-To');
+Without this one, threading won't work very nice in email agents.
+Anyway, I'm not really sure it's that healthy if we need to send
+several separate/different emails about the same transaction.
 
-        if ($squelch) {
-            @blacklist = split( /,/, $squelch );
-        }
-    }
-
-    # Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
-    my @non_recipients = $self->TicketObj->SquelchMailTo;
-    foreach my $attribute (@non_recipients) {
-        push @blacklist, $attribute->Content;
-    }
+=cut
 
-    # Cycle through the people we're sending to and pull out anyone on the
-    # system blacklist
+sub SetMessageID {
+    my $self = shift;
 
-    foreach my $person_to_yank (@blacklist) {
-        $person_to_yank =~ s/\s//g;
-        foreach my $type (@types) {
-            @{ $self->{$type} } =
-              grep ( !/^\Q$person_to_yank\E$/, @{ $self->{$type} } );
-        }
-    }
+    # TODO this one might be sort of broken.  If we have several scrips +++
+    # sending several emails to several different persons, we need to
+    # pull out different message-ids.  I'd suggest message ids like
+    # "rt-ticket#-transaction#-scrip#-receipient#"
+
+    $self->SetHeader( 'Message-ID',
+                      "<rt-"
+                        . $RT::VERSION ."-"
+                        . $self->TicketObj->id() . "-"
+                        . $self->TransactionObj->id() . "."
+                        . rand(20) . "\@"
+                        . $RT::Organization . ">" )
+      unless $self->TemplateObj->MIMEObj->head->get('Message-ID');
 }
 
 # }}}
+
+# }}}
+
 # {{{ sub SetReturnAddress
 
 =head2 SetReturnAddress is_comment => BOOLEAN
@@ -676,10 +409,8 @@ Calculate and set From and Reply-To headers based on the is_comment flag.
 sub SetReturnAddress {
 
     my $self = shift;
-    my %args = (
-        is_comment => 0,
-        @_
-    );
+    my %args = ( is_comment => 0,
+                 @_ );
 
     # From and Reply-To
     # $args{is_comment} should be set if the comment address is to be used.
@@ -695,27 +426,21 @@ sub SetReturnAddress {
     }
 
     unless ( $self->TemplateObj->MIMEObj->head->get('From') ) {
-        if ($RT::UseFriendlyFromLine) {
-            my $friendly_name = $self->TransactionObj->CreatorObj->RealName
-                || $self->TransactionObj->CreatorObj->Name;
-            if ( $friendly_name =~ /^"(.*)"$/ ) {    # a quoted string
-                $friendly_name = $1;
-            }
-
-            $friendly_name =~ s/"/\\"/g;
-            $self->SetHeader(
-                'From',
-                sprintf(
-                    $RT::FriendlyFromLineFormat,
-                    $self->MIMEEncodeString( $friendly_name,
-                        $RT::EmailOutputEncoding ),
-                    $replyto
-                ),
-            );
-        }
-        else {
-            $self->SetHeader( 'From', $replyto );
-        }
+       if ($RT::UseFriendlyFromLine) {
+           my $friendly_name = $self->TransactionObj->CreatorObj->RealName;
+           if ( $friendly_name =~ /^"(.*)"$/ ) {    # a quoted string
+               $friendly_name = $1;
+           }
+
+           $friendly_name =~ s/"/\\"/g;
+           $self->SetHeader( 'From',
+                       sprintf($RT::FriendlyFromLineFormat, 
+                $self->MIMEEncodeString( $friendly_name, $RT::EmailOutputEncoding ), $replyto),
+           );
+       }
+       else {
+           $self->SetHeader( 'From', $replyto );
+       }
     }
 
     unless ( $self->TemplateObj->MIMEObj->head->get('Reply-To') ) {
@@ -748,166 +473,155 @@ sub SetHeader {
 
 # }}}
 
+# {{{ sub SetRecipients
 
-# {{{ sub SetSubject
+=head2 SetRecipients
 
-=head2 SetSubject
-
-This routine sets the subject. it does not add the rt tag. that gets done elsewhere
-If $self->{'Subject'} is already defined, it uses that. otherwise, it tries to get
-the transaction's subject.
+Dummy method to be overriden by subclasses which want to set the recipients.
 
-=cut 
+=cut
 
-sub SetSubject {
+sub SetRecipients {
     my $self = shift;
-    my $subject;
+    return ();
+}
 
-    my $message = $self->TransactionObj->Attachments;
-    if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
-        return ();
-    }
-    if ( $self->{'Subject'} ) {
-        $subject = $self->{'Subject'};
-    }
-    elsif ( ( $message->First() ) && ( $message->First->Headers ) ) {
-        my $header = $message->First->Headers();
-        $header =~ s/\n\s+/ /g;
-        if ( $header =~ /^Subject: (.*?)$/m ) {
-            $subject = $1;
-        }
-        else {
-            $subject = $self->TicketObj->Subject();
-        }
+# }}}
 
-    }
-    else {
-        $subject = $self->TicketObj->Subject();
-    }
+# {{{ sub SetTo
 
-    $subject =~ s/(\r\n|\n|\s)/ /gi;
+=head2 SetTo
 
-    chomp $subject;
-    $self->SetHeader( 'Subject', $subject );
+Takes a string that is the addresses you want to send mail to
+
+=cut
 
+sub SetTo {
+    my $self      = shift;
+    my $addresses = shift;
+    return $self->SetHeader( 'To', $addresses );
 }
 
 # }}}
 
-# {{{ sub SetSubjectToken
+# {{{ sub SetCc
 
-=head2 SetSubjectToken
+=head2 SetCc
 
-This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
+Takes a string that is the addresses you want to Cc
 
 =cut
 
-sub SetSubjectToken {
-    my $self = shift;
-    my $sub  = $self->TemplateObj->MIMEObj->head->get('Subject');
-    my $id   = $self->TicketObj->id;
-
-    my $token_re = $RT::EmailSubjectTagRegex;
-    $token_re = qr/\Q$RT::rtname\E/o unless $token_re;
-    return if $sub =~ /\[$token_re\s+#$id\]/;
+sub SetCc {
+    my $self      = shift;
+    my $addresses = shift;
 
-    $sub =~ s/(\r\n|\n|\s)/ /gi;
-    chomp $sub;
-    $self->TemplateObj->MIMEObj->head->replace(
-        Subject => "[$RT::rtname #$id] $sub",
-    );
+    return $self->SetHeader( 'Cc', $addresses );
 }
 
 # }}}
 
-=head2 SetReferencesHeaders
+# {{{ sub SetBcc
+
+=head2 SetBcc
 
-Set References and In-Reply-To headers for this message.
+Takes a string that is the addresses you want to Bcc
 
 =cut
 
-sub SetReferencesHeaders {
+sub SetBcc {
+    my $self      = shift;
+    my $addresses = shift;
 
-    my $self = shift;
-    my ( @in_reply_to, @references, @msgid );
+    return $self->SetHeader( 'Bcc', $addresses );
+}
 
-    my $attachments = $self->TransactionObj->Message;
+# }}}
 
-    if ( my $top = $attachments->First() ) {
-        @in_reply_to = split(/\s+/m, $top->GetHeader('In-Reply-To') || '');  
-        @references = split(/\s+/m, $top->GetHeader('References') || '' );  
-        @msgid = split(/\s+/m, $top->GetHeader('Message-ID') || ''); 
-    }
-    else {
-        return (undef);
-    }
+# {{{ sub SetPrecedence
 
-    # There are two main cases -- this transaction was created with
-    # the RT Web UI, and hence we want to *not* append its Message-ID
-    # to the References and In-Reply-To.  OR it came from an outside
-    # source, and we should treat it as per the RFC
-    if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>/) {
-
-      # Make all references which are internal be to version which we
-      # have sent out
-      for (@references, @in_reply_to) {
-        s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>$/
-          "<$1." . $self->TicketObj->id .
-             "-" . $self->ScripObj->id .
-             "-" . $self->ScripActionObj->{_Message_ID} .
-             "@" . $RT::Organization . ">"/eg
-      }
-
-      # In reply to whatever the internal message was in reply to
-      $self->SetHeader( 'In-Reply-To', join( " ",  ( @in_reply_to )));
-
-      # Default the references to whatever we're in reply to
-      @references = @in_reply_to unless @references;
-
-      # References are unchanged from internal
-    } else {
-      # In reply to that message
-      $self->SetHeader( 'In-Reply-To', join( " ",  ( @msgid )));
-
-      # Default the references to whatever we're in reply to
-      @references = @in_reply_to unless @references;
-
-      # Push that message onto the end of the references
-      push @references, @msgid;
+sub SetPrecedence {
+    my $self = shift;
+
+    unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") ) {
+        $self->SetHeader( 'Precedence', "bulk" );
     }
+}
+
+# }}}
+
+# {{{ sub SetSubject
+
+=head2 SetSubject
+
+This routine sets the subject. it does not add the rt tag. that gets done elsewhere
+If $self->{'Subject'} is already defined, it uses that. otherwise, it tries to get
+the transaction's subject.
+
+=cut 
 
-    # Push pseudo-ref to the front
-    my $pseudo_ref = $self->PseudoReference;
-    @references = ($pseudo_ref, grep { $_ ne $pseudo_ref } @references);
+sub SetSubject {
+    my $self = shift;
+    my $subject;
+
+    unless ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
+        my $message = $self->TransactionObj->Attachments;
+        my $ticket  = $self->TicketObj->Id;
+
+        if ( $self->{'Subject'} ) {
+            $subject = $self->{'Subject'};
+        }
+        elsif (    ( $message->First() )
+                && ( $message->First->Headers ) ) {
+            my $header = $message->First->Headers();
+            $header =~ s/\n\s+/ /g;
+            if ( $header =~ /^Subject: (.*?)$/m ) {
+                $subject = $1;
+            }
+            else {
+                $subject = $self->TicketObj->Subject();
+            }
+
+        }
+        else {
+            $subject = $self->TicketObj->Subject();
+        }
 
-    # If there are more than 10 references headers, remove all but the
-    # first four and the last six (Gotta keep this from growing
-    # forever)
-    splice(@references, 4, -6) if ($#references >= 10);
+        $subject =~ s/(\r\n|\n|\s)/ /gi;
 
-    # Add on the references
-    $self->SetHeader( 'References', join( " ",   @references) );
-    $self->TemplateObj->MIMEObj->head->fold_length( 'References', 80 );
+        chomp $subject;
+        $self->SetHeader( 'Subject', $subject );
 
+    }
+    return ($subject);
 }
 
 # }}}
 
-=head2 PseudoReference
+# {{{ sub SetSubjectToken
 
-Returns a fake Message-ID: header for the ticket to allow a base level of threading
+=head2 SetSubjectToken
 
-=cut
+This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
 
-sub PseudoReference {
+=cut
 
+sub SetSubjectToken {
     my $self = shift;
-    my $pseudo_ref =  '<RT-Ticket-'.$self->TicketObj->id .'@'.$RT::Organization .'>';
-    return $pseudo_ref;
+    my $tag  = "[$RT::rtname #" . $self->TicketObj->id . "]";
+    my $sub  = $self->TemplateObj->MIMEObj->head->get('Subject');
+    unless ( $sub =~ /\Q$tag\E/ ) {
+        $sub =~ s/(\r\n|\n|\s)/ /gi;
+        chomp $sub;
+        $self->TemplateObj->MIMEObj->head->replace( 'Subject', "$tag $sub" );
+    }
 }
 
+# }}}
 
-# {{{ SetHeadingAsEncoding
+# }}}
+
+# {{{
 
 =head2 SetHeaderAsEncoding($field_name, $charset_encoding)
 
@@ -926,6 +640,10 @@ sub SetHeaderAsEncoding {
 
     my $value = $self->TemplateObj->MIMEObj->head->get($field);
 
+    # don't bother if it's us-ascii
+
+    # See RT::I18N, 'NOTES:  Why Encode::_utf8_off before Encode::from_to'
+
     $value =  $self->MIMEEncodeString($value, $enc);
 
     $self->TemplateObj->MIMEObj->head->replace( $field, $value );
@@ -934,7 +652,7 @@ sub SetHeaderAsEncoding {
 } 
 # }}}
 
-# {{{ MIMEEncodeString
+# {{{ MIMENcodeString
 
 =head2 MIMEEncodeString STRING ENCODING
 
@@ -945,52 +663,15 @@ Takes a string and a possible encoding and returns the string wrapped in MIME go
 sub MIMEEncodeString {
     my  $self = shift;
     my $value = shift;
-    # using RFC2047 notation, sec 2.
-    # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
-    my $charset = shift;
-    my $encoding = 'B';
-    # An 'encoded-word' may not be more than 75 characters long
-    #
-    # MIME encoding increases 4/3*(number of bytes), and always in multiples
-    # of 4. Thus we have to find the best available value of bytes available
-    # for each chunk.
-    #
-    # First we get the integer max which max*4/3 would fit on space.
-    # Then we find the greater multiple of 3 lower or equal than $max.
-    my $max = int(((75-length('=?'.$charset.'?'.$encoding.'?'.'?='))*3)/4);
-    $max = int($max/3)*3;
+    my $enc = shift;
 
     chomp $value;
-
-    if ( $max <= 0 ) {
-      # gives an error...
-      $RT::Logger->crit("Can't encode! Charset or encoding too big.\n");
-      return ($value);
-    }
-
     return ($value) unless $value =~ /[^\x20-\x7e]/;
 
     $value =~ s/\s*$//;
-
-    # we need perl string to split thing char by char
-    Encode::_utf8_on($value) unless Encode::is_utf8( $value );
-
-    my ($tmp, @chunks) = ('', ());
-    while ( length $value ) {
-        my $char = substr($value, 0, 1, '');
-        my $octets = Encode::encode( $charset, $char );
-        if ( length($tmp) + length($octets) > $max ) {
-            push @chunks, $tmp;
-            $tmp = '';
-        }
-        $tmp .= $octets;
-    }
-    push @chunks, $tmp if length $tmp;
-
-    # encode an join chuncks
-    $value = join "\n ",
-               map encode_mimeword( $_, $encoding, $charset ), @chunks ;
-    return($value); 
+    Encode::_utf8_off($value);
+    my $res = Encode::from_to( $value, "utf-8", $enc );
+    $value = encode_mimeword( $value,  'B', $enc );
 }
 
 # }}}
index ac1fcfe..2ed5201 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -134,7 +110,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -143,14 +119,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 TransactionId
+=item TransactionId
 
 Returns the current value of TransactionId. 
 (In the database, TransactionId is stored as int(11).)
 
 
 
-=head2 SetTransactionId VALUE
+=item SetTransactionId VALUE
 
 
 Set TransactionId to VALUE. 
@@ -161,14 +137,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Parent
+=item Parent
 
 Returns the current value of Parent. 
 (In the database, Parent is stored as int(11).)
 
 
 
-=head2 SetParent VALUE
+=item SetParent VALUE
 
 
 Set Parent to VALUE. 
@@ -179,14 +155,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 MessageId
+=item MessageId
 
 Returns the current value of MessageId. 
 (In the database, MessageId is stored as varchar(160).)
 
 
 
-=head2 SetMessageId VALUE
+=item SetMessageId VALUE
 
 
 Set MessageId to VALUE. 
@@ -197,14 +173,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Subject
+=item Subject
 
 Returns the current value of Subject. 
 (In the database, Subject is stored as varchar(255).)
 
 
 
-=head2 SetSubject VALUE
+=item SetSubject VALUE
 
 
 Set Subject to VALUE. 
@@ -215,14 +191,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Filename
+=item Filename
 
 Returns the current value of Filename. 
 (In the database, Filename is stored as varchar(255).)
 
 
 
-=head2 SetFilename VALUE
+=item SetFilename VALUE
 
 
 Set Filename to VALUE. 
@@ -233,14 +209,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ContentType
+=item ContentType
 
 Returns the current value of ContentType. 
 (In the database, ContentType is stored as varchar(80).)
 
 
 
-=head2 SetContentType VALUE
+=item SetContentType VALUE
 
 
 Set ContentType to VALUE. 
@@ -251,14 +227,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ContentEncoding
+=item ContentEncoding
 
 Returns the current value of ContentEncoding. 
 (In the database, ContentEncoding is stored as varchar(80).)
 
 
 
-=head2 SetContentEncoding VALUE
+=item SetContentEncoding VALUE
 
 
 Set ContentEncoding to VALUE. 
@@ -269,14 +245,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Content
+=item Content
 
 Returns the current value of Content. 
 (In the database, Content is stored as longtext.)
 
 
 
-=head2 SetContent VALUE
+=item SetContent VALUE
 
 
 Set Content to VALUE. 
@@ -287,14 +263,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Headers
+=item Headers
 
 Returns the current value of Headers. 
 (In the database, Headers is stored as longtext.)
 
 
 
-=head2 SetHeaders VALUE
+=item SetHeaders VALUE
 
 
 Set Headers to VALUE. 
@@ -305,7 +281,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -314,7 +290,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -324,33 +300,33 @@ Returns the current value of Created.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         TransactionId => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Parent => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         MessageId => 
-               {read => 1, write => 1, sql_type => 12, length => 160,  is_blob => 0,  is_numeric => 0,  type => 'varchar(160)', default => ''},
+               {read => 1, write => 1, type => 'varchar(160)', default => ''},
         Subject => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         Filename => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         ContentType => 
-               {read => 1, write => 1, sql_type => 12, length => 80,  is_blob => 0,  is_numeric => 0,  type => 'varchar(80)', default => ''},
+               {read => 1, write => 1, type => 'varchar(80)', default => ''},
         ContentEncoding => 
-               {read => 1, write => 1, sql_type => 12, length => 80,  is_blob => 0,  is_numeric => 0,  type => 'varchar(80)', default => ''},
+               {read => 1, write => 1, type => 'varchar(80)', default => ''},
         Content => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'longtext', default => ''},
+               {read => 1, write => 1, type => 'longtext', default => ''},
         Headers => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'longtext', default => ''},
+               {read => 1, write => 1, type => 'longtext', default => ''},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
 
  }
 };
@@ -382,7 +358,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 5d90582..177cdd0 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::Attachment item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 5251537..4519fcf 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
  
 
 package RT::Condition::AnyTransaction;
index 82248e2..bd26931 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 =head1 NAME
 
   RT::Condition::Generic - ;
@@ -52,7 +28,7 @@
 =head1 SYNOPSIS
 
     use RT::Condition::Generic;
-    my $foo = RT::Condition::Generic->new
+    my $foo = new RT::Condition::IsApplicable
                TransactionObj => $tr, 
                TicketObj => $ti, 
                ScripObj => $scr, 
@@ -81,8 +57,10 @@ ok (require RT::Condition::Generic);
 
 package RT::Condition::Generic;
 
+use RT::Base;
 use strict;
-use base qw/RT::Base/;
+use vars qw/@ISA/;
+@ISA = qw(RT::Base);
 
 # {{{ sub new 
 sub new  {
@@ -104,7 +82,6 @@ sub _Init  {
               TemplateObj => undef,
               Argument => undef,
               ApplicableTransTypes => undef,
-           CurrentUser => undef,
               @_ );
   
   $self->{'Argument'} = $args{'Argument'};
@@ -112,7 +89,6 @@ sub _Init  {
   $self->{'TicketObj'} = $args{'TicketObj'};
   $self->{'TransactionObj'} = $args{'TransactionObj'};
   $self->{'ApplicableTransTypes'} = $args{'ApplicableTransTypes'};
-  $self->CurrentUser($args{'CurrentUser'});
 }
 # }}}
 
index b18996d..8afabcd 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
  
 
 
index 9d406cc..4ca2f98 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 =head1 NAME
 
   RT::CurrentUser - an RT object representing the current user
@@ -75,7 +51,8 @@ use RT::Record;
 use RT::I18N;
 
 use strict;
-use base qw/RT::Record/;
+use vars qw/@ISA/;
+@ISA= qw(RT::Record);
 
 # {{{ sub _Init 
 
@@ -83,30 +60,17 @@ use base qw/RT::Record/;
 # to be a CurrentUser object. but that's hard to do when we're trying to load
 # the CurrentUser object
 
-sub _Init {
-    my $self = shift;
-    my $User = shift;
-
-    $self->{'table'} = "Users";
-
-    if ( defined($User) ) {
-
-        if (   UNIVERSAL::isa( $User, 'RT::User' )
-            || UNIVERSAL::isa( $User, 'RT::CurrentUser' ) )
-        {
-            $self->Load( $User->id );
+sub _Init  {
+  my $self = shift;
+  my $Name = shift;
 
-        }
-        elsif ( ref($User) ) {
-            $RT::Logger->crit(
-                "RT::CurrentUser->new() called with a bogus argument: $User");
-        }
-        else {
-            $self->Load($User);
-        }
-    }
+  $self->{'table'} = "Users";
 
-    $self->_BuildTableAttributes();
+  if (defined($Name)) {
+    $self->Load($Name);
+  }
+  
+  $self->CurrentUser($self);
 
 }
 # }}}
@@ -140,13 +104,15 @@ sub Delete {
 sub UserObj {
     my $self = shift;
     
+    unless ($self->{'UserObj'}) {
        use RT::User;
-       my $user = RT::User->new($self);
-
-       unless ($user->Load($self->Id)) {
+       $self->{'UserObj'} = RT::User->new($self);
+       unless ($self->{'UserObj'}->Load($self->Id)) {
            $RT::Logger->err($self->loc("Couldn't load [_1] from the users database.\n", $self->Id));
        }
-    return ($user);
+       
+    }
+    return ($self->{'UserObj'});
 }
 # }}}
 
@@ -187,18 +153,18 @@ sub PrincipalId {
 
 
 # {{{ sub _Accessible 
-
-
- sub _CoreAccessible  {
-     {
-         Name           => { 'read' => 1 },
-           Gecos        => { 'read' => 1 },
-           RealName     => { 'read' => 1 },
-           Lang     => { 'read' => 1 },
-           Password     => { 'read' => 0, 'write' => 0 },
-          EmailAddress => { 'read' => 1, 'write' => 0 }
-     };
-  
+sub _Accessible  {
+  my $self = shift;
+  my %Cols = (
+             Name => 'read',
+             Gecos => 'read',
+             RealName => 'read',
+             Password => 'neither',
+             EmailAddress => 'read',
+             Privileged => 'read',
+             IsAdministrator => 'read'
+            );
+  return($self->SUPER::_Accessible(@_, %Cols));
 }
 # }}}
 
@@ -246,7 +212,6 @@ sub LoadByGecos  {
 
 Loads a User into this CurrentUser object.
 Takes a Name.
-
 =cut
 
 sub LoadByName {
@@ -276,11 +241,6 @@ sub Load  {
   if ($identifier !~ /\D/) {
     $self->SUPER::LoadById($identifier);
   }
-
-  elsif (UNIVERSAL::isa($identifier,"RT::User")) {
-         # DWIM if they pass a user in
-         $self->SUPER::LoadById($identifier->Id);
-  } 
   else {
       # This is a bit dangerous, we might get false authen if somebody
       # uses ambigous userids or real names:
@@ -353,15 +313,12 @@ specification. but currently doesn't
 =begin testing
 
 ok (my $cu = RT::CurrentUser->new('root'));
-ok (my $lh = $cu->LanguageHandle('en-us'));
-ok (defined $lh);
+ok (my $lh = $cu->LanguageHandle);
+ok ($lh != undef);
 ok ($lh->isa('Locale::Maketext'));
-is ($cu->loc('TEST_STRING'), "Concrete Mixer", "Localized TEST_STRING into English");
+ok ($cu->loc('TEST_STRING') eq "Concrete Mixer", "Localized TEST_STRING into English");
 ok ($lh = $cu->LanguageHandle('fr'));
-SKIP: {
-    skip "fr locale is not loaded", 1 unless grep $_ eq 'fr', @RT::LexiconLanguages;
-    is ($cu->loc('Before'), "Avant", "Localized TEST_STRING into Frenc");
-}
+ok ($cu->loc('Before') eq "Avant", "Localized TEST_STRING into Frenc");
 
 =end testing
 
@@ -369,24 +326,16 @@ SKIP: {
 
 sub LanguageHandle {
     my $self = shift;
-    if (   ( !defined $self->{'LangHandle'} )
-        || ( !UNIVERSAL::can( $self->{'LangHandle'}, 'maketext' ) )
-        || (@_) ) {
-        if ( !$RT::SystemUser or ($self->id || 0) == $RT::SystemUser->id() ) {
-            @_ = qw(en-US);
-        }
-
-        elsif ( $self->Lang ) {
-            push @_, $self->Lang;
-        }
+    if  ((!defined $self->{'LangHandle'}) || 
+         (!UNIVERSAL::can($self->{'LangHandle'}, 'maketext')) || 
+         (@_))  {
         $self->{'LangHandle'} = RT::I18N->get_handle(@_);
     }
-
     # Fall back to english.
-    unless ( $self->{'LangHandle'} ) {
+    unless ($self->{'LangHandle'}) {
         die "We couldn't get a dictionary. Nye mogu naidti slovar. No puedo encontrar dictionario.";
     }
-    return ( $self->{'LangHandle'} );
+    return ($self->{'LangHandle'});
 }
 
 sub loc {
@@ -406,7 +355,7 @@ sub loc {
 
 sub loc_fuzzy {
     my $self = shift;
-    return '' if (!$_[0] ||  $_[0] eq '');
+    return '' if $_[0] eq '';
 
     # XXX: work around perl's deficiency when matching utf8 data
     return $_[0] if Encode::is_utf8($_[0]);
@@ -416,62 +365,6 @@ sub loc_fuzzy {
 }
 # }}}
 
-
-=head2 CurrentUser
-
-Return  the current currentuser object
-
-=cut
-
-sub CurrentUser {
-    my $self = shift;
-    return($self);
-
-}
-
-=head2 Authenticate
-
-Takes $password, $created and $nonce, and returns a boolean value
-representing whether the authentication succeeded.
-
-If both $nonce and $created are specified, validate $password against:
-
-    encode_base64(sha1(
-       $nonce .
-       $created .
-       sha1_hex( "$username:$realm:$server_pass" )
-    ))
-
-where $server_pass is the md5_hex(password) digest stored in the
-database, $created is in ISO time format, and $nonce is a random
-string no longer than 32 bytes.
-
-=cut
-
-sub Authenticate { 
-    my ($self, $password, $created, $nonce, $realm) = @_;
-
-    require Digest::MD5;
-    require Digest::SHA1;
-    require MIME::Base64;
-
-    my $username = $self->UserObj->Name or return;
-    my $server_pass = $self->UserObj->__Value('Password') or return;
-    my $auth_digest = MIME::Base64::encode_base64(Digest::SHA1::sha1(
-       $nonce .
-       $created .
-       Digest::MD5::md5_hex("$username:$realm:$server_pass")
-    ));
-
-    chomp($password);
-    chomp($auth_digest);
-
-    return ($password eq $auth_digest);
-}
-
-# }}}
-
-
 eval "require RT::CurrentUser_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/CurrentUser_Vendor.pm});
 eval "require RT::CurrentUser_Local";
index 7b441a6..355370a 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 =head1 NAME
 
   RT::Date - a simple Object Oriented date.
@@ -227,28 +203,23 @@ sub Set {
 
 # {{{ sub SetToMidnight 
 
-=head2 SetToMidnight [Timezone => 'utc']
+=head2 SetToMidnight
 
-Sets the date to midnight (at the beginning of the day).
+Sets the date to midnight (at the beginning of the day) GMT
 Returns the unixtime at midnight.
 
-Arguments:
-
-=over 4
-
-=item Timezone - Timezone context C<server> or C<UTC>
-
 =cut
 
 sub SetToMidnight {
     my $self = shift;
-    my %args = ( Timezone => 'UTC', @_ );
-    if ( lc $args{'Timezone'} eq 'server' ) {
-        $self->Unix( Time::Local::timelocal( 0,0,0,(localtime $self->Unix)[3..7] ) );
-    } else {
-        $self->Unix( Time::Local::timegm( 0,0,0,(gmtime $self->Unix)[3..7] ) );
-    }
+    
+    use Time::Local;
+    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime($self->Unix);
+    $self->Unix(timegm (0,0,0,$mday,$mon,$year,$wday,$yday));
+    
     return ($self->Unix);
+    
+    
 }
 
 
@@ -359,8 +330,7 @@ sub DurationAsString {
         $s         = int( $duration / $YEAR );
         $time_unit = $self->loc("years");
     }
-
-    if ($negative) {
+    if (0) { # For now, never display the "AGO" # $negative) {
         return $self->loc( "[_1] [_2] ago", $s, $time_unit );
     }
     else {
@@ -405,7 +375,6 @@ sub AsString {
 # }}}
 
 # {{{ GetWeekday
-
 =head2 GetWeekday DAY
 
 Takes an integer day of week and returns a localized string for that day of week
@@ -428,7 +397,6 @@ sub GetWeekday {
 # }}}
 
 # {{{ GetMonth
-
 =head2 GetMonth DAY
 
 Takes an integer month and returns a localized string for that month 
@@ -561,65 +529,8 @@ sub ISO {
 
 # }}}
 
-# {{{ sub Date
-
-=head2 Date
-
-Takes nothing
-
-Returns the object's date in yyyy-mm-dd format; this is the same as
-the ISO format without the time
-
-=cut
-
-sub Date {
-    my $self = shift;
-    my ($date, $time) = split ' ', $self->ISO;
-    return $date;
-}
-
-# }}}}
-
-# {{{ sub Time
-
-=head2 Time
-
-Takes nothing
-
-Returns the object's time in hh:mm:ss format; this is the same as
-the ISO format without the date
-
-=cut
-
-sub Time {
-    my $self = shift;
-    my ($date, $time) = split ' ', $self->ISO;
-    return $time;
-}
-
-# }}}}
-
-# {{{ sub W3CDTF
-
-=head2 W3CDTF
-
-Takes nothing
-
-Returns the object's date in W3C DTF format
-
-=cut
-
-sub W3CDTF {
-    my $self = shift;
-    my $date = $self->ISO . 'Z';
-    $date =~ s/ /T/;
-    return $date;
-};
-
-# }}}
 
 # {{{ sub LocalTimezone 
-
 =head2 LocalTimezone
 
   Returns the current timezone. For now, draws off a system timezone, RT::Timezone. Eventually, this may
diff --git a/rt/lib/RT/Extension/ActivityReports.pm b/rt/lib/RT/Extension/ActivityReports.pm
new file mode 100644 (file)
index 0000000..52d8ba6
--- /dev/null
@@ -0,0 +1,3 @@
+package RT::Extension::ActivityReports;
+
+our $VERSION = '0.2';
index 3dc832f..4dcef3f 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -93,7 +69,7 @@ Create takes a hash of values and creates a row in the database:
   varchar(255) 'Description'.
   varchar(64) 'Domain'.
   varchar(64) 'Type'.
-  int(11) 'Instance'.
+  varchar(64) 'Instance'.
 
 =cut
 
@@ -122,7 +98,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -131,14 +107,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 Name
+=item Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=head2 SetName VALUE
+=item SetName VALUE
 
 
 Set Name to VALUE. 
@@ -149,14 +125,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Description
+=item Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=head2 SetDescription VALUE
+=item SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -167,14 +143,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Domain
+=item Domain
 
 Returns the current value of Domain. 
 (In the database, Domain is stored as varchar(64).)
 
 
 
-=head2 SetDomain VALUE
+=item SetDomain VALUE
 
 
 Set Domain to VALUE. 
@@ -185,14 +161,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Type
+=item Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(64).)
 
 
 
-=head2 SetType VALUE
+=item SetType VALUE
 
 
 Set Type to VALUE. 
@@ -203,40 +179,40 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Instance
+=item Instance
 
 Returns the current value of Instance. 
-(In the database, Instance is stored as int(11).)
+(In the database, Instance is stored as varchar(64).)
 
 
 
-=head2 SetInstance VALUE
+=item SetInstance VALUE
 
 
 Set Instance to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Instance will be stored as a int(11).)
+(In the database, Instance will be stored as a varchar(64).)
 
 
 =cut
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         Domain => 
-               {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
+               {read => 1, write => 1, type => 'varchar(64)', default => ''},
         Type => 
-               {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
+               {read => 1, write => 1, type => 'varchar(64)', default => ''},
         Instance => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, write => 1, type => 'varchar(64)', default => ''},
 
  }
 };
@@ -268,7 +244,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 96664e0..8de1a73 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -113,7 +89,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -122,14 +98,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 GroupId
+=item GroupId
 
 Returns the current value of GroupId. 
 (In the database, GroupId is stored as int(11).)
 
 
 
-=head2 SetGroupId VALUE
+=item SetGroupId VALUE
 
 
 Set GroupId to VALUE. 
@@ -140,14 +116,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 MemberId
+=item MemberId
 
 Returns the current value of MemberId. 
 (In the database, MemberId is stored as int(11).)
 
 
 
-=head2 SetMemberId VALUE
+=item SetMemberId VALUE
 
 
 Set MemberId to VALUE. 
@@ -159,15 +135,15 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         GroupId => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         MemberId => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
 
  }
 };
@@ -199,7 +175,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 978bbba..31cb953 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::GroupMember item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 2ee4d58..29f12a5 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::Group item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 7ba5ee8..5cdb65e 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# CONTRIBUTION SUBMISSION POLICY:
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-# 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 =head1 NAME
 
   RT::Handle - RT's database handle
@@ -72,13 +48,7 @@ use vars qw/@ISA/;
 
 eval "use DBIx::SearchBuilder::Handle::$RT::DatabaseType;
 \@ISA= qw(DBIx::SearchBuilder::Handle::$RT::DatabaseType);";
-
-if ($@) {
-    die "Unable to load DBIx::SearchBuilder database handle for '$RT::DatabaseType'.".
-        "\n".
-        "Perhaps you've picked an invalid database type or spelled it incorrectly.".
-        "\n". $@;
-}
+#TODO check for errors here.
 
 =head2 Connect
 
@@ -88,41 +58,29 @@ Takes nothing. Calls SUPER::Connect with the needed args
 =cut
 
 sub Connect {
-    my $self = shift;
+my $self=shift;
 
-    if ($RT::DatabaseType eq 'Oracle') {
-        $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8";
-        $ENV{'NLS_NCHAR'} = "AL32UTF8";
-        
-    }
+# Unless the database port is a positive integer, we really don't want to pass it.
 
-    $self->SUPER::Connect(
+$self->SUPER::Connect(
                         User => $RT::DatabaseUser,
                         Password => $RT::DatabasePassword,
                        );
-
-    $self->dbh->{LongReadLen} = $RT::MaxAttachmentSize;
    
 }
 
-=head2 BuildDSN
+=item BuildDSN
 
 Build the DSN for the RT database. doesn't take any parameters, draws all that
 from the config file.
 
 =cut
 
-use File::Spec;
 
 sub BuildDSN {
     my $self = shift;
-# Unless the database port is a positive integer, we really don't want to pass it.
 $RT::DatabasePort = undef unless (defined $RT::DatabasePort && $RT::DatabasePort =~ /^(\d+)$/);
 $RT::DatabaseHost = undef unless (defined $RT::DatabaseHost && $RT::DatabaseHost ne '');
-$RT::DatabaseName = File::Spec->catfile($RT::VarPath, $RT::DatabaseName)
-    if ($RT::DatabaseType eq 'SQLite') and
-       not File::Spec->file_name_is_absolute($RT::DatabaseName);
-
 
     $self->SUPER::BuildDSN(Host => $RT::DatabaseHost, 
                         Database => $RT::DatabaseName, 
diff --git a/rt/lib/RT/I18N/en_malkovich.po b/rt/lib/RT/I18N/en_malkovich.po
deleted file mode 100644 (file)
index 74769f1..0000000
+++ /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"
-
index c2b5454..ec0e877 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 use strict;
 
 use RT;
@@ -57,7 +33,7 @@ BEGIN {
     use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
     
     # set the version for version checking
-    $VERSION = do { my @r = (q$Revision: 1.1.1.6 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
+    $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
     
     @ISA         = qw(Exporter);
     
index dfbd4ae..7eec050 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 package RT::Interface::Email;
 
 use strict;
 use Mail::Address;
 use MIME::Entity;
 use RT::EmailParser;
-use File::Temp;
-use UNIVERSAL::require;
+
 
 BEGIN {
     use Exporter ();
-    use vars qw ( @ISA @EXPORT_OK);
-
+    use vars qw ($VERSION  @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+    
     # set the version for version checking
-    our $VERSION = 2.0;
-
-    @ISA = qw(Exporter);
-
+    $VERSION = do { my @r = (q$Revision: 1.2 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
+    
+    @ISA         = qw(Exporter);
+    
     # your exported package globals go here,
     # as well as any optionally exported functions
-    @EXPORT_OK = qw(
-        &CreateUser
-        &GetMessageContent
-        &CheckForLoops
-        &CheckForSuspiciousSender
-        &CheckForAutoGenerated
-        &CheckForBounce
-        &MailError
-        &ParseCcAddressesFromHead
-        &ParseSenderAddressFromHead
-        &ParseErrorsToAddressFromHead
-        &ParseAddressFromHeader
-        &Gateway);
+    @EXPORT_OK   = qw(
+              &CreateUser
+                     &GetMessageContent
+                     &CheckForLoops 
+                     &CheckForSuspiciousSender
+                     &CheckForAutoGenerated 
+                     &MailError 
+                     &ParseCcAddressesFromHead
+                     &ParseSenderAddressFromHead 
+                     &ParseErrorsToAddressFromHead
+                      &ParseAddressFromHeader
+              &Gateway);
 
 }
 
 =head1 NAME
 
-  RT::Interface::Email - helper functions for parsing email sent to RT
+  RT::Interface::CLI - helper functions for creating a commandline RT interface
 
 =head1 SYNOPSIS
 
@@ -106,18 +80,19 @@ ok(require RT::Interface::Email);
 
 =cut
 
-# {{{ sub CheckForLoops
 
-sub CheckForLoops {
-    my $head = shift;
+# {{{ sub CheckForLoops 
 
+sub CheckForLoops  {
+    my $head = shift;
+    
     #If this instance of RT sent it our, we don't want to take it in
     my $RTLoop = $head->get("X-RT-Loop-Prevention") || "";
-    chomp($RTLoop);    #remove that newline
-    if ( $RTLoop eq "$RT::rtname" ) {
-        return (1);
+    chomp ($RTLoop); #remove that newline
+    if ($RTLoop eq "$RT::rtname") {
+       return (1);
     }
-
+    
     # TODO: We might not trap the case where RT instance A sends a mail
     # to RT instance B which sends a mail to ...
     return (undef);
@@ -131,24 +106,23 @@ sub CheckForSuspiciousSender {
     my $head = shift;
 
     #if it's from a postmaster or mailer daemon, it's likely a bounce.
-
+    
     #TODO: better algorithms needed here - there is no standards for
     #bounces, so it's very difficult to separate them from anything
     #else.  At the other hand, the Return-To address is only ment to be
     #used as an error channel, we might want to put up a separate
     #Return-To address which is treated differently.
-
+    
     #TODO: search through the whole email and find the right Ticket ID.
 
-    my ( $From, $junk ) = ParseSenderAddressFromHead($head);
-
-    if (   ( $From =~ /^mailer-daemon\@/i )
-        or ( $From =~ /^postmaster\@/i ) )
-    {
-        return (1);
-
+    my ($From, $junk) = ParseSenderAddressFromHead($head);
+    
+    if (($From =~ /^mailer-daemon/i) or
+       ($From =~ /^postmaster/i)){
+       return (1);
+       
     }
-
+    
     return (undef);
 
 }
@@ -158,126 +132,57 @@ sub CheckForSuspiciousSender {
 # {{{ sub CheckForAutoGenerated
 sub CheckForAutoGenerated {
     my $head = shift;
-
-    my $Precedence = $head->get("Precedence") || "";
-    if ( $Precedence =~ /^(bulk|junk)/i ) {
-        return (1);
-    }
-
-    # First Class mailer uses this as a clue.
-    my $FCJunk = $head->get("X-FC-Machinegenerated") || "";
-    if ( $FCJunk =~ /^true/i ) {
-        return (1);
+    
+    my $Precedence = $head->get("Precedence") || "" ;
+    if ($Precedence =~ /^(bulk|junk)/i) {
+       return (1);
     }
-
-    return (0);
-}
-
-# }}}
-
-# {{{ sub CheckForBounce
-sub CheckForBounce {
-    my $head = shift;
-
-    my $ReturnPath = $head->get("Return-path") || "";
-    return ( $ReturnPath =~ /<>/ );
-}
-
-# }}}
-
-# {{{ IsRTAddress
-
-=head2 IsRTAddress ADDRESS
-
-Takes a single parameter, an email address. 
-Returns true if that address matches the $RTAddressRegexp.  
-Returns false, otherwise.
-
-=cut
-
-sub IsRTAddress {
-    my $address = shift || '';
-
-    # Example: the following rule would tell RT not to Cc
-    #   "tickets@noc.example.com"
-    if ( defined($RT::RTAddressRegexp)
-        && $address =~ /$RT::RTAddressRegexp/i )
-    {
-        return (1);
-    } else {
-        return (undef);
+    else {
+       return (0);
     }
 }
 
 # }}}
 
-# {{{ CullRTAddresses
-
-=head2 CullRTAddresses ARRAY
-
-Takes a single argument, an array of email addresses.
-Returns the same array with any IsRTAddress()es weeded out.
 
-=cut
-
-sub CullRTAddresses {
-    return grep !IsRTAddress($_), @_;
-}
-
-# }}}
-
-# {{{ sub MailError
+# {{{ sub MailError 
 sub MailError {
-    my %args = (
-        To          => $RT::OwnerEmail,
-        Bcc         => undef,
-        From        => $RT::CorrespondAddress,
-        Subject     => 'There has been an error',
-        Explanation => 'Unexplained error',
-        MIMEObj     => undef,
-        Attach      => undef,
-        LogLevel    => 'crit',
-        @_
-    );
-
-    $RT::Logger->log(
-        level   => $args{'LogLevel'},
-        message => $args{'Explanation'}
-    );
-    # the colons are necessary to make ->build include non-standard headers
-    my $entity = MIME::Entity->build(
-        Type                   => "multipart/mixed",
-        From                   => $args{'From'},
-        Bcc                    => $args{'Bcc'},
-        To                     => $args{'To'},
-        Subject                => $args{'Subject'},
-        'Precedence:'             => 'bulk',
-        'X-RT-Loop-Prevention:' => $RT::rtname,
-        'In-Reply-To:'          => $args{'MIMEObj'} ? $args{'MIMEObj'}->head->get('Message-Id') : undef
-    );
-
-    $entity->attach( Data => $args{'Explanation'} . "\n" );
-
+    my %args = (To => $RT::OwnerEmail,
+               Bcc => undef,
+               From => $RT::CorrespondAddress,
+               Subject => 'There has been an error',
+               Explanation => 'Unexplained error',
+               MIMEObj => undef,
+               LogLevel => 'crit',
+               @_);
+
+
+    $RT::Logger->log(level => $args{'LogLevel'}, 
+                    message => $args{'Explanation'}
+                   );
+    my $entity = MIME::Entity->build( Type  =>"multipart/mixed",
+                                     From => $args{'From'},
+                                     Bcc => $args{'Bcc'},
+                                     To => $args{'To'},
+                                     Subject => $args{'Subject'},
+                                     'X-RT-Loop-Prevention' => $RT::rtname,
+                                   );
+
+    $entity->attach(  Data => $args{'Explanation'}."\n");
+    
     my $mimeobj = $args{'MIMEObj'};
     if ($mimeobj) {
         $mimeobj->sync_headers();
         $entity->add_part($mimeobj);
     }
-
-    if ( $args{'Attach'} ) {
-        $entity->attach( Data => $args{'Attach'}, Type => 'message/rfc822' );
-
-    }
-
-    if ( $RT::MailCommand eq 'sendmailpipe' ) {
-        open( MAIL,
-            "|$RT::SendmailPath $RT::SendmailBounceArguments $RT::SendmailArguments"
-            )
-            || return (0);
+    
+    if ($RT::MailCommand eq 'sendmailpipe') {
+        open (MAIL, "|$RT::SendmailPath $RT::SendmailArguments") || return(0);
         print MAIL $entity->as_string;
         close(MAIL);
-    } else {
-        $entity->send( $RT::MailCommand, $RT::MailParams );
+    }
+    else {
+       $entity->send($RT::MailCommand, $RT::MailParams);
     }
 }
 
@@ -286,38 +191,43 @@ sub MailError {
 # {{{ Create User
 
 sub CreateUser {
-    my ( $Username, $Address, $Name, $ErrorsTo, $entity ) = @_;
+    my ($Username, $Address, $Name, $ErrorsTo, $entity) = @_;
     my $NewUser = RT::User->new($RT::SystemUser);
 
-    my ( $Val, $Message ) = $NewUser->Create(
-        Name => ( $Username || $Address ),
-        EmailAddress => $Address,
-        RealName     => $Name,
-        Password     => undef,
-        Privileged   => 0,
-        Comments     => 'Autocreated on ticket submission'
-    );
-
+    # This data is tainted by some Very Broken mailers.
+    # (Sometimes they send raw ISO 8859-1 data here. fear that.
+    require Encode;
+    $Username = Encode::encode(utf8 => $Username, Encode::FB_PERLQQ()) if defined $Username;
+    $Name = Encode::encode(utf8 => $Name, Encode::FB_PERLQQ()) if defined $Name;
+    
+    my ($Val, $Message) = 
+      $NewUser->Create(Name => ($Username || $Address),
+                       EmailAddress => $Address,
+                       RealName => $Name,
+                       Password => undef,
+                       Privileged => 0,
+                       Comments => 'Autocreated on ticket submission'
+                      );
+    
     unless ($Val) {
-
+        
         # Deal with the race condition of two account creations at once
+        #
         if ($Username) {
             $NewUser->LoadByName($Username);
         }
-
-        unless ( $NewUser->Id ) {
+        
+        unless ($NewUser->Id) {
             $NewUser->LoadByEmail($Address);
         }
-
-        unless ( $NewUser->Id ) {
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "User could not be created",
-                Explanation =>
-                    "User creation failed in mailgateway: $Message",
-                MIMEObj  => $entity,
-                LogLevel => 'crit'
-            );
+        
+        unless ($NewUser->Id) {  
+            MailError( To => $ErrorsTo,
+                       Subject => "User could not be created",
+                       Explanation => "User creation failed in mailgateway: $Message",
+                       MIMEObj => $entity,
+                       LogLevel => 'crit'
+                     );
         }
     }
 
@@ -325,25 +235,20 @@ sub CreateUser {
     my $CurrentUser = RT::CurrentUser->new();
     $CurrentUser->LoadByEmail($Address);
 
-    unless ( $CurrentUser->id ) {
-        $RT::Logger->warning(
-            "Couldn't load user '$Address'." . "giving up" );
-        MailError(
-            To          => $ErrorsTo,
-            Subject     => "User could not be loaded",
-            Explanation =>
-                "User  '$Address' could not be loaded in the mail gateway",
-            MIMEObj  => $entity,
-            LogLevel => 'crit'
-        );
+    unless ($CurrentUser->id) {
+            $RT::Logger->warning("Couldn't load user '$Address'.".  "giving up");
+                MailError( To => $ErrorsTo,
+                           Subject => "User could not be loaded",
+                           Explanation => "User  '$Address' could not be loaded in the mail gateway",
+                           MIMEObj => $entity,
+                           LogLevel => 'crit'
+                     );
     }
 
     return $CurrentUser;
 }
-
-# }}}
-
-# {{{ ParseCcAddressesFromHead
+# }}}      
+# {{{ ParseCcAddressesFromHead 
 
 =head2 ParseCcAddressesFromHead HASHREF
 
@@ -353,34 +258,32 @@ headers b<except> the current Queue\'s email addresses, the CurrentUser\'s
 email address  and anything that the configuration sub RT::IsRTAddress matches.
 
 =cut
-
+  
 sub ParseCcAddressesFromHead {
-    my %args = (
-        Head        => undef,
-        QueueObj    => undef,
-        CurrentUser => undef,
-        @_
-    );
-
+    my %args = ( Head => undef,
+                QueueObj => undef,
+                CurrentUser => undef,
+                @_ );
+    
     my (@Addresses);
-
-    my @ToObjs = Mail::Address->parse( $args{'Head'}->get('To') );
-    my @CcObjs = Mail::Address->parse( $args{'Head'}->get('Cc') );
-
-    foreach my $AddrObj ( @ToObjs, @CcObjs ) {
-        my $Address = $AddrObj->address;
-        $Address = $args{'CurrentUser'}
-            ->UserObj->CanonicalizeEmailAddress($Address);
-        next if ( $args{'CurrentUser'}->EmailAddress   =~ /^\Q$Address\E$/i );
-        next if ( $args{'QueueObj'}->CorrespondAddress =~ /^\Q$Address\E$/i );
-        next if ( $args{'QueueObj'}->CommentAddress    =~ /^\Q$Address\E$/i );
-        next if ( RT::EmailParser->IsRTAddress($Address) );
-
-        push( @Addresses, $Address );
+        
+    my @ToObjs = Mail::Address->parse($args{'Head'}->get('To'));
+    my @CcObjs = Mail::Address->parse($args{'Head'}->get('Cc'));
+    
+    foreach my $AddrObj (@ToObjs, @CcObjs) {
+       my $Address = $AddrObj->address;
+       $Address = $args{'CurrentUser'}->UserObj->CanonicalizeEmailAddress($Address);
+       next if ($args{'CurrentUser'}->EmailAddress =~ /^$Address$/i);
+       next if ($args{'QueueObj'}->CorrespondAddress =~ /^$Address$/i);
+       next if ($args{'QueueObj'}->CommentAddress =~ /^$Address$/i);
+       next if (RT::EmailParser::IsRTAddress(undef, $Address));
+       
+       push (@Addresses, $Address);
     }
     return (@Addresses);
 }
 
+
 # }}}
 
 # {{{ ParseSenderAdddressFromHead
@@ -394,16 +297,11 @@ of the From (evaluated in order of Reply-To:, From:, Sender)
 
 sub ParseSenderAddressFromHead {
     my $head = shift;
-
     #Figure out who's sending this message.
-    foreach my $header ('Reply-To', 'From', 'Sender') {
-        my $From = $head->get($header);
-        my ($addr, $name) = ParseAddressFromHeader($From);
-        # only return if the address is not empty
-        return ($addr, $name) if $addr;
-    }
-
-    return (undef, undef);
+    my $From = $head->get('Reply-To') || 
+      $head->get('From') || 
+       $head->get('Sender');
+    return (ParseAddressFromHeader($From));
 }
 # }}}
 
@@ -412,29 +310,24 @@ sub ParseSenderAddressFromHead {
 =head2 ParseErrorsToAddressFromHead
 
 Takes a MIME::Header object. Return a single value : user@host
-of the From (evaluated in order of Return-path:,Errors-To:,Reply-To:,
-From:, Sender)
+of the From (evaluated in order of Errors-To:,Reply-To:, From:, Sender)
 
 =cut
 
 sub ParseErrorsToAddressFromHead {
     my $head = shift;
-
     #Figure out who's sending this message.
 
-    foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) {
-
-        # If there's a header of that name
-        my $headerobj = $head->get($header);
-        if ($headerobj) {
-            my ( $addr, $name ) = ParseAddressFromHeader($headerobj);
-
-            # If it's got actual useful content...
-            return ($addr) if ($addr);
-        }
+    foreach my $header ('Errors-To' , 'Reply-To', 'From', 'Sender' ) {
+       # If there's a header of that name
+       my $headerobj = $head->get($header);
+       if ($headerobj) {
+               my ($addr, $name ) = ParseAddressFromHeader($headerobj);
+               # If it's got actual useful content...
+               return ($addr) if ($addr);
+       }
     }
 }
-
 # }}}
 
 # {{{ ParseAddressFromHeader
@@ -445,547 +338,311 @@ Takes an address from $head->get('Line') and returns a tuple: user@host, friendl
 
 =cut
 
-sub ParseAddressFromHeader {
-    my $Addr = shift;
 
-    # Some broken mailers send:  ""Vincent, Jesse"" <jesse@fsck.com>. Hate
-    $Addr =~ s/\"\"(.*?)\"\"/\"$1\"/g;                                                                                                                                                  
+sub ParseAddressFromHeader{
+    my $Addr = shift;
+    
     my @Addresses = Mail::Address->parse($Addr);
+    
+    my $AddrObj = $Addresses[0];
 
-    my ($AddrObj) = grep ref $_, @Addresses;
-    unless ( $AddrObj ) {
-        return ( undef, undef );
+    unless (ref($AddrObj)) {
+       return(undef,undef);
     }
-
-    my $Name = ( $AddrObj->phrase || $AddrObj->comment || $AddrObj->address );
-
+    my $Name =  ($AddrObj->phrase || $AddrObj->comment || $AddrObj->address);
+    
     #Lets take the from and load a user object.
     my $Address = $AddrObj->address;
 
-    return ( $Address, $Name );
+    return ($Address, $Name);
 }
-
 # }}}
 
-# {{{ sub ParseTicketId
 
-sub ParseTicketId {
-    my $Subject = shift;
-    my $id;
-
-    my $test_name = $RT::EmailSubjectTagRegex || qr/\Q$RT::rtname\E/i;
-
-    if ( $Subject =~ s/\[$test_name\s+\#(\d+)\s*\]//i ) {
-        my $id = $1;
-        $RT::Logger->debug("Found a ticket ID. It's $id");
-        return ($id);
-    } else {
-        return (undef);
-    }
-}
-
-# }}}
-
-=head2 Gateway ARGSREF
-
-
-Takes parameters:
-
-    action
-    queue
-    message
 
+=head2 Gateway
 
 This performs all the "guts" of the mail rt-mailgate program, and is
 designed to be called from the web interface with a message, user
 object, and so on.
 
-Can also take an optional 'ticket' parameter; this ticket id overrides
-any ticket id found in the subject.
-
-Returns:
-
-    An array of:
-    
-    (status code, message, optional ticket object)
-
-    status code is a numeric value.
-
-      for temporary failures, the status code should be -75
-
-      for permanent failures which are handled by RT, the status code 
-      should be 0
-    
-      for succces, the status code should be 1
-
-
-
 =cut
 
 sub Gateway {
-    my $argsref = shift;
-    my %args    = (
-        action  => 'correspond',
-        queue   => '1',
-        ticket  => undef,
-        message => undef,
-        %$argsref
-    );
-
-    my $SystemTicket;
-    my $Right;
+    my %args = ( message => undef,
+                 queue   => 1,
+                 action  => 'correspond',
+                 ticket  => undef,
+                 @_ );
 
     # Validate the action
-    my ( $status, @actions ) = IsCorrectAction( $args{'action'} );
-    unless ($status) {
-        return (
-            -75,
-            "Invalid 'action' parameter "
-                . $actions[0]
-                . " for queue "
-                . $args{'queue'},
-            undef
-        );
+    unless ( $args{'action'} =~ /^(comment|correspond|action)$/ ) {
+
+        # Can't safely loc this. What object do we loc around?
+        return ( 0, "Invalid 'action' parameter", undef );
     }
 
     my $parser = RT::EmailParser->new();
-    $parser->SmartParseMIMEEntityFromScalar( Message => $args{'message'} );
-    my $Message = $parser->Entity();
-
-    unless ($Message) {
-        MailError(
-            To          => $RT::OwnerEmail,
-            Subject     => "RT Bounce: Unparseable message",
-            Explanation => "RT couldn't process the message below",
-            Attach      => $args{'message'}
-        );
-
-        return ( 0,
-            "Failed to parse this message. Something is likely badly wrong with the message"
-        );
-    }
+    $parser->ParseMIMEEntityFromScalar( $args{'message'} );
 
+    my $Message = $parser->Entity();
     my $head = $Message->head;
 
+    my ( $CurrentUser, $AuthStat, $status, $error );
+
     my $ErrorsTo = ParseErrorsToAddressFromHead($head);
 
-    my $MessageId = $head->get('Message-ID')
-        || "<no-message-id-" . time . rand(2000) . "\@.$RT::Organization>";
+    my $MessageId = $head->get('Message-Id')
+      || "<no-message-id-" . time . rand(2000) . "\@.$RT::Organization>";
 
     #Pull apart the subject line
     my $Subject = $head->get('Subject') || '';
     chomp $Subject;
-    
-    # {{{ Lets check for mail loops of various sorts.
-    my ($should_store_machine_generated_message, $IsALoop, $result);
-    ( $should_store_machine_generated_message, $ErrorsTo, $result, $IsALoop ) =
-      _HandleMachineGeneratedMail(
-        Message  => $Message,
-        ErrorsTo => $ErrorsTo,
-        Subject  => $Subject,
-        MessageId => $MessageId
-    );
-
-    # Do not pass loop messages to MailPlugins, to make sure the loop
-    # is broken, unless $RT::StoreLoops is set.
-    if ($IsALoop && !$should_store_machine_generated_message) {
-        return ( 0, $result, undef );
-    }
 
-    $args{'ticket'} ||= ParseTicketId($Subject);
 
-    $SystemTicket = RT::Ticket->new($RT::SystemUser);
-    $SystemTicket->Load( $args{'ticket'} ) if ( $args{'ticket'} ) ;
-    if ( $SystemTicket->id ) {
-        $Right = 'ReplyToTicket';
-    } else {
-        $Right = 'CreateTicket';
+    $args{'ticket'} ||= $parser->ParseTicketId($Subject);
+
+    my $SystemTicket;
+    if ($args{'ticket'} ) {
+        $SystemTicket = RT::Ticket->new($RT::SystemUser);
+        $SystemTicket->Load($args{'ticket'});
     }
 
     #Set up a queue object
     my $SystemQueueObj = RT::Queue->new($RT::SystemUser);
     $SystemQueueObj->Load( $args{'queue'} );
 
+
     # We can safely have no queue of we have a known-good ticket
-    unless ( $SystemTicket->id || $SystemQueueObj->id ) {
-        return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
+    unless ( $args{'ticket'} || $SystemQueueObj->id ) {
+        MailError(
+                 To          => $RT::OwnerEmail,
+                 Subject     => "RT Bounce: $Subject",
+                 Explanation => "RT couldn't find the queue: " . $args{'queue'},
+                 MIMEObj     => $Message );
+        return ( 0, "RT couldn't find the queue: " . $args{'queue'}, undef );
     }
 
-    # Authentication Level ($AuthStat)
-    # -1 - Get out.  this user has been explicitly declined
+    # Authentication Level
+    # -1 - Get out.  this user has been explicitly declined 
     # 0 - User may not do anything (Not used at the moment)
     # 1 - Normal user
     # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate
-    my ( $CurrentUser, $AuthStat, $error );
-
-    # Initalize AuthStat so comparisons work correctly
-    $AuthStat = -9999999;
-
-    push @RT::MailPlugins, "Auth::MailFrom" unless @RT::MailPlugins;
-
-    # if plugin returns AuthStat -2 we skip action
-    # NOTE: this is experimental API and it would be changed
-    my %skip_action = ();
 
+    push @RT::MailPlugins, "Auth::MailFrom"   unless @RT::MailPlugins;
     # Since this needs loading, no matter what
-    foreach (@RT::MailPlugins) {
-        my ($Code, $NewAuthStat);
+
+    for (@RT::MailPlugins) {
+        my $Code;
+        my $NewAuthStat;
         if ( ref($_) eq "CODE" ) {
             $Code = $_;
-        } else {
-            my $Class = $_;
-            $Class = "RT::Interface::Email::" . $Class
-                unless $Class =~ /^RT::Interface::Email::/;
-            $Class->require or
-                do { $RT::Logger->error("Couldn't load $Class: $@"); next };
-
+        }
+        else {
+            $_ = "RT::Interface::Email::$_" unless /^RT::Interface::Email::/;
+            eval "require $_;";
+            if ($@) {
+                die ("Couldn't load module $_: $@");
+                next;
+            }
             no strict 'refs';
-            unless ( defined( $Code = *{ $Class . "::GetCurrentUser" }{CODE} ) ) {
-                $RT::Logger->crit( "No 'GetCurrentUser' function found in '$Class' module");
+            if ( !defined( $Code = *{ $_ . "::GetCurrentUser" }{CODE} ) ) {
+                die ("No GetCurrentUser code found in $_ module");
                 next;
             }
         }
 
-        foreach my $action (@actions) {
-            ( $CurrentUser, $NewAuthStat ) = $Code->(
-                Message       => $Message,
-                RawMessageRef => \$args{'message'},
-                CurrentUser   => $CurrentUser,
-                AuthLevel     => $AuthStat,
-                Action        => $action,
-                Ticket        => $SystemTicket,
-                Queue         => $SystemQueueObj
-            );
-
-# You get the highest level of authentication you were assigned, unless you get the magic -1
-# If a module returns a "-1" then we discard the ticket, so.
-            $AuthStat = $NewAuthStat
-                if ( $NewAuthStat > $AuthStat or $NewAuthStat == -1 or $NewAuthStat == -2 );
-
-            last if $AuthStat == -1;
-            $skip_action{$action}++ if $AuthStat == -2;
-        }
-
-        # strip actions we should skip
-        @actions = grep !$skip_action{$_}, @actions if $AuthStat == -2;
-        last unless @actions;
+        ( $CurrentUser, $NewAuthStat ) = $Code->( Message     => $Message,
+                                                  CurrentUser => $CurrentUser,
+                                                  AuthLevel   => $AuthStat,
+                                                  Action => $args{'action'},
+                                                  Ticket => $SystemTicket,
+                                                  Queue  => $SystemQueueObj );
 
+        # You get the highest level of authentication you were assigned.
         last if $AuthStat == -1;
+        $AuthStat = $NewAuthStat if $NewAuthStat > $AuthStat;
     }
+
     # {{{ If authentication fails and no new user was created, get out.
-    if ( !$CurrentUser || !$CurrentUser->id || $AuthStat == -1 ) {
+    if ( !$CurrentUser or !$CurrentUser->Id or $AuthStat == -1 ) {
 
         # If the plugins refused to create one, they lose.
-        unless ( $AuthStat == -1 ) {
-            _NoAuthorizedUserFound(
-                Right     => $Right,
-                Message   => $Message,
-                Requestor => $ErrorsTo,
-                Queue     => $args{'queue'}
-            );
-
-        }
-        return ( 0, "Could not load a valid user", undef );
-    }
-
-    # If we got a user, but they don't have the right to say things
-    if ( $AuthStat == 0 ) {
         MailError(
-            To          => $ErrorsTo,
-            Subject     => "Permission Denied",
-            Explanation =>
-                "You do not have permission to communicate with RT",
-            MIMEObj => $Message
-        );
-        return (
-            0,
-            "$ErrorsTo tried to submit a message to "
-                . $args{'Queue'}
-                . " without permission.",
-            undef
-        );
-    }
+            Subject     => "Could not load a valid user",
+            Explanation => <<EOT,
+RT could not load a valid user, and RT's configuration does not allow
+for the creation of a new user for your email.
 
+Your RT administrator needs to grant 'Everyone' the right 'CreateTicket'
+for this queue.
 
-    unless ($should_store_machine_generated_message) {
-        return ( 0, $result, undef );
+EOT
+            MIMEObj  => $Message,
+            LogLevel => 'error' )
+          unless $AuthStat == -1;
+        return ( 0, "Could not load a valid user", undef );
     }
-    
-    # if plugin's updated SystemTicket then update arguments
-    $args{'ticket'} = $SystemTicket->Id if $SystemTicket && $SystemTicket->Id;
 
-    my $Ticket = RT::Ticket->new($CurrentUser);
+    # }}}
 
-    if ( !$args{'ticket'} && grep /^(comment|correspond)$/, @actions )
-    {
+    # {{{ Lets check for mail loops of various sorts.
+    my $IsAutoGenerated = CheckForAutoGenerated($head);
 
-        my @Cc;
-        my @Requestors = ( $CurrentUser->id );
+    my $IsSuspiciousSender = CheckForSuspiciousSender($head);
 
-        if ($RT::ParseNewMessageForTicketCcs) {
-            @Cc = ParseCcAddressesFromHead(
-                Head        => $head,
-                CurrentUser => $CurrentUser,
-                QueueObj    => $SystemQueueObj
-            );
-        }
+    my $IsALoop = CheckForLoops($head);
 
-        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
-            Queue     => $SystemQueueObj->Id,
-            Subject   => $Subject,
-            Requestor => \@Requestors,
-            Cc        => \@Cc,
-            MIMEObj   => $Message
-        );
-        if ( $id == 0 ) {
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "Ticket creation failed: $Subject",
-                Explanation => $ErrStr,
-                MIMEObj     => $Message
-            );
-            return ( 0, "Ticket creation failed: $ErrStr", $Ticket );
-        }
+    my $SquelchReplies = 0;
 
-        # strip comments&corresponds from the actions we don't need
-        # to record them if we've created the ticket just now
-        @actions = grep !/^(comment|correspond)$/, @actions;
-        $args{'ticket'} = $id;
+    #If the message is autogenerated, we need to know, so we can not
+    # send mail to the sender
+    if ( $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
+        $SquelchReplies = 1;
+        $ErrorsTo       = $RT::OwnerEmail;
+    }
 
-    } elsif ( $args{'ticket'} ) {
+    # }}}
 
-        $Ticket->Load( $args{'ticket'} );
-        unless ( $Ticket->Id ) {
-            my $error = "Could not find a ticket with id " . $args{'ticket'};
-            MailError(
-                To          => $ErrorsTo,
-                Subject     => "Message not recorded: $Subject",
-                Explanation => $error,
-                MIMEObj     => $Message
-            );
-
-            return ( 0, $error );
-        }
-        $args{'ticket'} = $Ticket->id;
-    } else {
-        return ( 1, "Success", $Ticket );
+    # {{{ Drop it if it's disallowed
+    if ( $AuthStat == 0 ) {
+        MailError(
+             To          => $ErrorsTo,
+             Subject     => "Permission Denied",
+             Explanation => "You do not have permission to communicate with RT",
+             MIMEObj     => $Message );
     }
 
     # }}}
-    foreach my $action (@actions) {
-
-        #   If the action is comment, add a comment.
-        if ( $action =~ /^(?:comment|correspond)$/i ) {
-            my $method = ucfirst lc $action;
-            my ( $status, $msg ) = $Ticket->$method( MIMEObj => $Message );
-            unless ($status) {
-
-                #Warn the sender that we couldn't actually submit the comment.
-                MailError(
-                    To          => $ErrorsTo,
-                    Subject     => "Message not recorded: $Subject",
-                    Explanation => $msg,
-                    MIMEObj     => $Message
-                );
-                return ( 0, "Message not recorded: $msg", $Ticket );
-            }
-        } elsif ($RT::UnsafeEmailCommands) {
-            my ( $status, $msg ) = _RunUnsafeAction(
-                Action      => $action,
-                ErrorsTo    => $ErrorsTo,
-                Message     => $Message,
-                Ticket      => $Ticket,
-                CurrentUser => $CurrentUser,
-            );
-            return ($status, $msg, $Ticket) unless $status == 1;
-        }
-    }
-    return ( 1, "Success", $Ticket );
-}
+    # {{{ Warn someone  if it's a loop
 
-sub _RunUnsafeAction {
-    my %args = (
-        Action      => undef,
-        ErrorsTo    => undef,
-        Message     => undef,
-        Ticket      => undef,
-        CurrentUser => undef,
-        @_
-    );
-
-    if ( $args{'Action'} =~ /^take$/i ) {
-        my ( $status, $msg ) = $args{'Ticket'}->SetOwner( $args{'CurrentUser'}->id );
-        unless ($status) {
-            MailError(
-                To          => $args{'ErrorsTo'},
-                Subject     => "Ticket not taken",
-                Explanation => $msg,
-                MIMEObj     => $args{'Message'}
-            );
-            return ( 0, "Ticket not taken" );
-        }
-    } elsif ( $args{'Action'} =~ /^resolve$/i ) {
-        my ( $status, $msg ) = $args{'Ticket'}->SetStatus('resolved');
-        unless ($status) {
+    # Warn someone if it's a loop, before we drop it on the ground
+    if ($IsALoop) {
+        $RT::Logger->crit("RT Recieved mail ($MessageId) from itself.");
 
-            #Warn the sender that we couldn't actually submit the comment.
-            MailError(
-                To          => $args{'ErrorsTo'},
-                Subject     => "Ticket not resolved",
-                Explanation => $msg,
-                MIMEObj     => $args{'Message'}
-            );
-            return ( 0, "Ticket not resolved" );
+        #Should we mail it to RTOwner?
+        if ($RT::LoopsToRTOwner) {
+            MailError( To          => $RT::OwnerEmail,
+                       Subject     => "RT Bounce: $Subject",
+                       Explanation => "RT thinks this message may be a bounce",
+                       MIMEObj     => $Message );
+
+            #Do we actually want to store it?
+            return ( 0, "Message Bounced", undef ) unless ($RT::StoreLoops);
         }
-    } else {
-        return ( 0, "Not supported unsafe action $args{'Action'}", $args{'Ticket'} );
     }
-    return ( 1, "Success" );
-}
-
-=head2 _NoAuthorizedUserFound
 
-Emails the RT Owner and the requestor when the auth plugins return "No auth user found"
-
-=cut
-
-sub _NoAuthorizedUserFound {
-    my %args = (
-        Right     => undef,
-        Message   => undef,
-        Requestor => undef,
-        Queue     => undef,
-        @_
-    );
-
-    # Notify the RT Admin of the failure.
-    MailError(
-        To          => $RT::OwnerEmail,
-        Subject     => "Could not load a valid user",
-        Explanation => <<EOT,
-RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for this email (@{[$args{Requestor}]}).
+    # }}}
 
-You might need to grant 'Everyone' the right '@{[$args{Right}]}' for the
-queue @{[$args{'Queue'}]}.
+    # {{{ Squelch replies if necessary
+    # Don't let the user stuff the RT-Squelch-Replies-To header.
+    if ( $head->get('RT-Squelch-Replies-To') ) {
+        $head->add( 'RT-Relocated-Squelch-Replies-To',
+                    $head->get('RT-Squelch-Replies-To') );
+        $head->delete('RT-Squelch-Replies-To');
+    }
 
-EOT
-        MIMEObj  => $args{'Message'},
-        LogLevel => 'error'
-    );
-
-    # Also notify the requestor that his request has been dropped.
-    if ($args{'Requestor'} ne $RT::OwnerEmail) {
-    MailError(
-        To          => $args{'Requestor'},
-        Subject     => "Could not load a valid user",
-        Explanation => <<EOT,
-RT could not load a valid user, and RT's configuration does not allow
-for the creation of a new user for your email.
+    if ($SquelchReplies) {
+        ## TODO: This is a hack.  It should be some other way to
+        ## indicate that the transaction should be "silent".
 
-EOT
-        MIMEObj  => $args{'Message'},
-        LogLevel => 'error'
-    );
+        my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
+        $head->add( 'RT-Squelch-Replies-To', $Sender );
     }
-}
 
-=head2 _HandleMachineGeneratedMail
-
-Takes named params:
-    Message
-    ErrorsTo
-    Subject
+    # }}}
 
-Checks the message to see if it's a bounce, if it looks like a loop, if it's autogenerated, etc.
-Returns a triple of ("Should we continue (boolean)", "New value for $ErrorsTo", "Status message",
-"This message appears to be a loop (boolean)" );
+    my $Ticket = RT::Ticket->new($CurrentUser);
 
-=cut
+    # {{{ If we don't have a ticket Id, we're creating a new ticket
+    if ( !$args{'ticket'} ) {
 
-sub _HandleMachineGeneratedMail {
-    my %args = ( Message => undef, ErrorsTo => undef, Subject => undef, MessageId => undef, @_ );
-    my $head = $args{'Message'}->head;
-    my $ErrorsTo = $args{'ErrorsTo'};
+        # {{{ Create a new ticket
 
-    my $IsBounce = CheckForBounce($head);
+        my @Cc;
+        my @Requestors = ( $CurrentUser->id );
 
-    my $IsAutoGenerated = CheckForAutoGenerated($head);
+        if ($RT::ParseNewMessageForTicketCcs) {
+            @Cc = ParseCcAddressesFromHead( Head        => $head,
+                                            CurrentUser => $CurrentUser,
+                                            QueueObj    => $SystemQueueObj );
+        }
 
-    my $IsSuspiciousSender = CheckForSuspiciousSender($head);
+        my ( $id, $Transaction, $ErrStr ) = $Ticket->Create(
+                                                      Queue     => $SystemQueueObj->Id,
+                                                      Subject   => $Subject,
+                                                      Requestor => \@Requestors,
+                                                      Cc        => \@Cc,
+                                                      MIMEObj   => $Message );
+        if ( $id == 0 ) {
+            MailError( To          => $ErrorsTo,
+                       Subject     => "Ticket creation failed",
+                       Explanation => $ErrStr,
+                       MIMEObj     => $Message );
+            $RT::Logger->error("Create failed: $id / $Transaction / $ErrStr ");
+            return ( 0, "Ticket creation failed", $Ticket );
+        }
 
-    my $IsALoop = CheckForLoops($head);
+        # }}}
+    }
 
-    my $SquelchReplies = 0;
+    # }}}
 
-    #If the message is autogenerated, we need to know, so we can not
-    # send mail to the sender
-    if ( $IsBounce || $IsSuspiciousSender || $IsAutoGenerated || $IsALoop ) {
-        $SquelchReplies = 1;
-        $ErrorsTo       = $RT::OwnerEmail;
-    }
+    #   If the action is comment, add a comment.
+    elsif ( $args{'action'} =~ /^(comment|correspond)$/i ) {
+        $Ticket->Load($args{'ticket'});
+        unless ( $Ticket->Id ) {
+            my $message = "Could not find a ticket with id ".$args{'ticket'};
+            MailError( To          => $ErrorsTo,
+                     Subject     => "Message not recorded",
+                     Explanation => $message,
+                     MIMEObj     => $Message );
 
-    # Warn someone if it's a loop, before we drop it on the ground
-    if ($IsALoop) {
-        $RT::Logger->crit("RT Received mail (".$args{MessageId}.") from itself.");
+            return ( 0, $message);
+        }
 
-        #Should we mail it to RTOwner?
-        if ($RT::LoopsToRTOwner) {
-            MailError(
-                To          => $RT::OwnerEmail,
-                Subject     => "RT Bounce: ".$args{'Subject'},
-                Explanation => "RT thinks this message may be a bounce",
-                MIMEObj     => $args{Message}
-            );
+        my ( $status, $msg );
+        if ( $args{'action'} =~ /^correspond$/ ) {
+            ( $status, $msg ) = $Ticket->Correspond( MIMEObj => $Message );
         }
+        else {
+            ( $status, $msg ) = $Ticket->Comment( MIMEObj => $Message );
+        }
+        unless ($status) {
 
-        #Do we actually want to store it?
-        return ( 0, $ErrorsTo, "Message Bounced", $IsALoop ) unless ($RT::StoreLoops);
+            #Warn the sender that we couldn't actually submit the comment.
+            MailError( To          => $ErrorsTo,
+                       Subject     => "Message not recorded",
+                       Explanation => $msg,
+                       MIMEObj     => $Message );
+            return ( 0, "Message not recorded", $Ticket );
+        }
     }
 
-    # Squelch replies if necessary
-    # Don't let the user stuff the RT-Squelch-Replies-To header.
-    if ( $head->get('RT-Squelch-Replies-To') ) {
-        $head->add(
-            'RT-Relocated-Squelch-Replies-To',
-            $head->get('RT-Squelch-Replies-To')
-        );
-        $head->delete('RT-Squelch-Replies-To');
-    }
+    else {
 
-    if ($SquelchReplies) {
+        #Return mail to the sender with an error
+        MailError( To          => $ErrorsTo,
+                   Subject     => "RT Configuration error",
+                   Explanation => "'"
+                     . $args{'action'}
+                     . "' not a recognized action."
+                     . " Your RT administrator has misconfigured "
+                     . "the mail aliases which invoke RT",
+                   MIMEObj => $Message );
+        $RT::Logger->crit( $args{'action'} . " type unknown for $MessageId" );
+        return ( 0, "Configuration error: " . $args{'action'} . " not a recognized action", $Ticket );
 
-        # Squelch replies to the sender, and also leave a clue to
-        # allow us to squelch ALL outbound messages. This way we
-        # can punt the logic of "what to do when we get a bounce"
-        # to the scrip. We might want to notify nobody. Or just
-        # the RT Owner. Or maybe all Privileged watchers.
-        my ( $Sender, $junk ) = ParseSenderAddressFromHead($head);
-        $head->add( 'RT-Squelch-Replies-To',    $Sender );
-        $head->add( 'RT-DetectedAutoGenerated', 'true' );
     }
-    return ( 1, $ErrorsTo, "Handled machine detection", $IsALoop );
-}
-
-=head2 IsCorrectAction
 
-Returns a list of valid actions we've found for this message
 
-=cut
-
-sub IsCorrectAction {
-    my $action = shift;
-    my @actions = grep $_, split /-/, $action;
-    return ( 0, '(no value)' ) unless @actions;
-    foreach (@actions) {
-        return ( 0, $_ ) unless /^(?:comment|correspond|take|resolve)$/;
-    }
-    return ( 1, @actions );
+return ( 1, "Success", $Ticket );
 }
 
 eval "require RT::Interface::Email_Vendor";
-die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm} );
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Vendor.pm});
 eval "require RT::Interface::Email_Local";
-die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm} );
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Email_Local.pm});
 
 1;
index bc63f7c..5097f54 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 ## Portions Copyright 2000 Tobias Brox <tobix@fsck.com>
 
 ## This is a library of static subs to be used by the Mason web
@@ -64,171 +40,106 @@ use_ok(RT::Interface::Web);
 =cut
 
 
-use strict;
-use warnings;
-
 package RT::Interface::Web;
-use HTTP::Date;
-use RT::SavedSearches;
-use URI;
-
-# {{{ EscapeUTF8
-
-=head2 EscapeUTF8 SCALARREF
-
-does a css-busting but minimalist escaping of whatever html you're passing in.
-
-=cut
-
-sub EscapeUTF8  {
-        my  $ref = shift;
-        return unless defined $$ref;
-        my $val = $$ref;
-        use bytes;
-        $val =~ s/&/&#38;/g;
-        $val =~ s/</&lt;/g; 
-        $val =~ s/>/&gt;/g;
-        $val =~ s/\(/&#40;/g;
-        $val =~ s/\)/&#41;/g;
-        $val =~ s/"/&#34;/g;
-        $val =~ s/'/&#39;/g;
-        $$ref = $val;
-        Encode::_utf8_on($$ref);
-
-
-}
-
-# }}}
-
-# {{{ EscapeURI
-
-=head2 EscapeURI SCALARREF
+use strict;
 
-Escapes URI component according to RFC2396
 
-=cut
 
-use Encode qw();
-sub EscapeURI {
-    my $ref = shift;
-    $$ref = Encode::encode_utf8( $$ref );
-    $$ref =~ s/([^a-zA-Z0-9_.!~*'()-])/uc sprintf("%%%02X", ord($1))/eg;
-    Encode::_utf8_on( $$ref );
-}
 
-# }}}
 
-# {{{ WebCanonicalizeInfo
+# {{{ sub NewApacheHandler 
 
-=head2 WebCanonicalizeInfo();
+=head2 NewApacheHandler
 
-Different web servers set different environmental varibles. This
-function must return something suitable for REMOTE_USER. By default,
-just downcase $ENV{'REMOTE_USER'}
+  Takes extra options to pass to HTML::Mason::ApacheHandler->new
+  Returns a new Mason::ApacheHandler object
 
 =cut
 
-sub WebCanonicalizeInfo {
-    my $user;
-
-    if ( defined $ENV{'REMOTE_USER'} ) {
-        $user = lc ( $ENV{'REMOTE_USER'} ) if( length($ENV{'REMOTE_USER'}) );
-    }
+sub NewApacheHandler {
+    require HTML::Mason::ApacheHandler;
+    my $ah = new HTML::Mason::ApacheHandler( 
+    
+        comp_root                    => [
+            [ local    => $RT::MasonLocalComponentRoot ],
+            [ standard => $RT::MasonComponentRoot ]
+        ],
+        args_method => "CGI",
+        default_escape_flags => 'h',
+        allow_globals        => [qw(%session)],
+        data_dir => "$RT::MasonDataDir",
+        @_
+    );
 
-    return $user;
+    $ah->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
+    
+    return ($ah);
 }
 
 # }}}
 
-# {{{ WebExternalAutoInfo
+# {{{ sub NewCGIHandler 
 
-=head2 WebExternalAutoInfo($user);
+=head2 NewCGIHandler
 
-Returns a hash of user attributes, used when WebExternalAuto is set.
+  Returns a new Mason::CGIHandler object
 
 =cut
 
-sub WebExternalAutoInfo {
-    my $user = shift;
+sub NewCGIHandler {
+    my %args = (
+        @_
+    );
 
-    my %user_info;
+    my $handler = HTML::Mason::CGIHandler->new(
+        comp_root                    => [
+            [ local    => $RT::MasonLocalComponentRoot ],
+            [ standard => $RT::MasonComponentRoot ]
+        ],
+        data_dir => "$RT::MasonDataDir",
+        default_escape_flags => 'h',
+        allow_globals        => [qw(%session)]
+    );
+  
 
-    $user_info{'Privileged'} = 1;
+    $handler->interp->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
 
-    if ($^O !~ /^(?:riscos|MacOS|MSWin32|dos|os2)$/) {
-        # Populate fields with information from Unix /etc/passwd
 
-        my ($comments, $realname) = (getpwnam($user))[5, 6];
-        $user_info{'Comments'} = $comments if defined $comments;
-        $user_info{'RealName'} = $realname if defined $realname;
-    }
-    elsif ($^O eq 'MSWin32' and eval 'use Net::AdminMisc; 1') {
-        # Populate fields with information from NT domain controller
-    }
+    return ($handler);
 
-    # and return the wad of stuff
-    return {%user_info};
 }
-
 # }}}
 
 
+# {{{ EscapeUTF8
 
-=head2 Redirect URL
+=head2 EscapeUTF8 SCALARREF
 
-This routine ells the current user's browser to redirect to URL.  
-Additionally, it unties the user's currently active session, helping to avoid 
-A bug in Apache::Session 1.81 and earlier which clobbers sessions if we try to use 
-a cached DBI statement handle twice at the same time.
+does a css-busting but minimalist escaping of whatever html you're passing in.
 
 =cut
 
+sub EscapeUTF8  {
+        my  $ref = shift;
+        my $val = $$ref;
+        use bytes;
+        $val =~ s/&/&#38;/g;
+        $val =~ s/</&lt;/g; 
+        $val =~ s/>/&gt;/g;
+        $val =~ s/\(/&#40;/g;
+        $val =~ s/\)/&#41;/g;
+        $val =~ s/"/&#34;/g;
+        $val =~ s/'/&#39;/g;
+        $$ref = $val;
+        Encode::_utf8_on($$ref);
 
-sub Redirect {
-    my $redir_to = shift;
-    untie $HTML::Mason::Commands::session;
-    my $uri = URI->new($redir_to);
-    my $server_uri = URI->new($RT::WebURL);
-
-    # If the user is coming in via a non-canonical
-    # hostname, don't redirect them to the canonical host,
-    # it will just upset them (and invalidate their credentials)
-    if ($uri->host  eq $server_uri->host && 
-        $uri->port eq $server_uri->port) {
-            $uri->host($ENV{'HTTP_HOST'});
-            $uri->port($ENV{'SERVER_PORT'});
-        }
-
-    $HTML::Mason::Commands::m->redirect($uri->canonical);
-    $HTML::Mason::Commands::m->abort;
 }
 
-
-=head2 StaticFileHeaders 
-
-Send the browser a few headers to try to get it to (somewhat agressively)
-cache RT's static Javascript and CSS files.
-
-This routine could really use _accurate_ heuristics. (XXX TODO)
-
-=cut
-
-sub StaticFileHeaders {
-    # make cache public
-    $HTML::Mason::Commands::r->headers_out->{'Cache-Control'} = 'max-age=259200, public';
-
-    # Expire things in a month.
-    $HTML::Mason::Commands::r->headers_out->{'Expires'} = HTTP::Date::time2str( time() + 2592000 );
-
-    # if we set 'Last-Modified' then browser request a comp using 'If-Modified-Since'
-    # request, but we don't handle it and generate full reply again
-    # Last modified at server start time
-    #$HTML::Mason::Commands::r->headers_out->{'Last-Modified'} = HTTP::Date::time2str($^T);
-
-}
+# }}}
 
 
 package HTML::Mason::Commands;
+use strict;
 use vars qw/$r $m %session/;
 
 
@@ -249,13 +160,10 @@ sub loc {
         UNIVERSAL::can($session{'CurrentUser'}, 'loc')){
         return($session{'CurrentUser'}->loc(@_));
     }
-    elsif ( my $u = eval { RT::CurrentUser->new($RT::SystemUser->Id) } ) {
+    else  {
+        my $u = RT::CurrentUser->new($RT::SystemUser);
         return ($u->loc(@_));
     }
-    else {
-        # pathetic case -- SystemUser is gone.
-        return $_[0];
-    }
 }
 
 # }}}
@@ -281,7 +189,7 @@ sub loc_fuzzy {
         return($session{'CurrentUser'}->loc_fuzzy($msg));
     }
     else  {
-        my $u = RT::CurrentUser->new($RT::SystemUser->Id);
+        my $u = RT::CurrentUser->new($RT::SystemUser);
         return ($u->loc_fuzzy($msg));
     }
 }
@@ -336,30 +244,23 @@ sub CreateTicket {
     my $starts = new RT::Date( $session{'CurrentUser'} );
     $starts->Set( Format => 'unknown', Value => $ARGS{'Starts'} );
 
+    my @Requestors = split ( /\s*,\s*/, $ARGS{'Requestors'} );
+    my @Cc         = split ( /\s*,\s*/, $ARGS{'Cc'} );
+    my @AdminCc    = split ( /\s*,\s*/, $ARGS{'AdminCc'} );
+
     my $MIMEObj = MakeMIMEEntity(
         Subject             => $ARGS{'Subject'},
         From                => $ARGS{'From'},
         Cc                  => $ARGS{'Cc'},
         Body                => $ARGS{'Content'},
-        Type                => $ARGS{'ContentType'},
     );
 
-    if ( $ARGS{'Attachments'} ) {
-        my $rv = $MIMEObj->make_multipart;
-        $RT::Logger->error("Couldn't make multipart message")
-            if !$rv || $rv !~ /^(?:DONE|ALREADY)$/;
-
-        foreach ( values %{$ARGS{'Attachments'}} ) {
-            unless ( $_ ) {
-                $RT::Logger->error("Couldn't add empty attachemnt");
-                next;
-            }
-            $MIMEObj->add_part($_);
-        }
+    if ($ARGS{'Attachments'}) {
+        $MIMEObj->make_multipart;
+        $MIMEObj->add_part($_) foreach values %{$ARGS{'Attachments'}};
     }
 
     my %create_args = (
-        Type            => $ARGS{'Type'} || 'ticket',
         Queue           => $ARGS{'Queue'},
         Owner           => $ARGS{'Owner'},
         InitialPriority => $ARGS{'InitialPriority'},
@@ -367,106 +268,45 @@ sub CreateTicket {
         TimeLeft        => $ARGS{'TimeLeft'},
         TimeEstimated        => $ARGS{'TimeEstimated'},
         TimeWorked      => $ARGS{'TimeWorked'},
+        Requestor       => \@Requestors,
+        Cc              => \@Cc,
+        AdminCc         => \@AdminCc,
         Subject         => $ARGS{'Subject'},
         Status          => $ARGS{'Status'},
         Due             => $due->ISO,
         Starts          => $starts->ISO,
         MIMEObj         => $MIMEObj
     );
-
-    my @temp_squelch;
-    foreach my $type (qw(Requestors Cc AdminCc)) {
-        my @tmp = map { $_->format } grep { $_->address} Mail::Address->parse( $ARGS{ $type } );
-
-        $create_args{ $type } = [
-            grep $_, map {
-                my $user = RT::User->new( $RT::SystemUser );
-                $user->LoadOrCreateByEmail( $_ );
-                # convert to ids to avoid work later
-                $user->id;
-            } @tmp
-        ];
-        $RT::Logger->debug(
-            "$type got ".join(',',@{$create_args{ $type }}) );
-
-    }
-    # XXX: workaround for name conflict :(
-    $create_args{'Requestor'} = delete $create_args{'Requestors'};
-
-    foreach my $arg (keys %ARGS) {
-        next if $arg =~ /-(?:Magic|Category)$/;
-
-        if ($arg =~ /^Object-RT::Transaction--CustomField-/) {
-            $create_args{$arg} = $ARGS{$arg};
-        }
-        # Object-RT::Ticket--CustomField-3-Values
-        elsif ($arg =~ /^Object-RT::Ticket--CustomField-(\d+)(.*?)$/) {
-            my $cfid = $1;
-            my $cf = RT::CustomField->new( $session{'CurrentUser'});
-            $cf->Load($cfid);
-
-            if ( $cf->Type eq 'Freeform' && ! $cf->SingleValue) {
-                $ARGS{$arg} =~ s/\r\n/\n/g;
-                $ARGS{$arg} = [split('\n', $ARGS{$arg})];
-            }
-
-            if ( $cf->Type =~ /text/i) { # Catch both Text and Wikitext
-                $ARGS{$arg} =~ s/\r//g;
-            }
-
-            if ( $arg =~ /-Upload$/ ) {
-                $create_args{"CustomField-".$cfid} = _UploadedFile($arg);
-            }
-            else {
-                $create_args{"CustomField-".$cfid} = $ARGS{"$arg"};
-            }
+  foreach my $arg (%ARGS) {
+        if ($arg =~ /^CustomField-(\d+)(.*?)$/) {
+            next if ($arg =~ /-Magic$/);
+            $create_args{"CustomField-".$1} = $ARGS{"$arg"};
         }
     }
-
-
-    # XXX TODO This code should be about six lines. and badly needs refactoring.
-    # {{{ turn new link lists into arrays, and pass in the proper arguments
-    my (@dependson, @dependedonby, @parents, @children, @refersto, @referredtoby);
-
-    foreach my $luri ( split ( / /, $ARGS{"new-DependsOn"} ) ) {
-        $luri =~ s/\s*$//;    # Strip trailing whitespace
-        push @dependson, $luri;
-    }
-    $create_args{'DependsOn'} = \@dependson;
-
-    foreach my $luri ( split ( / /, $ARGS{"DependsOn-new"} ) ) {
-        push @dependedonby, $luri;
-    }
-    $create_args{'DependedOnBy'} = \@dependedonby;
-
-    foreach my $luri ( split ( / /, $ARGS{"new-MemberOf"} ) ) {
-        $luri =~ s/\s*$//;    # Strip trailing whitespace
-        push @parents, $luri;
+    my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args);
+    unless ( $id && $Trans ) {
+        Abort($ErrMsg);
     }
-    $create_args{'Parents'} = \@parents;
+    my @linktypes = qw( DependsOn MemberOf RefersTo );
 
-    foreach my $luri ( split ( / /, $ARGS{"MemberOf-new"} ) ) {
-        push @children, $luri;
-    }
-    $create_args{'Children'} = \@children;
+    foreach my $linktype (@linktypes) {
+        foreach my $luri ( split ( / /, $ARGS{"new-$linktype"} ) ) {
+            $luri =~ s/\s*$//;    # Strip trailing whitespace
+            my ( $val, $msg ) = $Ticket->AddLink(
+                Target => $luri,
+                Type   => $linktype
+            );
+            push ( @Actions, $msg ) unless ($val);
+        }
 
-    foreach my $luri ( split ( / /, $ARGS{"new-RefersTo"} ) ) {
-        $luri =~ s/\s*$//;    # Strip trailing whitespace
-        push @refersto, $luri;
-    }
-    $create_args{'RefersTo'} = \@refersto;
+        foreach my $luri ( split ( / /, $ARGS{"$linktype-new"} ) ) {
+            my ( $val, $msg ) = $Ticket->AddLink(
+                Base => $luri,
+                Type => $linktype
+            );
 
-    foreach my $luri ( split ( / /, $ARGS{"RefersTo-new"} ) ) {
-        push @referredtoby, $luri;
-    }
-    $create_args{'ReferredToBy'} = \@referredtoby;
-    # }}}
-  
-    my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(%create_args);
-    unless ( $id ) {
-        Abort($ErrMsg);
+            push ( @Actions, $msg ) unless ($val);
+        }
     }
 
     push ( @Actions, split("\n", $ErrMsg) );
@@ -525,10 +365,7 @@ sub ProcessUpdateMessage {
     );
 
     #Make the update content have no 'weird' newlines in it
-    if (   $args{ARGSRef}->{'UpdateTimeWorked'}
-        || $args{ARGSRef}->{'UpdateContent'}
-        || $args{ARGSRef}->{'UpdateAttachments'} )
-    {
+    if ( $args{ARGSRef}->{'UpdateContent'} ) {
 
         if (
             $args{ARGSRef}->{'UpdateSubject'} eq $args{'TicketObj'}->Subject() )
@@ -537,77 +374,43 @@ sub ProcessUpdateMessage {
         }
 
         my $Message = MakeMIMEEntity(
-            Subject => $args{ARGSRef}->{'UpdateSubject'},
-            Body    => $args{ARGSRef}->{'UpdateContent'},
-            Type    => $args{ARGSRef}->{'UpdateContentType'},
+            Subject             => $args{ARGSRef}->{'UpdateSubject'},
+            Body                => $args{ARGSRef}->{'UpdateContent'},
         );
 
-        $Message->head->add( 'Message-ID' => 
-              "<rt-"
-              . $RT::VERSION . "-"
-              . $$ . "-"
-              . CORE::time() . "-"
-              . int(rand(2000)) . "."
-              . $args{'TicketObj'}->id . "-"
-              . "0" . "-"  # Scrip
-              . "0" . "@"  # Email sent
-              . $RT::Organization
-              . ">" );
-        my $old_txn = RT::Transaction->new( $session{'CurrentUser'} );
-        if ( $args{ARGSRef}->{'QuoteTransaction'} ) {
-            $old_txn->Load( $args{ARGSRef}->{'QuoteTransaction'} );
-        }
-        else {
-            $old_txn = $args{TicketObj}->Transactions->First();
+        if ($args{ARGSRef}->{'UpdateAttachments'}) {
+            $Message->make_multipart;
+            $Message->add_part($_) foreach values %{$args{ARGSRef}->{'UpdateAttachments'}};
         }
 
-        if ( $old_txn->Message && $old_txn->Message->First ) {
-            my @in_reply_to = split(/\s+/m, $old_txn->Message->First->GetHeader('In-Reply-To') || '');  
-            my @references = split(/\s+/m, $old_txn->Message->First->GetHeader('References') || '' );  
-            my @msgid = split(/\s+/m,$old_txn->Message->First->GetHeader('Message-ID') || ''); 
-            my @rtmsgid = split(/\s+/m,$old_txn->Message->First->GetHeader('RT-Message-ID') || ''); 
-
-            $Message->head->replace( 'In-Reply-To', join (' ', @rtmsgid ? @rtmsgid : @msgid));
-            $Message->head->replace( 'References', join(' ', @references, @msgid, @rtmsgid));
+        ## TODO: Implement public comments
+        if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
+            my ( $Transaction, $Description ) = $args{TicketObj}->Comment(
+                CcMessageTo  => $args{ARGSRef}->{'UpdateCc'},
+                BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
+                MIMEObj      => $Message,
+                TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
+            );
+            push ( @{ $args{Actions} }, $Description );
+        }
+        elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
+            my ( $Transaction, $Description ) = $args{TicketObj}->Correspond(
+                CcMessageTo  => $args{ARGSRef}->{'UpdateCc'},
+                BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
+                MIMEObj      => $Message,
+                TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
+            );
+            push ( @{ $args{Actions} }, $Description );
+        }
+        else {
+            push ( @{ $args{'Actions'} },
+                loc("Update type was neither correspondence nor comment.").
+                " ".
+                loc("Update not recorded.")
+            );
         }
-
-    if ( $args{ARGSRef}->{'UpdateAttachments'} ) {
-        $Message->make_multipart;
-        $Message->add_part($_)
-          foreach values %{ $args{ARGSRef}->{'UpdateAttachments'} };
-    }
-
-    ## TODO: Implement public comments
-    if ( $args{ARGSRef}->{'UpdateType'} =~ /^(private|public)$/ ) {
-        my ( $Transaction, $Description, $Object ) = $args{TicketObj}->Comment(
-            CcMessageTo  => $args{ARGSRef}->{'UpdateCc'},
-            BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
-            MIMEObj      => $Message,
-            TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
-        );
-        push( @{ $args{Actions} }, $Description );
-        $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
-    }
-    elsif ( $args{ARGSRef}->{'UpdateType'} eq 'response' ) {
-        my ( $Transaction, $Description, $Object ) =
-          $args{TicketObj}->Correspond(
-            CcMessageTo  => $args{ARGSRef}->{'UpdateCc'},
-            BccMessageTo => $args{ARGSRef}->{'UpdateBcc'},
-            MIMEObj      => $Message,
-            TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
-          );
-        push( @{ $args{Actions} }, $Description );
-        $Object->UpdateCustomFields( ARGSRef => $args{ARGSRef} ) if $Object;
-    }
-    else {
-        push(
-            @{ $args{'Actions'} },
-            loc("Update type was neither correspondence nor comment.") . " "
-              . loc("Update not recorded.")
-        );
     }
 }
-}
 
 # }}}
 
@@ -617,8 +420,6 @@ sub ProcessUpdateMessage {
 
 Takes a paramhash Subject, Body and AttachmentFieldName.
 
-Also takes Form, Cc and Type as optional paramhash keys.
-
   Returns a MIME::Entity.
 
 =cut
@@ -632,14 +433,12 @@ sub MakeMIMEEntity {
         Cc                  => undef,
         Body                => undef,
         AttachmentFieldName => undef,
-        Type                => undef,
-#        map Encode::encode_utf8($_), @_,
-        @_,
+        map Encode::encode_utf8($_), @_,
     );
 
     #Make the update content have no 'weird' newlines in it
 
-    $args{'Body'} =~ s/\r\n/\n/gs if $args{'Body'};
+    $args{'Body'} =~ s/\r\n/\n/gs;
     my $Message;
     {
         # MIME::Head is not happy in utf-8 domain.  This only happens
@@ -650,8 +449,6 @@ sub MakeMIMEEntity {
             Subject => $args{'Subject'} || "",
             From    => $args{'From'},
             Cc      => $args{'Cc'},
-            Type    => $args{'Type'} || 'text/plain',
-            'Charset:' => 'utf8',
             Data    => [ $args{'Body'} ]
         );
     }
@@ -666,14 +463,7 @@ sub MakeMIMEEntity {
 
     #foreach my $filehandle (@filenames) {
 
-    my ( $fh, $temp_file );
-    for ( 1 .. 10 ) {
-        # on NFS and NTFS, it is possible that tempfile() conflicts
-        # with other processes, causing a race condition. we try to
-        # accommodate this by pausing and retrying.
-        last if ($fh, $temp_file) = eval { tempfile( UNLINK => 1) };
-        sleep 1;
-    }
+    my ( $fh, $temp_file ) = tempfile();
 
     binmode $fh;    #thank you, windows
     my ($buffer);
@@ -691,7 +481,7 @@ sub MakeMIMEEntity {
 
     $Message->attach(
         Path     => $temp_file,
-        Filename => Encode::decode_utf8($filename),
+        Filename => $filename,
         Type     => $uploadinfo->{'Content-Type'},
     );
     close($fh);
@@ -804,13 +594,13 @@ sub ProcessSearchQuery {
 
     # }}}
     # {{{ Limit requestor email
-     if ( $args{ARGS}->{'ValueOfWatcherRole'} ne '' ) {
-         $session{'tickets'}->LimitWatcher(
-             TYPE     => $args{ARGS}->{'WatcherRole'},
-             VALUE    => $args{ARGS}->{'ValueOfWatcherRole'},
-             OPERATOR => $args{ARGS}->{'WatcherRoleOp'},
 
+    if ( $args{ARGS}->{'ValueOfRequestor'} ne '' ) {
+        my $alias = $session{'tickets'}->LimitRequestor(
+            VALUE    => $args{ARGS}->{'ValueOfRequestor'},
+            OPERATOR => $args{ARGS}->{'RequestorOp'},
         );
+
     }
 
     # }}}
@@ -955,6 +745,19 @@ sub ParseDateToISO {
 
 # }}}
 
+# {{{ sub Config 
+# TODO: This might eventually read the cookies, user configuration
+# information from the DB, queue configuration information from the
+# DB, etc.
+
+sub Config {
+    my $args = shift;
+    my $key  = shift;
+    return $args->{$key} || $RT::WebOptions{$key};
+}
+
+# }}}
+
 # {{{ sub ProcessACLChanges
 
 sub ProcessACLChanges {
@@ -977,13 +780,17 @@ sub ProcessACLChanges {
 
             my $obj;
 
-             if ($object_type eq 'RT::System') {
-                $obj = $RT::System;
-            } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) {
-                $obj = $object_type->new($session{'CurrentUser'});
+            if ($object_type eq 'RT::Queue') {
+                $obj = RT::Queue->new($session{'CurrentUser'});
+                $obj->Load($object_id);      
+            } elsif ($object_type eq 'RT::Group') {
+                $obj = RT::Group->new($session{'CurrentUser'});
                 $obj->Load($object_id);      
+
+            } elsif ($object_type eq 'RT::System') {
+                $obj = $RT::System;
             } else {
-                push (@results, loc("System Error"). ': '.
+                push (@results, loc("System Error").
                                 loc("Rights could not be granted for [_1]", $object_type));
                 next;
             }
@@ -1006,13 +813,17 @@ sub ProcessACLChanges {
             next unless ($right);
             my $obj;
 
-             if ($object_type eq 'RT::System') {
-                $obj = $RT::System;
-            } elsif ($RT::ACE::OBJECT_TYPES{$object_type}) {
-                $obj = $object_type->new($session{'CurrentUser'});
+            if ($object_type eq 'RT::Queue') {
+                $obj = RT::Queue->new($session{'CurrentUser'});
+                $obj->Load($object_id);      
+            } elsif ($object_type eq 'RT::Group') {
+                $obj = RT::Group->new($session{'CurrentUser'});
                 $obj->Load($object_id);      
+
+            } elsif ($object_type eq 'RT::System') {
+                $obj = $RT::System;
             } else {
-                push (@results, loc("System Error"). ': '.
+                push (@results, loc("System Error").
                                 loc("Rights could not be revoked for [_1]", $object_type));
                 next;
             }
@@ -1048,12 +859,52 @@ sub UpdateRecordObject {
         @_
     );
 
-    my $Object = $args{'Object'};
-    my @results = $Object->Update(AttributesRef => $args{'AttributesRef'},
-                                  ARGSRef       => $args{'ARGSRef'},
-                  AttributePrefix => $args{'AttributePrefix'}
-                                  );
+    my (@results);
+
+    my $object     = $args{'Object'};
+    my $attributes = $args{'AttributesRef'};
+    my $ARGSRef    = $args{'ARGSRef'};
+    foreach my $attribute (@$attributes) {
+        my $value;
+        if ( defined $ARGSRef->{$attribute} ) {
+            $value = $ARGSRef->{$attribute};
+        }
+        elsif (
+              defined( $args{'AttributePrefix'} )
+              && defined(
+                  $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute }
+              )
+          ) {
+            $value = $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute };
+
+        } else {
+                next;
+        }
 
+            $value =~ s/\r\n/\n/gs;
+
+        if ($value ne $object->$attribute()){
+
+              my $method = "Set$attribute";
+              my ( $code, $msg ) = $object->$method($value);
+
+              push @results, loc($attribute) . ': ' . loc_fuzzy($msg);
+=for loc
+                                   "[_1] could not be set to [_2].",       # loc
+                                   "That is already the current value",    # loc
+                                   "No value sent to _Set!\n",             # loc
+                                   "Illegal value for [_1]",               # loc
+                                   "The new value has been set.",          # loc
+                                   "No column specified",                  # loc
+                                   "Immutable field",                      # loc
+                                   "Nonexistant field?",                   # loc
+                                   "Invalid data",                         # loc
+                                   "Couldn't find row",                    # loc
+                                   "Missing a primary key?: [_1]",         # loc
+                                   "Found Object",                         # loc
+=cut
+          };
+    }
     return (@results);
 }
 
@@ -1102,17 +953,6 @@ sub ProcessCustomFieldUpdates {
         my ( $err, $msg ) = $Object->DeleteValue($id);
         push ( @results, $msg );
     }
-
-    my $vals = $Object->Values();
-    while (my $cfv = $vals->Next()) {
-        if (my $so = $ARGSRef->{ 'CustomField-' . $Object->Id . '-SortOrder' . $cfv->Id }) {
-            if ($cfv->SortOrder != $so) {
-                my ( $err, $msg ) = $cfv->SetSortOrder($so);
-                push ( @results, $msg );
-            }
-        }
-    }
-
     return (@results);
 }
 
@@ -1145,12 +985,10 @@ sub ProcessTicketBasics {
       TimeEstimated
       TimeWorked
       TimeLeft
-      Type
       Status
       Queue
     );
 
-
     if ( $ARGSRef->{'Queue'} and ( $ARGSRef->{'Queue'} !~ /^(\d+)$/ ) ) {
         my $tempqueue = RT::Queue->new($RT::SystemUser);
         $tempqueue->Load( $ARGSRef->{'Queue'} );
@@ -1159,11 +997,6 @@ sub ProcessTicketBasics {
         }
     }
 
-
-   # Status isn't a field that can be set to a null value.
-   # RT core complains if you try
-    delete $ARGSRef->{'Status'} unless ($ARGSRef->{'Status'});
-    
     my @results = UpdateRecordObject(
         AttributesRef => \@attribs,
         Object        => $TicketObj,
@@ -1192,205 +1025,142 @@ sub ProcessTicketBasics {
 
 # }}}
 
-sub ProcessTicketCustomFieldUpdates {
-    my %args = @_;
-    $args{'Object'} = delete $args{'TicketObj'};
-    my $ARGSRef = { %{ $args{'ARGSRef'} } };
+# {{{ Sub ProcessTicketCustomFieldUpdates
 
-    # Build up a list of objects that we want to work with
-    my %custom_fields_to_mod;
-    foreach my $arg ( keys %$ARGSRef ) {
-        if ( $arg =~ /^Ticket-(\d+-.*)/) {
-            $ARGSRef->{"Object-RT::Ticket-$1"} = delete $ARGSRef->{$arg};
-        }
-        elsif ( $arg =~ /^CustomField-(\d+-.*)/) {
-            $ARGSRef->{"Object-RT::Ticket--$1"} = delete $ARGSRef->{$arg};
-        }
-    }
+sub ProcessTicketCustomFieldUpdates {
+    my %args = (
+        ARGSRef => undef,
+        @_
+    );
 
-    return ProcessObjectCustomFieldUpdates(%args, ARGSRef => $ARGSRef);
-}
+    my @results;
 
-sub ProcessObjectCustomFieldUpdates {
-    my %args = @_;
     my $ARGSRef = $args{'ARGSRef'};
-    my @results;
 
-    # Build up a list of objects that we want to work with
+    # Build up a list of tickets that we want to work with
+    my %tickets_to_mod;
     my %custom_fields_to_mod;
-    foreach my $arg ( keys %$ARGSRef ) {
-        # format: Object-<object class>-<object id>-CustomField-<CF id>-<commands>
-        next unless $arg =~ /^Object-([\w:]+)-(\d*)-CustomField-(\d+)-(.*)$/;
-
-        # For each of those objects, find out what custom fields we want to work with.
-        $custom_fields_to_mod{ $1 }{ $2 || 0 }{ $3 }{ $4 } = $ARGSRef->{ $arg };
-    }
-
-    # For each of those objects
-    foreach my $class ( keys %custom_fields_to_mod ) {
-        foreach my $id ( keys %{$custom_fields_to_mod{$class}} ) {
-            my $Object = $args{'Object'};
-            $Object = $class->new( $session{'CurrentUser'} )
-                unless $Object && ref $Object eq $class;
-
-            $Object->Load( $id ) unless ($Object->id || 0) == $id;
-            unless ( $Object->id ) {
-                $RT::Logger->warning("Couldn't load object $class #$id");
-                next;
-            }
+    foreach my $arg ( keys %{$ARGSRef} ) {
+        if ( $arg =~ /^Ticket-(\d+)-CustomField-(\d+)-/ ) {
 
-            foreach my $cf ( keys %{ $custom_fields_to_mod{ $class }{ $id } } ) {
-                my $CustomFieldObj = RT::CustomField->new( $session{'CurrentUser'} );
-                $CustomFieldObj->LoadById( $cf );
-                unless ( $CustomFieldObj->id ) {
-                    $RT::Logger->warning("Couldn't load custom field #$id");
-                    next;
-                }
-                push @results, _ProcessObjectCustomFieldUpdates(
-                    Prefix      => "Object-$class-$id-CustomField-$cf-",
-                    Object      => $Object,
-                    CustomField => $CustomFieldObj,
-                    ARGS        => $custom_fields_to_mod{$class}{$id}{$cf},
-                );
-            }
+            # For each of those tickets, find out what custom fields we want to work with.
+            $custom_fields_to_mod{$1}{$2} = 1;
         }
     }
-    return @results;
-}
-
-sub _ProcessObjectCustomFieldUpdates {
-    my %args = @_;
-    my $cf = $args{'CustomField'};
-    my $cf_type = $cf->Type;
-
-    my @results;
-    foreach my $arg ( keys %{ $args{'ARGS'} } ) {
-        
-        next if $arg =~ /Category$/;
 
-        # since http won't pass in a form element with a null value, we need
-        # to fake it
-        if ( $arg eq 'Values-Magic' ) {
-            # We don't care about the magic, if there's really a values element;
-            next if $args{'ARGS'}->{'Value'} || $args{'ARGS'}->{'Values'};
+    # For each of those tickets
+    foreach my $tick ( keys %custom_fields_to_mod ) {
+        my $Ticket = RT::Ticket->new( $session{'CurrentUser'} );
+        $Ticket->Load($tick);
 
-            # "Empty" values does not mean anything for Image and Binary fields
-            next if $cf_type =~ /^(?:Image|Binary)$/;
+        # For each custom field  
+        foreach my $cf ( keys %{ $custom_fields_to_mod{$tick} } ) {
 
-            $arg = 'Values';
-            $args{'ARGS'}->{'Values'} = undef;
-        }
+           my $CustomFieldObj = RT::CustomField->new($session{'CurrentUser'});
+           $CustomFieldObj->LoadById($cf);
 
-        my @values = ();
-        if ( ref $args{'ARGS'}->{ $arg } eq 'ARRAY' ) {
-            @values = @{ $args{'ARGS'}->{$arg} };
-        } elsif ( $cf_type =~ /text/i ) { # Both Text and Wikitext
-            @values = ($args{'ARGS'}->{$arg});
-        } elsif ( defined( $args{'ARGS'}->{ $arg } ) ) {
-            @values = split /\n/, $args{'ARGS'}->{ $arg };
-        }
-        
-        if ( ( $cf_type eq 'Freeform' && !$cf->SingleValue ) || $cf_type =~ /text/i ) {
-            s/\r//g foreach @values;
-        }
-        @values = grep defined && $_ ne '', @values;
+            foreach my $arg ( keys %{$ARGSRef} ) {
+                # since http won't pass in a form element with a null value, we need
+                # to fake it
+                if ($arg =~ /^(.*?)-Values-Magic$/ ) {
+                    # We don't care about the magic, if there's really a values element;
+                    next if (exists $ARGSRef->{$1.'-Values'}) ;
 
-        if ( $arg eq 'AddValue' || $arg eq 'Value' ) {
-            foreach my $value (@values) {
-                my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue(
-                    Field => $cf->id,
-                    Value => $value
-                );
-                push ( @results, $msg );
-            }
-        }
-        elsif ( $arg eq 'Upload' ) {
-            my $value_hash = _UploadedFile( $args{'Prefix'} . $arg ) or next;
-            my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue(
-                %$value_hash,
-                Field => $cf,
-            );
-            push ( @results, $msg );
-        }
-        elsif ( $arg eq 'DeleteValues' ) {
-            foreach my $value ( @values ) {
-                my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue(
-                    Field => $cf,
-                    Value => $value,
-                );
-                push ( @results, $msg );
-            }
-        }
-        elsif ( $arg eq 'DeleteValueIds' ) {
-            foreach my $value ( @values ) {
-                my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue(
-                    Field   => $cf,
-                    ValueId => $value,
-                );
-                push ( @results, $msg );
-            }
-        }
-        elsif ( $arg eq 'Values' && !$cf->Repeated ) {
-            my $cf_values = $args{'Object'}->CustomFieldValues( $cf->id );
-
-            my %values_hash;
-            foreach my $value ( @values ) {
-                # build up a hash of values that the new set has
-                $values_hash{$value} = 1;
-                next if $cf_values->HasEntry( $value );
-
-                my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue(
-                    Field => $cf,
-                    Value => $value
-                );
-                push ( @results, $msg );
-            }
-
-            $cf_values->RedoSearch;
-            while ( my $cf_value = $cf_values->Next ) {
-                next if $values_hash{ $cf_value->Content };
-
-                my ( $val, $msg ) = $args{'Object'}->DeleteCustomFieldValue(
-                    Field => $cf,
-                    Value => $cf_value->Content
-                );
-                push ( @results, $msg);
-            }
-        }
-        elsif ( $arg eq 'Values' ) {
-            my $cf_values = $args{'Object'}->CustomFieldValues( $cf->id );
-
-            # keep everything up to the point of difference, delete the rest
-            my $delete_flag;
-            foreach my $old_cf (@{$cf_values->ItemsArrayRef}) {
-                if (!$delete_flag and @values and $old_cf->Content eq $values[0]) {
-                    shift @values;
-                    next;
+                    $arg = $1."-Values";
+                    $ARGSRef->{$1."-Values"} = undef;
+                
+                }
+                next unless ( $arg =~ /^Ticket-$tick-CustomField-$cf-/ );
+                my @values =
+                  ( ref( $ARGSRef->{$arg} ) eq 'ARRAY' ) 
+                  ? @{ $ARGSRef->{$arg} }
+                  : ( $ARGSRef->{$arg} );
+                if ( ( $arg =~ /-AddValue$/ ) || ( $arg =~ /-Value$/ ) ) {
+                    foreach my $value (@values) {
+                        next unless ($value);
+                        my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
+                            Field => $cf,
+                            Value => $value
+                        );
+                        push ( @results, $msg );
+                    }
+                }
+                elsif ( $arg =~ /-DeleteValues$/ ) {
+                    foreach my $value (@values) {
+                        next unless ($value);
+                        my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue(
+                            Field => $cf,
+                            Value => $value
+                        );
+                        push ( @results, $msg );
+                    }
+                }
+                elsif ( $arg =~ /-Values$/ and $CustomFieldObj->Type !~ /Entry/) {
+                    my $cf_values = $Ticket->CustomFieldValues($cf);
+
+                    my %values_hash;
+                    foreach my $value (@values) {
+                        next unless ($value);
+
+                        # build up a hash of values that the new set has
+                        $values_hash{$value} = 1;
+
+                        unless ( $cf_values->HasEntry($value) ) {
+                            my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
+                                Field => $cf,
+                                Value => $value
+                            );
+                            push ( @results, $msg );
+                        }
+
+                    }
+                    while ( my $cf_value = $cf_values->Next ) {
+                        unless ( $values_hash{ $cf_value->Content } == 1 ) {
+                            my ( $val, $msg ) = $Ticket->DeleteCustomFieldValue(
+                                Field => $cf,
+                                Value => $cf_value->Content
+                            );
+                            push ( @results, $msg);
+
+                        }
+
+                    }
+                }
+                elsif ( $arg =~ /-Values$/ ) {
+                    my $cf_values = $Ticket->CustomFieldValues($cf);
+
+                   # keep everything up to the point of difference, delete the rest
+                   my $delete_flag;
+                   foreach my $old_cf (@{$cf_values->ItemsArrayRef}) {
+                       if (!$delete_flag and @values and $old_cf->Content eq $values[0]) {
+                           shift @values;
+                           next;
+                       }
+
+                       $delete_flag ||= 1;
+                       $old_cf->Delete;
+                   }
+
+                   # now add/replace extra things, if any
+                   foreach my $value (@values) {
+                       my ( $val, $msg ) = $Ticket->AddCustomFieldValue(
+                           Field => $cf,
+                           Value => $value
+                       );
+                       push ( @results, $msg );
+                   }
+               }
+                else {
+                    push ( @results, "User asked for an unknown update type for custom field " . $cf->Name . " for ticket " . $Ticket->id );
                 }
-
-                $delete_flag ||= 1;
-                $old_cf->Delete;
-            }
-
-            # now add/replace extra things, if any
-            foreach my $value ( @values ) {
-                my ( $val, $msg ) = $args{'Object'}->AddCustomFieldValue(
-                    Field => $cf,
-                    Value => $value
-                );
-                push ( @results, $msg );
             }
         }
-        else {
-            push ( @results,
-                loc("User asked for an unknown update type for custom field [_1] for [_2] object #[_3]",
-                $cf->Name, ref $args{'Object'}, $args{'Object'}->id )
-            );
-        }
+        return (@results);
     }
-    return @results;
 }
 
+# }}}
+
 # {{{ sub ProcessTicketWatchers
 
 =head2 ProcessTicketWatchers ( TicketObj => $Ticket, ARGSRef => \%ARGS );
@@ -1410,30 +1180,27 @@ sub ProcessTicketWatchers {
     my $Ticket  = $args{'TicketObj'};
     my $ARGSRef = $args{'ARGSRef'};
 
-    # Munge watchers
+    # {{{ Munge watchers
 
     foreach my $key ( keys %$ARGSRef ) {
 
-        # Delete deletable watchers
-        if ( ( $key =~ /^Ticket-DeleteWatcher-Type-(.*)-Principal-(\d+)$/ ) )
-        {
-            my ( $code, $msg ) = $Ticket->DeleteWatcher(
-                PrincipalId => $2,
-                Type        => $1
-            );
+        # {{{ Delete deletable watchers
+        if ( ( $key =~ /^Ticket-DelWatcher-Type-(.*)-Principal-(\d+)$/ )  ) {
+            my ( $code, $msg ) = 
+                $Ticket->DeleteWatcher(PrincipalId => $2,
+                                       Type => $1);
             push @results, $msg;
         }
 
         # Delete watchers in the simple style demanded by the bulk manipulator
         elsif ( $key =~ /^Delete(Requestor|Cc|AdminCc)$/ ) {
-            my ( $code, $msg ) = $Ticket->DeleteWatcher(
-                Email => $ARGSRef->{$key},
-                Type  => $1
-            );
+            my ( $code, $msg ) = $Ticket->DeleteWatcher( Type => $ARGSRef->{$key}, PrincipalId => $1 );
             push @results, $msg;
         }
 
-        # Add new wathchers by email address
+        # }}}
+
+        # Add new wathchers by email address      
         elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )
             and ( $key =~ /^WatcherTypeEmail(\d*)$/ ) )
         {
@@ -1456,21 +1223,18 @@ sub ProcessTicketWatchers {
         }
 
         # Add new  watchers by owner
-        elsif ( $key =~ /^Ticket-AddWatcher-Principal-(\d*)$/ ) {
-            my $principal_id = $1;
-            my $form = $ARGSRef->{$key};
-            foreach my $value ( ref($form) ? @{$form} : ($form) ) {
-                next unless $value =~ /^(?:AdminCc|Cc|Requestor)$/i;
+        elsif ( ( $ARGSRef->{$key} =~ /^(AdminCc|Cc|Requestor)$/ )
+            and ( $key =~ /^Ticket-AddWatcher-Principal-(\d*)$/ ) ) {
 
-                my ( $code, $msg ) = $Ticket->AddWatcher(
-                    Type        => $value,
-                    PrincipalId => $principal_id
-                );
-                push @results, $msg;
-            }
+            #They're in this order because otherwise $1 gets clobbered :/
+            my ( $code, $msg ) =
+              $Ticket->AddWatcher( Type => $ARGSRef->{$key}, PrincipalId => $1 );
+            push @results, $msg;
         }
-
     }
+
+    # }}}
+
     return (@results);
 }
 
@@ -1512,7 +1276,7 @@ sub ProcessTicketDates {
         my $DateObj = RT::Date->new( $session{'CurrentUser'} );
 
         #If it's something other than just whitespace
-        if ( $ARGSRef->{ $field . '_Date' } && ($ARGSRef->{ $field . '_Date' } ne '') ) {
+        if ( $ARGSRef->{ $field . '_Date' } ne '' ) {
             $DateObj->Set(
                 Format => 'unknown',
                 Value  => $ARGSRef->{ $field . '_Date' }
@@ -1550,30 +1314,6 @@ sub ProcessTicketLinks {
     my $Ticket  = $args{'TicketObj'};
     my $ARGSRef = $args{'ARGSRef'};
 
-
-    my (@results) = ProcessRecordLinks(RecordObj => $Ticket,
-                                       ARGSRef => $ARGSRef);
-
-    #Merge if we need to
-    if ( $ARGSRef->{ $Ticket->Id . "-MergeInto" } ) {
-        my ( $val, $msg ) =
-          $Ticket->MergeInto( $ARGSRef->{ $Ticket->Id . "-MergeInto" } );
-        push @results, $msg;
-    }
-
-    return (@results);
-}
-
-# }}}
-
-sub ProcessRecordLinks {
-    my %args = ( RecordObj => undef,
-                 ARGSRef   => undef,
-                 @_ );
-
-    my $Record  = $args{'RecordObj'};
-    my $ARGSRef = $args{'ARGSRef'};
-
     my (@results);
 
     # Delete links that are gone gone gone.
@@ -1585,7 +1325,7 @@ sub ProcessRecordLinks {
 
             push @results,
               "Trying to delete: Base: $base Target: $target  Type $type";
-            my ( $val, $msg ) = $Record->DeleteLink( Base   => $base,
+            my ( $val, $msg ) = $Ticket->DeleteLink( Base   => $base,
                                                      Type   => $type,
                                                      Target => $target );
 
@@ -1598,18 +1338,18 @@ sub ProcessRecordLinks {
     my @linktypes = qw( DependsOn MemberOf RefersTo );
 
     foreach my $linktype (@linktypes) {
-        if ( $ARGSRef->{ $Record->Id . "-$linktype" } ) {
-            for my $luri ( split ( / /, $ARGSRef->{ $Record->Id . "-$linktype" } ) ) {
+        if ( $ARGSRef->{ $Ticket->Id . "-$linktype" } ) {
+            for my $luri ( split ( / /, $ARGSRef->{ $Ticket->Id . "-$linktype" } ) ) {
                 $luri =~ s/\s*$//;    # Strip trailing whitespace
-                my ( $val, $msg ) = $Record->AddLink( Target => $luri,
+                my ( $val, $msg ) = $Ticket->AddLink( Target => $luri,
                                                       Type   => $linktype );
                 push @results, $msg;
             }
         }
-        if ( $ARGSRef->{ "$linktype-" . $Record->Id } ) {
+        if ( $ARGSRef->{ "$linktype-" . $Ticket->Id } ) {
 
-            for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Record->Id } ) ) {
-                my ( $val, $msg ) = $Record->AddLink( Base => $luri,
+            for my $luri ( split ( / /, $ARGSRef->{ "$linktype-" . $Ticket->Id } ) ) {
+                my ( $val, $msg ) = $Ticket->AddLink( Base => $luri,
                                                       Type => $linktype );
 
                 push @results, $msg;
@@ -1617,68 +1357,18 @@ sub ProcessRecordLinks {
         } 
     }
 
-    return (@results);
-}
-
-
-=head2 _UploadedFile ( $arg );
-
-Takes a CGI parameter name; if a file is uploaded under that name,
-return a hash reference suitable for AddCustomFieldValue's use:
-C<( Value => $filename, LargeContent => $content, ContentType => $type )>.
-
-Returns C<undef> if no files were uploaded in the C<$arg> field.
-
-=cut
-
-sub _UploadedFile {
-    my $arg = shift;
-    my $cgi_object = $m->cgi_object;
-    my $fh = $cgi_object->upload($arg) or return undef;
-    my $upload_info = $cgi_object->uploadInfo($fh);
-
-    my $filename = "$fh";
-    $filename =~ s#^.*[\\/]##;
-    binmode($fh);
-
-    return {
-        Value => $filename,
-        LargeContent => do { local $/; scalar <$fh> },
-        ContentType => $upload_info->{'Content-Type'},
-    };
-}
-
-=head2 _load_container_object ( $type, $id );
-
-Instantiate container object for saving searches.
-
-=cut
-
-sub _load_container_object {
-    my ($obj_type, $obj_id) = @_;
-    return RT::SavedSearch->new($session{'CurrentUser'})->_load_privacy_object($obj_type, $obj_id);
-}
-
-=head2 _parse_saved_search ( $arg );
-
-Given a serialization string for saved search, and returns the
-container object and the search id.
-
-=cut
-
-sub _parse_saved_search {
-    my $spec = shift;
-    return unless $spec;
-    if ($spec  !~ /^(.*?)-(\d+)-SavedSearch-(\d+)$/ ) {
-        return;
+    #Merge if we need to
+    if ( $ARGSRef->{ $Ticket->Id . "-MergeInto" } ) {
+        my ( $val, $msg ) =
+          $Ticket->MergeInto( $ARGSRef->{ $Ticket->Id . "-MergeInto" } );
+        push @results, $msg;
     }
-    my $obj_type  = $1;
-    my $obj_id    = $2;
-    my $search_id = $3;
 
-    return (_load_container_object ($obj_type, $obj_id), $search_id);
+    return (@results);
 }
 
+# }}}
+
 eval "require RT::Interface::Web_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web_Vendor.pm});
 eval "require RT::Interface::Web_Local";
diff --git a/rt/lib/RT/Interface/Web_Vendor.pm b/rt/lib/RT/Interface/Web_Vendor.pm
new file mode 100644 (file)
index 0000000..5be20e6
--- /dev/null
@@ -0,0 +1,95 @@
+# 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.
+
+=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,
+        @_
+    );
+    my @results = ();
+
+    my $Ticket  = $args{'TicketObj'};
+    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 ) = $Ticket->DeleteLink( Base   => $base,
+                                                     Type   => $type,
+                                                     Target => $target );
+
+            push @results, $msg;
+
+        }
+
+    }
+    ###
+
+    my @delete_custnums =
+      map  { /^Ticket-AddCustomer-(\d+)$/; $1 }
+      grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
+      keys %$ARGSRef;
+
+    my @custnums = map  { /^Ticket-AddCustomer-(\d+)$/; $1 }
+                   grep { /^Ticket-AddCustomer-(\d+)$/ && $ARGSRef->{$_} }
+                   keys %$ARGSRef;
+
+    foreach my $custnum ( @custnums ) {
+      my( $val, $msg ) =
+        $Ticket->AddLink( 'Type'   => 'MemberOf',
+                          'Target' => "freeside://freeside/cust_main/$custnum",
+                        );
+      push @results, $msg;
+    }
+
+    return @results;
+
+}
+
+1;
+
index 72ad1d5..962c378 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -122,7 +98,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -131,14 +107,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 Base
+=item Base
 
 Returns the current value of Base. 
 (In the database, Base is stored as varchar(240).)
 
 
 
-=head2 SetBase VALUE
+=item SetBase VALUE
 
 
 Set Base to VALUE. 
@@ -149,14 +125,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Target
+=item Target
 
 Returns the current value of Target. 
 (In the database, Target is stored as varchar(240).)
 
 
 
-=head2 SetTarget VALUE
+=item SetTarget VALUE
 
 
 Set Target to VALUE. 
@@ -167,14 +143,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Type
+=item Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(20).)
 
 
 
-=head2 SetType VALUE
+=item SetType VALUE
 
 
 Set Type to VALUE. 
@@ -185,14 +161,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 LocalTarget
+=item LocalTarget
 
 Returns the current value of LocalTarget. 
 (In the database, LocalTarget is stored as int(11).)
 
 
 
-=head2 SetLocalTarget VALUE
+=item SetLocalTarget VALUE
 
 
 Set LocalTarget to VALUE. 
@@ -203,14 +179,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 LocalBase
+=item LocalBase
 
 Returns the current value of LocalBase. 
 (In the database, LocalBase is stored as int(11).)
 
 
 
-=head2 SetLocalBase VALUE
+=item SetLocalBase VALUE
 
 
 Set LocalBase to VALUE. 
@@ -221,7 +197,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 LastUpdatedBy
+=item LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -230,7 +206,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=head2 LastUpdated
+=item LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -239,7 +215,7 @@ Returns the current value of LastUpdated.
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -248,7 +224,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -258,29 +234,29 @@ Returns the current value of Created.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         Base => 
-               {read => 1, write => 1, sql_type => 12, length => 240,  is_blob => 0,  is_numeric => 0,  type => 'varchar(240)', default => ''},
+               {read => 1, write => 1, type => 'varchar(240)', default => ''},
         Target => 
-               {read => 1, write => 1, sql_type => 12, length => 240,  is_blob => 0,  is_numeric => 0,  type => 'varchar(240)', default => ''},
+               {read => 1, write => 1, type => 'varchar(240)', default => ''},
         Type => 
-               {read => 1, write => 1, sql_type => 12, length => 20,  is_blob => 0,  is_numeric => 0,  type => 'varchar(20)', default => ''},
+               {read => 1, write => 1, type => 'varchar(20)', default => ''},
         LocalTarget => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         LocalBase => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         LastUpdatedBy => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
 
  }
 };
@@ -312,7 +288,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 5c21e2d..7a1773a 100644 (file)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::Link item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 3b4681a..b362c9f 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -131,7 +107,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -140,14 +116,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 Name
+=item Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=head2 SetName VALUE
+=item SetName VALUE
 
 
 Set Name to VALUE. 
@@ -158,14 +134,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Description
+=item Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=head2 SetDescription VALUE
+=item SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -176,14 +152,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 CorrespondAddress
+=item CorrespondAddress
 
 Returns the current value of CorrespondAddress. 
 (In the database, CorrespondAddress is stored as varchar(120).)
 
 
 
-=head2 SetCorrespondAddress VALUE
+=item SetCorrespondAddress VALUE
 
 
 Set CorrespondAddress to VALUE. 
@@ -194,14 +170,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 CommentAddress
+=item CommentAddress
 
 Returns the current value of CommentAddress. 
 (In the database, CommentAddress is stored as varchar(120).)
 
 
 
-=head2 SetCommentAddress VALUE
+=item SetCommentAddress VALUE
 
 
 Set CommentAddress to VALUE. 
@@ -212,14 +188,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 InitialPriority
+=item InitialPriority
 
 Returns the current value of InitialPriority. 
 (In the database, InitialPriority is stored as int(11).)
 
 
 
-=head2 SetInitialPriority VALUE
+=item SetInitialPriority VALUE
 
 
 Set InitialPriority to VALUE. 
@@ -230,14 +206,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 FinalPriority
+=item FinalPriority
 
 Returns the current value of FinalPriority. 
 (In the database, FinalPriority is stored as int(11).)
 
 
 
-=head2 SetFinalPriority VALUE
+=item SetFinalPriority VALUE
 
 
 Set FinalPriority to VALUE. 
@@ -248,14 +224,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 DefaultDueIn
+=item DefaultDueIn
 
 Returns the current value of DefaultDueIn. 
 (In the database, DefaultDueIn is stored as int(11).)
 
 
 
-=head2 SetDefaultDueIn VALUE
+=item SetDefaultDueIn VALUE
 
 
 Set DefaultDueIn to VALUE. 
@@ -266,7 +242,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -275,7 +251,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -284,7 +260,7 @@ Returns the current value of Created.
 =cut
 
 
-=head2 LastUpdatedBy
+=item LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -293,7 +269,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=head2 LastUpdated
+=item LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -302,14 +278,14 @@ Returns the current value of LastUpdated.
 =cut
 
 
-=head2 Disabled
+=item Disabled
 
 Returns the current value of Disabled. 
 (In the database, Disabled is stored as smallint(6).)
 
 
 
-=head2 SetDisabled VALUE
+=item SetDisabled VALUE
 
 
 Set Disabled to VALUE. 
@@ -321,35 +297,35 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         CorrespondAddress => 
-               {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
+               {read => 1, write => 1, type => 'varchar(120)', default => ''},
         CommentAddress => 
-               {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
+               {read => 1, write => 1, type => 'varchar(120)', default => ''},
         InitialPriority => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         FinalPriority => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         DefaultDueIn => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         Disabled => 
-               {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
+               {read => 1, write => 1, type => 'smallint(6)', default => '0'},
 
  }
 };
@@ -381,7 +357,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index bc5f480..60aec90 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::Queue item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index a7598bf..6962221 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 =head1 NAME
 
   RT::Record - Base class for RT record objects
@@ -66,23 +42,18 @@ ok (require RT::Record);
 =cut
 
 package RT::Record;
-
-use strict;
-use warnings;
-
-our @ISA;
-use base qw(RT::Base);
-
 use RT::Date;
 use RT::User;
-use RT::Attributes;
+
+use RT::Base;
 use DBIx::SearchBuilder::Record::Cachable;
-use Encode qw();
 
-our $_TABLE_ATTR = { };
+use strict;
+use vars qw/@ISA/;
 
+@ISA = qw(RT::Base);
 
-if ( $RT::DontCacheSearchBuilderRecords ) {
+if ($RT::DontCacheSearchBuilderRecords ) {
     push (@ISA, 'DBIx::SearchBuilder::Record');
 } else {
     push (@ISA, 'DBIx::SearchBuilder::Record::Cachable');
@@ -93,8 +64,8 @@ if ( $RT::DontCacheSearchBuilderRecords ) {
 
 sub _Init {
     my $self = shift;
-    $self->_BuildTableAttributes unless ($_TABLE_ATTR->{ref($self)});
     $self->CurrentUser(@_);
+
 }
 
 # }}}
@@ -114,150 +85,6 @@ sub _PrimaryKeys {
 
 # }}}
 
-=head2 Delete
-
-Delete this record object from the database.
-
-=cut
-
-sub Delete {
-    my $self = shift;
-    my ($rv) = $self->SUPER::Delete;
-    if ($rv) {
-        return ($rv, $self->loc("Object deleted"));
-    } else {
-
-        return(0, $self->loc("Object could not be deleted"))
-    } 
-}
-
-=head2 ObjectTypeStr
-
-Returns a string which is this object's type.  The type is the class,
-without the "RT::" prefix.
-
-=begin testing
-
-my $ticket = RT::Ticket->new($RT::SystemUser);
-my $group = RT::Group->new($RT::SystemUser);
-is($ticket->ObjectTypeStr, 'Ticket', "Ticket returns correct typestring");
-is($group->ObjectTypeStr, 'Group', "Group returns correct typestring");
-
-=end testing
-
-=cut
-
-sub ObjectTypeStr {
-    my $self = shift;
-    if (ref($self) =~ /^.*::(\w+)$/) {
-       return $self->loc($1);
-    } else {
-       return $self->loc(ref($self));
-    }
-}
-
-=head2 Attributes
-
-Return this object's attributes as an RT::Attributes object
-
-=cut
-
-sub Attributes {
-    my $self = shift;
-    
-    unless ($self->{'attributes'}) {
-        $self->{'attributes'} = RT::Attributes->new($self->CurrentUser);     
-       $self->{'attributes'}->LimitToObject($self); 
-    }
-    return ($self->{'attributes'}); 
-
-}
-
-
-=head2 AddAttribute { Name, Description, Content }
-
-Adds a new attribute for this object.
-
-=cut
-
-sub AddAttribute {
-    my $self = shift;
-    my %args = ( Name        => undef,
-                 Description => undef,
-                 Content     => undef,
-                 @_ );
-
-    my $attr = RT::Attribute->new( $self->CurrentUser );
-    my ( $id, $msg ) = $attr->Create( 
-                                      Object    => $self,
-                                      Name        => $args{'Name'},
-                                      Description => $args{'Description'},
-                                      Content     => $args{'Content'} );
-
-
-    # XXX TODO: Why won't RedoSearch work here?                                     
-    $self->Attributes->_DoSearch;
-    
-    return ($id, $msg);
-}
-
-
-=head2 SetAttribute { Name, Description, Content }
-
-Like AddAttribute, but replaces all existing attributes with the same Name.
-
-=cut
-
-sub SetAttribute {
-    my $self = shift;
-    my %args = ( Name        => undef,
-                 Description => undef,
-                 Content     => undef,
-                 @_ );
-
-    my @AttributeObjs = $self->Attributes->Named( $args{'Name'} )
-        or return $self->AddAttribute( %args );
-
-    my $AttributeObj = pop( @AttributeObjs );
-    $_->Delete foreach @AttributeObjs;
-
-    $AttributeObj->SetDescription( $args{'Description'} );
-    $AttributeObj->SetContent( $args{'Content'} );
-
-    $self->Attributes->RedoSearch;
-    return 1;
-}
-
-=head2 DeleteAttribute NAME
-
-Deletes all attributes with the matching name for this object.
-
-=cut
-
-sub DeleteAttribute {
-    my $self = shift;
-    my $name = shift;
-    return $self->Attributes->DeleteEntry( Name => $name );
-}
-
-=head2 FirstAttribute NAME
-
-Returns the first attribute with the matching name for this object (as an
-L<RT::Attribute> object), or C<undef> if no such attributes exist.
-
-Note that if there is more than one attribute with the matching name on the
-object, the choice of which one to return is basically arbitrary.  This may be
-made well-defined in the future.
-
-=cut
-
-sub FirstAttribute {
-    my $self = shift;
-    my $name = shift;
-    return ($self->Attributes->Named( $name ))[0];
-}
-
-
 # {{{ sub _Handle 
 sub _Handle {
     my $self = shift;
@@ -268,7 +95,7 @@ sub _Handle {
 
 # {{{ sub Create 
 
-=head2  Create PARAMHASH
+=item  Create PARAMHASH
 
 Takes a PARAMHASH of Column -> Value pairs.
 If any Column has a Validate$PARAMNAME subroutine defined and the 
@@ -368,9 +195,6 @@ sub LoadByCols {
     my $self = shift;
     my %hash = (@_);
 
-    # We don't want to hang onto this
-    delete $self->{'attributes'};
-
     # If this database is case sensitive we need to uncase objects for
     # explicit loading
     if ( $self->_Handle->CaseSensitive ) {
@@ -387,11 +211,7 @@ sub LoadByCols {
                 $newhash{$key} = $hash{$key};
             }
             else {
-                my ($op, $val, $func);
-                ($key, $op, $val, $func) = $self->_Handle->_MakeClauseCaseInsensitive($key, '=', $hash{$key});
-                $newhash{$key}->{operator} = $op;
-                $newhash{$key}->{value} = $val;
-                $newhash{$key}->{function} = $func;
+                $newhash{ "lower(" . $key . ")" } = lc( $hash{$key} );
             }
         }
 
@@ -493,7 +313,6 @@ sub LongSinceUpdateAsString {
 # }}} Datehandling
 
 # {{{ sub _Set 
-#
 sub _Set {
     my $self = shift;
 
@@ -511,33 +330,12 @@ sub _Set {
         $args{'Value'} = 0;
     }
 
-    my $old_val = $self->__Value($args{'Field'});
-     $self->_SetLastUpdated();
-    my $ret = $self->SUPER::_Set(
+    $self->_SetLastUpdated();
+    my ( $val, $msg ) = $self->SUPER::_Set(
         Field => $args{'Field'},
         Value => $args{'Value'},
         IsSQL => $args{'IsSQL'}
     );
-        my ($status, $msg) =  $ret->as_array();
-
-        # @values has two values, a status code and a message.
-
-    # $ret is a Class::ReturnValue object. as such, in a boolean context, it's a bool
-    # we want to change the standard "success" message
-    if ($status) {
-        $msg =
-          $self->loc(
-            "[_1] changed from [_2] to [_3]",
-            $args{'Field'},
-            ( $old_val ? "'$old_val'" : $self->loc("(no value)") ),
-            '"' . $self->__Value( $args{'Field'}) . '"' 
-          );
-      } else {
-
-          $msg = $self->CurrentUser->loc_fuzzy($msg);
-    }
-    return wantarray ? ($status, $msg) : $ret;     
-
 }
 
 # }}}
@@ -612,55 +410,9 @@ sub LastUpdatedByObj {
 
 # }}}
 
-# {{{ sub URI 
-
-=head2 URI
-
-Returns this record's URI
-
-=cut
-
-sub URI {
-    my $self = shift;
-    my $uri = RT::URI::fsck_com_rt->new($self->CurrentUser);
-    return($uri->URIForObject($self));
-}
-
-# }}}
-
-=head2 ValidateName NAME
-
-Validate the name of the record we're creating. Mostly, just make sure it's not a numeric ID, which is invalid for Name
-
-=cut
-
-sub ValidateName {
-    my $self = shift;
-    my $value = shift;
-    if ($value && $value=~ /^\d+$/) {
-        return(0);
-    } else  {
-         return (1);
-    }
-}
-
-
-
-=head2 SQLType attribute
-
-return the SQL type for the attribute 'attribute' as stored in _ClassAccessible
-
-=cut
-
-sub SQLType {
-    my $self = shift;
-    my $field = shift;
-
-    return ($self->_Accessible($field, 'type'));
-
-
-}
 
+require Encode::compat if $] < 5.007001;
+require Encode;
 
 sub __Value {
     my $self  = shift;
@@ -675,14 +427,7 @@ sub __Value {
 
     return('') if ( !defined($value) || $value eq '');
 
-    if( $args{'decode_utf8'} ) {
-       # XXX: is_utf8 check should be here unless Encode bug would be fixed
-        # see http://rt.cpan.org/NoAuth/Bug.html?id=14559 
-        return Encode::decode_utf8($value) unless Encode::is_utf8($value);
-    } else {
-        # check is_utf8 here just to be shure
-        return Encode::encode_utf8($value) if Encode::is_utf8($value);
-    }
+    return Encode::decode_utf8($value) || $value if $args{'decode_utf8'};
     return $value;
 }
 
@@ -691,1217 +436,17 @@ sub __Value {
 sub _CacheConfig {
   {
      'cache_p'        => 1,
+     'fast_update_p'  => 1,
      'cache_for_sec'  => 30,
   }
 }
 
+=head2 _DecodeUTF8
 
-
-sub _BuildTableAttributes {
-    my $self = shift;
-
-    my $attributes;
-    if ( UNIVERSAL::can( $self, '_CoreAccessible' ) ) {
-       $attributes = $self->_CoreAccessible();
-    } elsif ( UNIVERSAL::can( $self, '_ClassAccessible' ) ) {
-       $attributes = $self->_ClassAccessible();
-
-    }
-
-    foreach my $column (%$attributes) {
-        foreach my $attr ( %{ $attributes->{$column} } ) {
-            $_TABLE_ATTR->{ref($self)}->{$column}->{$attr} = $attributes->{$column}->{$attr};
-        }
-    }
-    if ( UNIVERSAL::can( $self, '_OverlayAccessible' ) ) {
-        $attributes = $self->_OverlayAccessible();
-
-        foreach my $column (%$attributes) {
-            foreach my $attr ( %{ $attributes->{$column} } ) {
-                $_TABLE_ATTR->{ref($self)}->{$column}->{$attr} = $attributes->{$column}->{$attr};
-            }
-        }
-    }
-    if ( UNIVERSAL::can( $self, '_VendorAccessible' ) ) {
-        $attributes = $self->_VendorAccessible();
-
-        foreach my $column (%$attributes) {
-            foreach my $attr ( %{ $attributes->{$column} } ) {
-                $_TABLE_ATTR->{ref($self)}->{$column}->{$attr} = $attributes->{$column}->{$attr};
-            }
-        }
-    }
-    if ( UNIVERSAL::can( $self, '_LocalAccessible' ) ) {
-        $attributes = $self->_LocalAccessible();
-
-        foreach my $column (%$attributes) {
-            foreach my $attr ( %{ $attributes->{$column} } ) {
-                $_TABLE_ATTR->{ref($self)}->{$column}->{$attr} = $attributes->{$column}->{$attr};
-            }
-        }
-    }
-
-}
-
-
-=head2 _ClassAccessible 
-
-Overrides the "core" _ClassAccessible using $_TABLE_ATTR. Behaves identical to the version in
-DBIx::SearchBuilder::Record
-
-=cut
-
-sub _ClassAccessible {
-    my $self = shift;
-    return $_TABLE_ATTR->{ref($self)};
-}
-
-=head2 _Accessible COLUMN ATTRIBUTE
-
-returns the value of ATTRIBUTE for COLUMN
-
-
-=cut 
-
-sub _Accessible  {
-  my $self = shift;
-  my $column = shift;
-  my $attribute = lc(shift);
-  return 0 unless defined ($_TABLE_ATTR->{ref($self)}->{$column});
-  return $_TABLE_ATTR->{ref($self)}->{$column}->{$attribute} || 0;
-
-}
-
-=head2 _EncodeLOB BODY MIME_TYPE
-
-Takes a potentially large attachment. Returns (ContentEncoding, EncodedBody) based on system configuration and selected database
-
-=cut
-
-sub _EncodeLOB {
-        my $self = shift;
-        my $Body = shift;
-        my $MIMEType = shift;
-
-        my $ContentEncoding = 'none';
-
-        #get the max attachment length from RT
-        my $MaxSize = $RT::MaxAttachmentSize;
-
-        #if the current attachment contains nulls and the
-        #database doesn't support embedded nulls
-
-        if ( $RT::AlwaysUseBase64 or
-             ( !$RT::Handle->BinarySafeBLOBs ) && ( $Body =~ /\x00/ ) ) {
-
-            # set a flag telling us to mimencode the attachment
-            $ContentEncoding = 'base64';
-
-            #cut the max attchment size by 25% (for mime-encoding overhead.
-            $RT::Logger->debug("Max size is $MaxSize\n");
-            $MaxSize = $MaxSize * 3 / 4;
-        # Some databases (postgres) can't handle non-utf8 data
-        } elsif (    !$RT::Handle->BinarySafeBLOBs
-                  && $MIMEType !~ /text\/plain/gi
-                  && !Encode::is_utf8( $Body, 1 ) ) {
-              $ContentEncoding = 'quoted-printable';
-        }
-
-        #if the attachment is larger than the maximum size
-        if ( ($MaxSize) and ( $MaxSize < length($Body) ) ) {
-
-            # if we're supposed to truncate large attachments
-            if ($RT::TruncateLongAttachments) {
-
-                # truncate the attachment to that length.
-                $Body = substr( $Body, 0, $MaxSize );
-
-            }
-
-            # elsif we're supposed to drop large attachments on the floor,
-            elsif ($RT::DropLongAttachments) {
-
-                # drop the attachment on the floor
-                $RT::Logger->info( "$self: Dropped an attachment of size "
-                                   . length($Body) . "\n"
-                                   . "It started: " . substr( $Body, 0, 60 ) . "\n"
-                                 );
-                return ("none", "Large attachment dropped" );
-            }
-        }
-
-        # if we need to mimencode the attachment
-        if ( $ContentEncoding eq 'base64' ) {
-
-            # base64 encode the attachment
-            Encode::_utf8_off($Body);
-            $Body = MIME::Base64::encode_base64($Body);
-
-        } elsif ($ContentEncoding eq 'quoted-printable') {
-            Encode::_utf8_off($Body);
-            $Body = MIME::QuotedPrint::encode($Body);
-        }
-
-
-        return ($ContentEncoding, $Body);
-
-}
-
-sub _DecodeLOB {
-    my $self            = shift;
-    my $ContentType     = shift;
-    my $ContentEncoding = shift;
-    my $Content         = shift;
-
-    if ( $ContentEncoding eq 'base64' ) {
-        $Content = MIME::Base64::decode_base64($Content);
-    }
-    elsif ( $ContentEncoding eq 'quoted-printable' ) {
-        $Content = MIME::QuotedPrint::decode($Content);
-    }
-    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);
-    }
-        return ($Content);
-}
-
-# {{{ LINKDIRMAP
-# 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', },
-
-);
-
-=head2 Update  ARGSHASH
-
-Updates fields on an object for you using the proper Set methods,
-skipping unchanged values.
-
- ARGSRef => a hashref of attributes => value for the update
- AttributesRef => an arrayref of keys in ARGSRef that should be updated
- AttributePrefix => a prefix that should be added to the attributes in AttributesRef
-                    when looking up values in ARGSRef
-                    Bare attributes are tried before prefixed attributes
-
-Returns a list of localized results of the update
-
-=cut
-
-sub Update {
-    my $self = shift;
-
-    my %args = (
-        ARGSRef         => undef,
-        AttributesRef   => undef,
-        AttributePrefix => undef,
-        @_
-    );
-
-    my $attributes = $args{'AttributesRef'};
-    my $ARGSRef    = $args{'ARGSRef'};
-    my @results;
-
-    foreach my $attribute (@$attributes) {
-        my $value;
-        if ( defined $ARGSRef->{$attribute} ) {
-            $value = $ARGSRef->{$attribute};
-        }
-        elsif (
-            defined( $args{'AttributePrefix'} )
-            && defined(
-                $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute }
-            )
-          ) {
-            $value = $ARGSRef->{ $args{'AttributePrefix'} . "-" . $attribute };
-
-        }
-        else {
-            next;
-        }
-
-        $value =~ s/\r\n/\n/gs;
-
-
-        # If Queue is 'General', we want to resolve the queue name for
-        # the object.
-
-        # This is in an eval block because $object might not exist.
-        # and might not have a Name method. But "can" won't find autoloaded
-        # items. If it fails, we don't care
-        eval {
-            my $object = $attribute . "Obj";
-            next if ($self->$object->Name eq $value);
-        };
-        next if ( $value eq $self->$attribute() );
-        my $method = "Set$attribute";
-        my ( $code, $msg ) = $self->$method($value);
-        my ($prefix) = ref($self) =~ /RT(?:.*)::(\w+)/;
-
-        # Default to $id, but use name if we can get it.
-        my $label = $self->id;
-        $label = $self->Name if (UNIVERSAL::can($self,'Name'));
-        push @results, $self->loc( "$prefix [_1]", $label ) . ': '. $msg;
-
-=for loc
-
-                                   "[_1] could not be set to [_2].",       # loc
-                                   "That is already the current value",    # loc
-                                   "No value sent to _Set!\n",             # loc
-                                   "Illegal value for [_1]",               # loc
-                                   "The new value has been set.",          # loc
-                                   "No column specified",                  # loc
-                                   "Immutable field",                      # loc
-                                   "Nonexistant field?",                   # loc
-                                   "Invalid data",                         # loc
-                                   "Couldn't find row",                    # loc
-                                   "Missing a primary key?: [_1]",         # loc
-                                   "Found Object",                         # loc
-
-=cut
-
-    }
-
-    return @results;
-}
-
-# {{{ Routines dealing with Links
-
-# {{{ Link Collections
-
-# {{{ sub Members
-
-=head2 Members
-
-  This returns an RT::Links object which references all the tickets 
-which are 'MembersOf' this ticket
-
-=cut
-
-sub Members {
-    my $self = shift;
-    return ( $self->_Links( 'Target', 'MemberOf' ) );
-}
-
-# }}}
-
-# {{{ sub MemberOf
-
-=head2 MemberOf
-
-  This returns an RT::Links object which references all the tickets that this
-ticket is a 'MemberOf'
-
-=cut
-
-sub MemberOf {
-    my $self = shift;
-    return ( $self->_Links( 'Base', 'MemberOf' ) );
-}
-
-# }}}
-
-# {{{ RefersTo
-
-=head2 RefersTo
-
-  This returns an RT::Links object which shows all references for which this ticket is a base
-
-=cut
-
-sub RefersTo {
-    my $self = shift;
-    return ( $self->_Links( 'Base', 'RefersTo' ) );
-}
-
-# }}}
-
-# {{{ ReferredToBy
-
-=head2 ReferredToBy
-
-This returns an L<RT::Links> object which shows all references for which this ticket is a target
+ When passed a string will "decode" it int a proper UTF-8 string
 
 =cut
 
-sub ReferredToBy {
-    my $self = shift;
-    return ( $self->_Links( 'Target', 'RefersTo' ) );
-}
-
-# }}}
-
-# {{{ DependedOnBy
-
-=head2 DependedOnBy
-
-  This returns an RT::Links object which references all the tickets that depend on this one
-
-=cut
-
-sub DependedOnBy {
-    my $self = shift;
-    return ( $self->_Links( 'Target', 'DependsOn' ) );
-}
-
-# }}}
-
-
-
-=head2 HasUnresolvedDependencies
-
-  Takes a paramhash of Type (default to '__any').  Returns true if
-$self->UnresolvedDependencies returns an object with one or more members
-of that type.  Returns false otherwise
-
-
-=begin testing
-
-my $t1 = RT::Ticket->new($RT::SystemUser);
-my ($id, $trans, $msg) = $t1->Create(Subject => 'DepTest1', Queue => 'general');
-ok($id, "Created dep test 1 - $msg");
-
-my $t2 = RT::Ticket->new($RT::SystemUser);
-my ($id2, $trans, $msg2) = $t2->Create(Subject => 'DepTest2', Queue => 'general');
-ok($id2, "Created dep test 2 - $msg2");
-my $t3 = RT::Ticket->new($RT::SystemUser);
-my ($id3, $trans, $msg3) = $t3->Create(Subject => 'DepTest3', Queue => 'general', Type => 'approval');
-ok($id3, "Created dep test 3 - $msg3");
-my ($addid, $addmsg);
-ok (($addid, $addmsg) =$t1->AddLink( Type => 'DependsOn', Target => $t2->id));
-ok ($addid, $addmsg);
-ok (($addid, $addmsg) =$t1->AddLink( Type => 'DependsOn', Target => $t3->id));
-
-ok ($addid, $addmsg);
-my $link = RT::Link->new($RT::SystemUser);
-my ($rv, $msg) = $link->Load($addid);
-ok ($rv, $msg);
-ok ($link->LocalTarget == $t3->id, "Link LocalTarget is correct");
-ok ($link->LocalBase   == $t1->id, "Link LocalBase   is correct");
-
-ok ($t1->HasUnresolvedDependencies, "Ticket ".$t1->Id." has unresolved deps");
-ok (!$t1->HasUnresolvedDependencies( Type => 'blah' ), "Ticket ".$t1->Id." has no unresolved blahs");
-ok ($t1->HasUnresolvedDependencies( Type => 'approval' ), "Ticket ".$t1->Id." has unresolved approvals");
-ok (!$t2->HasUnresolvedDependencies, "Ticket ".$t2->Id." has no unresolved deps");
-;
-
-my ($rid, $rmsg)= $t1->Resolve();
-ok(!$rid, $rmsg);
-my ($rid2, $rmsg2) = $t2->Resolve();
-ok ($rid2, $rmsg2);
-($rid, $rmsg)= $t1->Resolve();
-ok(!$rid, $rmsg);
-my ($rid3,$rmsg3) = $t3->Resolve;
-ok ($rid3,$rmsg3);
-($rid, $rmsg)= $t1->Resolve();
-ok($rid, $rmsg);
-
-
-=end testing
-
-=cut
-
-sub HasUnresolvedDependencies {
-    my $self = shift;
-    my %args = (
-        Type   => undef,
-        @_
-    );
-
-    my $deps = $self->UnresolvedDependencies;
-
-    if ($args{Type}) {
-        $deps->Limit( FIELD => 'Type', 
-              OPERATOR => '=',
-              VALUE => $args{Type}); 
-    }
-    else {
-           $deps->IgnoreType;
-    }
-
-    if ($deps->Count > 0) {
-        return 1;
-    }
-    else {
-        return (undef);
-    }
-}
-
-
-# {{{ UnresolvedDependencies 
-
-=head2 UnresolvedDependencies
-
-Returns an RT::Tickets object of tickets which this ticket depends on
-and which have a status of new, open or stalled. (That list comes from
-RT::Queue->ActiveStatusArray
-
-=cut
-
-
-sub UnresolvedDependencies {
-    my $self = shift;
-    my $deps = RT::Tickets->new($self->CurrentUser);
-
-    my @live_statuses = RT::Queue->ActiveStatusArray();
-    foreach my $status (@live_statuses) {
-        $deps->LimitStatus(VALUE => $status);
-    }
-    $deps->LimitDependedOnBy($self->Id);
-
-    return($deps);
-
-}
-
-# }}}
-
-# {{{ AllDependedOnBy
-
-=head2 AllDependedOnBy
-
-Returns an array of RT::Ticket objects which (directly or indirectly)
-depends on this ticket; takes an optional 'Type' argument in the param
-hash, which will limit returned tickets to that type, as well as cause
-tickets with that type to serve as 'leaf' nodes that stops the recursive
-dependency search.
-
-=cut
-
-sub AllDependedOnBy {
-    my $self = shift;
-    my $dep = $self->DependedOnBy;
-    my %args = (
-        Type   => undef,
-       _found => {},
-       _top   => 1,
-        @_
-    );
-
-    while (my $link = $dep->Next()) {
-       next unless ($link->BaseURI->IsLocal());
-       next if $args{_found}{$link->BaseObj->Id};
-
-       if (!$args{Type}) {
-           $args{_found}{$link->BaseObj->Id} = $link->BaseObj;
-           $link->BaseObj->AllDependedOnBy( %args, _top => 0 );
-       }
-       elsif ($link->BaseObj->Type eq $args{Type}) {
-           $args{_found}{$link->BaseObj->Id} = $link->BaseObj;
-       }
-       else {
-           $link->BaseObj->AllDependedOnBy( %args, _top => 0 );
-       }
-    }
-
-    if ($args{_top}) {
-       return map { $args{_found}{$_} } sort keys %{$args{_found}};
-    }
-    else {
-       return 1;
-    }
-}
-
-# }}}
-
-# {{{ DependsOn
-
-=head2 DependsOn
-
-  This returns an RT::Links object which references all the tickets that this ticket depends on
-
-=cut
-
-sub DependsOn {
-    my $self = shift;
-    return ( $self->_Links( 'Base', 'DependsOn' ) );
-}
-
-# }}}
-
-
-
-
-# {{{ sub _Links 
-
-=head2 Links DIRECTION [TYPE]
-
-Return links (L<RT::Links>) to/from this object.
-
-DIRECTION is either 'Base' or 'Target'.
-
-TYPE is a type of links to return, it can be omitted to get
-links of any type.
-
-=cut
-
-*Links = \&_Links;
-
-sub _Links {
-    my $self = shift;
-
-    #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic ---
-    #tobias meant by $f
-    my $field = shift;
-    my $type  = shift || "";
-
-    unless ( $self->{"$field$type"} ) {
-        $self->{"$field$type"} = new RT::Links( $self->CurrentUser );
-            # at least to myself
-            $self->{"$field$type"}->Limit( FIELD => $field,
-                                           VALUE => $self->URI,
-                                           ENTRYAGGREGATOR => 'OR' );
-            $self->{"$field$type"}->Limit( FIELD => 'Type',
-                                           VALUE => $type )
-              if ($type);
-    }
-    return ( $self->{"$field$type"} );
-}
-
-# }}}
-
-# }}}
-
-# {{{ sub _AddLink
-
-=head2 _AddLink
-
-Takes a paramhash of Type and one of Base or Target. Adds that link to this object.
-
-Returns C<link id>, C<message> and C<exist> flag.
-
-
-=cut
-
-
-sub _AddLink {
-    my $self = shift;
-    my %args = ( Target => '',
-                 Base   => '',
-                 Type   => '',
-                 Silent => undef,
-                 @_ );
-
-
-    # Remote_link is the URI of the object that is not this ticket
-    my $remote_link;
-    my $direction;
-
-    if ( $args{'Base'} and $args{'Target'} ) {
-        $RT::Logger->debug( "$self tried to create a link. both base and target were specified\n" );
-        return ( 0, $self->loc("Can't specifiy both base and target") );
-    }
-    elsif ( $args{'Base'} ) {
-        $args{'Target'} = $self->URI();
-        $remote_link    = $args{'Base'};
-        $direction      = 'Target';
-    }
-    elsif ( $args{'Target'} ) {
-        $args{'Base'} = $self->URI();
-        $remote_link  = $args{'Target'};
-        $direction    = 'Base';
-    }
-    else {
-        return ( 0, $self->loc('Either base or target must be specified') );
-    }
-
-    # {{{ Check if the link already exists - we don't want duplicates
-    use RT::Link;
-    my $old_link = RT::Link->new( $self->CurrentUser );
-    $old_link->LoadByParams( Base   => $args{'Base'},
-                             Type   => $args{'Type'},
-                             Target => $args{'Target'} );
-    if ( $old_link->Id ) {
-        $RT::Logger->debug("$self Somebody tried to duplicate a link");
-        return ( $old_link->id, $self->loc("Link already exists"), 1 );
-    }
-
-    # }}}
-
-
-    # Storing the link in the DB.
-    my $link = RT::Link->new( $self->CurrentUser );
-    my ($linkid, $linkmsg) = $link->Create( Target => $args{Target},
-                                  Base   => $args{Base},
-                                  Type   => $args{Type} );
-
-    unless ($linkid) {
-        $RT::Logger->error("Link could not be created: ".$linkmsg);
-        return ( 0, $self->loc("Link could not be created") );
-    }
-
-    my $TransString =
-      "Record $args{'Base'} $args{Type} record $args{'Target'}.";
-
-    return ( $linkid, $self->loc( "Link created ([_1])", $TransString ) );
-}
-
-# }}}
-
-# {{{ sub _DeleteLink 
-
-=head2 _DeleteLink
-
-Delete a link. takes a paramhash of Base, Target and Type.
-Either Base or Target must be null. The null value will 
-be replaced with this ticket\'s id
-
-=cut 
-
-sub _DeleteLink {
-    my $self = shift;
-    my %args = (
-        Base   => undef,
-        Target => undef,
-        Type   => undef,
-        @_
-    );
-
-    #we want one of base and target. we don't care which
-    #but we only want _one_
-
-    my $direction;
-    my $remote_link;
-
-    if ( $args{'Base'} and $args{'Target'} ) {
-        $RT::Logger->debug("$self ->_DeleteLink. got both Base and Target\n");
-        return ( 0, $self->loc("Can't specifiy both base and target") );
-    }
-    elsif ( $args{'Base'} ) {
-        $args{'Target'} = $self->URI();
-       $remote_link = $args{'Base'};
-       $direction = 'Target';
-    }
-    elsif ( $args{'Target'} ) {
-        $args{'Base'} = $self->URI();
-       $remote_link = $args{'Target'};
-        $direction='Base';
-    }
-    else {
-        $RT::Logger->error("Base or Target must be specified\n");
-        return ( 0, $self->loc('Either base or target must be specified') );
-    }
-
-    my $link = new RT::Link( $self->CurrentUser );
-    $RT::Logger->debug( "Trying to load link: " . $args{'Base'} . " " . $args{'Type'} . " " . $args{'Target'} . "\n" );
-
-
-    $link->LoadByParams( Base=> $args{'Base'}, Type=> $args{'Type'}, Target=>  $args{'Target'} );
-    #it's a real link. 
-    if ( $link->id ) {
-
-        my $linkid = $link->id;
-        $link->Delete();
-
-        my $TransString = "Record $args{'Base'} no longer $args{Type} record $args{'Target'}.";
-        return ( 1, $self->loc("Link deleted ([_1])", $TransString));
-    }
-
-    #if it's not a link we can find
-    else {
-        $RT::Logger->debug("Couldn't find that link\n");
-        return ( 0, $self->loc("Link not found") );
-    }
-}
-
-# }}}
-
-# }}}
-
-# {{{ Routines dealing with transactions
-
-# {{{ sub _NewTransaction
-
-=head2 _NewTransaction  PARAMHASH
-
-Private function to create a new RT::Transaction object for this ticket update
-
-=cut
-
-sub _NewTransaction {
-    my $self = shift;
-    my %args = (
-        TimeTaken => undef,
-        Type      => undef,
-        OldValue  => undef,
-        NewValue  => undef,
-        OldReference  => undef,
-        NewReference  => undef,
-        ReferenceType => undef,
-        Data      => undef,
-        Field     => undef,
-        MIMEObj   => undef,
-        ActivateScrips => 1,
-        CommitScrips => 1,
-        @_
-    );
-
-    my $old_ref = $args{'OldReference'};
-    my $new_ref = $args{'NewReference'};
-    my $ref_type = $args{'ReferenceType'};
-    if ($old_ref or $new_ref) {
-       $ref_type ||= ref($old_ref) || ref($new_ref);
-       if (!$ref_type) {
-           $RT::Logger->error("Reference type not specified for transaction");
-           return;
-       }
-       $old_ref = $old_ref->Id if ref($old_ref);
-       $new_ref = $new_ref->Id if ref($new_ref);
-    }
-
-    require RT::Transaction;
-    my $trans = new RT::Transaction( $self->CurrentUser );
-    my ( $transaction, $msg ) = $trans->Create(
-       ObjectId  => $self->Id,
-       ObjectType => ref($self),
-        TimeTaken => $args{'TimeTaken'},
-        Type      => $args{'Type'},
-        Data      => $args{'Data'},
-        Field     => $args{'Field'},
-        NewValue  => $args{'NewValue'},
-        OldValue  => $args{'OldValue'},
-        NewReference  => $new_ref,
-        OldReference  => $old_ref,
-        ReferenceType => $ref_type,
-        MIMEObj   => $args{'MIMEObj'},
-        ActivateScrips => $args{'ActivateScrips'},
-        CommitScrips => $args{'CommitScrips'},
-    );
-
-    # Rationalize the object since we may have done things to it during the caching.
-    $self->Load($self->Id);
-
-    $RT::Logger->warning($msg) unless $transaction;
-
-    $self->_SetLastUpdated;
-
-    if ( defined $args{'TimeTaken'} and $self->can('_UpdateTimeTaken')) {
-        $self->_UpdateTimeTaken( $args{'TimeTaken'} );
-    }
-    if ( $RT::UseTransactionBatch and $transaction ) {
-           push @{$self->{_TransactionBatch}}, $trans if $args{'CommitScrips'};
-    }
-    return ( $transaction, $msg, $trans );
-}
-
-# }}}
-
-# {{{ sub Transactions 
-
-=head2 Transactions
-
-  Returns an RT::Transactions object of all transactions on this record object
-
-=cut
-
-sub Transactions {
-    my $self = shift;
-
-    use RT::Transactions;
-    my $transactions = RT::Transactions->new( $self->CurrentUser );
-
-    #If the user has no rights, return an empty object
-    $transactions->Limit(
-        FIELD => 'ObjectId',
-        VALUE => $self->id,
-    );
-    $transactions->Limit(
-        FIELD => 'ObjectType',
-        VALUE => ref($self),
-    );
-
-    return ($transactions);
-}
-
-# }}}
-# }}}
-#
-# {{{ Routines dealing with custom fields
-
-sub CustomFields {
-    my $self = shift;
-    my $cfs  = RT::CustomFields->new( $self->CurrentUser );
-
-    # XXX handle multiple types properly
-    $cfs->LimitToLookupType( $self->CustomFieldLookupType );
-    $cfs->LimitToGlobalOrObjectId(
-        $self->_LookupId( $self->CustomFieldLookupType ) );
-
-    return $cfs;
-}
-
-# TODO: This _only_ works for RT::Class classes. it doesn't work, for example, for RT::FM classes.
-
-sub _LookupId {
-    my $self = shift;
-    my $lookup = shift;
-    my @classes = ($lookup =~ /RT::(\w+)-/g);
-
-    my $object = $self;
-    foreach my $class (reverse @classes) {
-       my $method = "${class}Obj";
-       $object = $object->$method;
-    }
-
-    return $object->Id;
-}
-
-
-=head2 CustomFieldLookupType 
-
-Returns the path RT uses to figure out which custom fields apply to this object.
-
-=cut
-
-sub CustomFieldLookupType {
-    my $self = shift;
-    return ref($self);
-}
-
-#TODO Deprecated API. Destroy in 3.6
-sub _LookupTypes { 
-    my  $self = shift;
-    $RT::Logger->warning("_LookupTypes call is deprecated at (". join(":",caller)."). Replace with CustomFieldLookupType");
-
-    return($self->CustomFieldLookupType);
-
-}
-
-# {{{ AddCustomFieldValue
-
-=head2 AddCustomFieldValue { Field => FIELD, Value => VALUE }
-
-VALUE should be a string.
-FIELD can be a CustomField object OR a CustomField ID.
-
-
-Adds VALUE as a value of CustomField FIELD.  If this is a single-value custom field,
-deletes the old value. 
-If VALUE is not a valid value for the custom field, returns 
-(0, 'Error message' ) otherwise, returns (1, 'Success Message')
-
-=cut
-
-sub AddCustomFieldValue {
-    my $self = shift;
-    $self->_AddCustomFieldValue(@_);
-}
-
-sub _AddCustomFieldValue {
-    my $self = shift;
-    my %args = (
-        Field             => undef,
-        Value             => undef,
-        RecordTransaction => 1,
-        @_
-    );
-
-    my $cf = $self->LoadCustomFieldByIdentifier($args{'Field'});
-
-    unless ( $cf->Id ) {
-        return ( 0, $self->loc( "Custom field [_1] not found", $args{'Field'} ) );
-    }
-
-    my $OCFs = $self->CustomFields;
-    $OCFs->Limit( FIELD => 'id', VALUE => $cf->Id );
-    unless ( $OCFs->Count ) {
-        return (
-            0,
-            $self->loc(
-                "Custom field [_1] does not apply to this object",
-                $args{'Field'}
-            )
-        );
-    }
-    # Load up a ObjectCustomFieldValues object for this custom field and this ticket
-    my $values = $cf->ValuesForObject($self);
-
-    unless ( $cf->ValidateValue( $args{'Value'} ) ) {
-        return ( 0, $self->loc("Invalid value for custom field") );
-    }
-
-    # If the custom field only accepts a certain # of values, delete the existing
-    # value and record a "changed from foo to bar" transaction
-    unless ( $cf->UnlimitedValues) {
-
- # We need to whack any old values here.  In most cases, the custom field should
- # only have one value to delete.  In the pathalogical case, this custom field
- # used to be a multiple and we have many values to whack....
-        my $cf_values = $values->Count;
-
-        if ( $cf_values > $cf->MaxValues ) {
-            my $i = 0;   #We want to delete all but the max we can currently have , so we can then
-                 # execute the same code to "change" the value from old to new
-            while ( my $value = $values->Next ) {
-                $i++;
-                if ( $i < $cf_values ) {
-                    my ( $val, $msg ) = $cf->DeleteValueForObject(
-                        Object  => $self,
-                        Content => $value->Content
-                    );
-                    unless ($val) {
-                        return ( 0, $msg );
-                    }
-                    my ( $TransactionId, $Msg, $TransactionObj ) =
-                      $self->_NewTransaction(
-                        Type         => 'CustomField',
-                        Field        => $cf->Id,
-                        OldReference => $value,
-                      );
-                }
-            }
-            $values->RedoSearch if $i; # redo search if have deleted at least one value
-        }
-
-        my ( $old_value, $old_content );
-        if ( $old_value = $values->First ) {
-            $old_content = $old_value->Content();
-            return (1) if( $old_content eq $args{'Value'} && $old_value->LargeContent eq $args{'LargeContent'});;
-        }
-
-        my ( $new_value_id, $value_msg ) = $cf->AddValueForObject(
-            Object       => $self,
-            Content      => $args{'Value'},
-            LargeContent => $args{'LargeContent'},
-            ContentType  => $args{'ContentType'},
-        );
-
-        unless ($new_value_id) {
-            return ( 0, $self->loc( "Could not add new custom field value: [_1]", $value_msg) );
-        }
-
-        my $new_value = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
-        $new_value->Load($new_value_id);
-
-        # now that adding the new value was successful, delete the old one
-        if ($old_value) {
-            my ( $val, $msg ) = $old_value->Delete();
-            unless ($val) {
-                return ( 0, $msg );
-            }
-        }
-
-        if ( $args{'RecordTransaction'} ) {
-            my ( $TransactionId, $Msg, $TransactionObj ) =
-              $self->_NewTransaction(
-                Type         => 'CustomField',
-                Field        => $cf->Id,
-                OldReference => $old_value,
-                NewReference => $new_value,
-              );
-        }
-
-        if ( $old_value eq '' ) {
-            return ( 1, $self->loc( "[_1] [_2] added", $cf->Name, $new_value->Content ));
-        }
-        elsif ( $new_value->Content eq '' ) {
-            return ( 1,
-                $self->loc( "[_1] [_2] deleted", $cf->Name, $old_value->Content ) );
-        }
-        else {
-            return ( 1, $self->loc( "[_1] [_2] changed to [_3]", $cf->Name, $old_content,                $new_value->Content));
-        }
-
-    }
-
-    # otherwise, just add a new value and record "new value added"
-    else {
-        my ($new_value_id, $value_msg) = $cf->AddValueForObject(
-            Object       => $self,
-            Content      => $args{'Value'},
-            LargeContent => $args{'LargeContent'},
-            ContentType  => $args{'ContentType'},
-        );
-
-        unless ($new_value_id) {
-            return ( 0, $self->loc( "Could not add new custom field value: [_1]", $value_msg) );
-        }
-        if ( $args{'RecordTransaction'} ) {
-            my ( $TransactionId, $Msg, $TransactionObj ) =
-              $self->_NewTransaction(
-                Type          => 'CustomField',
-                Field         => $cf->Id,
-                NewReference  => $new_value_id,
-                ReferenceType => 'RT::ObjectCustomFieldValue',
-              );
-            unless ($TransactionId) {
-                return ( 0,
-                    $self->loc( "Couldn't create a transaction: [_1]", $Msg ) );
-            }
-        }
-        return ( 1, $self->loc( "[_1] added as a value for [_2]", $args{'Value'}, $cf->Name));
-    }
-
-}
-
-# }}}
-
-# {{{ DeleteCustomFieldValue
-
-=head2 DeleteCustomFieldValue { Field => FIELD, Value => VALUE }
-
-Deletes VALUE as a value of CustomField FIELD. 
-
-VALUE can be a string, a CustomFieldValue or a ObjectCustomFieldValue.
-
-If VALUE is not a valid value for the custom field, returns 
-(0, 'Error message' ) otherwise, returns (1, 'Success Message')
-
-=cut
-
-sub DeleteCustomFieldValue {
-    my $self = shift;
-    my %args = (
-        Field   => undef,
-        Value   => undef,
-        ValueId => undef,
-        @_
-    );
-
-    my $cf = $self->LoadCustomFieldByIdentifier($args{'Field'});
-
-    unless ( $cf->Id ) {
-        return ( 0, $self->loc( "Custom field [_1] not found", $args{'Field'} ) );
-    }
-    my ( $val, $msg ) = $cf->DeleteValueForObject(
-        Object  => $self,
-        Id      => $args{'ValueId'},
-        Content => $args{'Value'},
-    );
-    unless ($val) {
-        return ( 0, $msg );
-    }
-    my ( $TransactionId, $Msg, $TransactionObj ) = $self->_NewTransaction(
-        Type          => 'CustomField',
-        Field         => $cf->Id,
-        OldReference  => $val,
-        ReferenceType => 'RT::ObjectCustomFieldValue',
-    );
-    unless ($TransactionId) {
-        return ( 0, $self->loc( "Couldn't create a transaction: [_1]", $Msg ) );
-    }
-
-    return (
-        $TransactionId,
-        $self->loc(
-            "[_1] is no longer a value for custom field [_2]",
-            $TransactionObj->OldValue, $cf->Name
-        )
-    );
-}
-
-# }}}
-
-# {{{ FirstCustomFieldValue
-
-=head2 FirstCustomFieldValue FIELD
-
-Return the content of the first value of CustomField FIELD for this ticket
-Takes a field id or name
-
-=cut
-
-sub FirstCustomFieldValue {
-    my $self = shift;
-    my $field = shift;
-    my $values = $self->CustomFieldValues($field);
-    if ($values->First) {
-        return $values->First->Content;
-    } else {
-        return undef;
-    }
-
-}
-
-
-
-# {{{ CustomFieldValues
-
-=head2 CustomFieldValues FIELD
-
-Return a ObjectCustomFieldValues object of all values of the CustomField whose 
-id or Name is FIELD for this record.
-
-Returns an RT::ObjectCustomFieldValues object
-
-=cut
-
-sub CustomFieldValues {
-    my $self  = shift;
-    my $field = shift;
-
-    if ($field) {
-        my $cf = $self->LoadCustomFieldByIdentifier($field);
-
-        # we were asked to search on a custom field we couldn't fine
-        unless ( $cf->id ) {
-            return RT::ObjectCustomFieldValues->new( $self->CurrentUser );
-        }
-        return ( $cf->ValuesForObject($self) );
-    }
-
-    # we're not limiting to a specific custom field;
-    my $ocfs = RT::ObjectCustomFieldValues->new( $self->CurrentUser );
-    $ocfs->LimitToObject($self);
-    return $ocfs;
-
-}
-
-=head2 CustomField IDENTIFER
-
-Find the custom field has id or name IDENTIFIER for this object.
-
-If no valid field is found, returns an empty RT::CustomField object.
-
-=cut
-
-sub LoadCustomFieldByIdentifier {
-    my $self = shift;
-    my $field = shift;
-    
-    my $cf = RT::CustomField->new($self->CurrentUser);
-
-    if ( UNIVERSAL::isa( $field, "RT::CustomField" ) ) {
-        $cf->LoadById( $field->id );
-    }
-    elsif ($field =~ /^\d+$/) {
-        $cf = RT::CustomField->new($self->CurrentUser);
-        $cf->Load($field); 
-    } else {
-
-        my $cfs = $self->CustomFields($self->CurrentUser);
-        $cfs->Limit(FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0);
-        $cf = $cfs->First || RT::CustomField->new($self->CurrentUser);
-    }
-    return $cf;
-}
-
-
-# }}}
-
-# }}}
-
-# }}}
-
-sub BasicColumns {
-}
-
-sub WikiBase {
-  return $RT::WebPath. "/index.html?q=";
-}
-
 eval "require RT::Record_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Record_Vendor.pm});
 eval "require RT::Record_Local";
index 8b24a64..a69dde0 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -89,7 +65,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -144,7 +120,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -153,14 +129,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 Description
+=item Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=head2 SetDescription VALUE
+=item SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -171,14 +147,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ScripCondition
+=item ScripCondition
 
 Returns the current value of ScripCondition. 
 (In the database, ScripCondition is stored as int(11).)
 
 
 
-=head2 SetScripCondition VALUE
+=item SetScripCondition VALUE
 
 
 Set ScripCondition to VALUE. 
@@ -189,7 +165,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ScripConditionObj
+=item ScripConditionObj
 
 Returns the ScripCondition Object which has the id returned by ScripCondition
 
@@ -203,14 +179,14 @@ sub ScripConditionObj {
        return($ScripCondition);
 }
 
-=head2 ScripAction
+=item ScripAction
 
 Returns the current value of ScripAction. 
 (In the database, ScripAction is stored as int(11).)
 
 
 
-=head2 SetScripAction VALUE
+=item SetScripAction VALUE
 
 
 Set ScripAction to VALUE. 
@@ -221,7 +197,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ScripActionObj
+=item ScripActionObj
 
 Returns the ScripAction Object which has the id returned by ScripAction
 
@@ -235,14 +211,14 @@ sub ScripActionObj {
        return($ScripAction);
 }
 
-=head2 ConditionRules
+=item ConditionRules
 
 Returns the current value of ConditionRules. 
 (In the database, ConditionRules is stored as text.)
 
 
 
-=head2 SetConditionRules VALUE
+=item SetConditionRules VALUE
 
 
 Set ConditionRules to VALUE. 
@@ -253,14 +229,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ActionRules
+=item ActionRules
 
 Returns the current value of ActionRules. 
 (In the database, ActionRules is stored as text.)
 
 
 
-=head2 SetActionRules VALUE
+=item SetActionRules VALUE
 
 
 Set ActionRules to VALUE. 
@@ -271,14 +247,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 CustomIsApplicableCode
+=item CustomIsApplicableCode
 
 Returns the current value of CustomIsApplicableCode. 
 (In the database, CustomIsApplicableCode is stored as text.)
 
 
 
-=head2 SetCustomIsApplicableCode VALUE
+=item SetCustomIsApplicableCode VALUE
 
 
 Set CustomIsApplicableCode to VALUE. 
@@ -289,14 +265,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 CustomPrepareCode
+=item CustomPrepareCode
 
 Returns the current value of CustomPrepareCode. 
 (In the database, CustomPrepareCode is stored as text.)
 
 
 
-=head2 SetCustomPrepareCode VALUE
+=item SetCustomPrepareCode VALUE
 
 
 Set CustomPrepareCode to VALUE. 
@@ -307,14 +283,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 CustomCommitCode
+=item CustomCommitCode
 
 Returns the current value of CustomCommitCode. 
 (In the database, CustomCommitCode is stored as text.)
 
 
 
-=head2 SetCustomCommitCode VALUE
+=item SetCustomCommitCode VALUE
 
 
 Set CustomCommitCode to VALUE. 
@@ -325,14 +301,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Stage
+=item Stage
 
 Returns the current value of Stage. 
 (In the database, Stage is stored as varchar(32).)
 
 
 
-=head2 SetStage VALUE
+=item SetStage VALUE
 
 
 Set Stage to VALUE. 
@@ -343,14 +319,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Queue
+=item Queue
 
 Returns the current value of Queue. 
 (In the database, Queue is stored as int(11).)
 
 
 
-=head2 SetQueue VALUE
+=item SetQueue VALUE
 
 
 Set Queue to VALUE. 
@@ -361,7 +337,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 QueueObj
+=item QueueObj
 
 Returns the Queue Object which has the id returned by Queue
 
@@ -375,14 +351,14 @@ sub QueueObj {
        return($Queue);
 }
 
-=head2 Template
+=item Template
 
 Returns the current value of Template. 
 (In the database, Template is stored as int(11).)
 
 
 
-=head2 SetTemplate VALUE
+=item SetTemplate VALUE
 
 
 Set Template to VALUE. 
@@ -393,7 +369,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 TemplateObj
+=item TemplateObj
 
 Returns the Template Object which has the id returned by Template
 
@@ -407,7 +383,7 @@ sub TemplateObj {
        return($Template);
 }
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -416,7 +392,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -425,7 +401,7 @@ Returns the current value of Created.
 =cut
 
 
-=head2 LastUpdatedBy
+=item LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -434,7 +410,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=head2 LastUpdated
+=item LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -444,41 +420,41 @@ Returns the current value of LastUpdated.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         Description => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         ScripCondition => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         ScripAction => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         ConditionRules => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
+               {read => 1, write => 1, type => 'text', default => ''},
         ActionRules => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
+               {read => 1, write => 1, type => 'text', default => ''},
         CustomIsApplicableCode => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
+               {read => 1, write => 1, type => 'text', default => ''},
         CustomPrepareCode => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
+               {read => 1, write => 1, type => 'text', default => ''},
         CustomCommitCode => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
+               {read => 1, write => 1, type => 'text', default => ''},
         Stage => 
-               {read => 1, write => 1, sql_type => 12, length => 32,  is_blob => 0,  is_numeric => 0,  type => 'varchar(32)', default => ''},
+               {read => 1, write => 1, type => 'varchar(32)', default => ''},
         Queue => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Template => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
 
  }
 };
@@ -510,7 +486,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 643977b..26824df 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -119,7 +95,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -128,14 +104,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 Name
+=item Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=head2 SetName VALUE
+=item SetName VALUE
 
 
 Set Name to VALUE. 
@@ -146,14 +122,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Description
+=item Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=head2 SetDescription VALUE
+=item SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -164,14 +140,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ExecModule
+=item ExecModule
 
 Returns the current value of ExecModule. 
 (In the database, ExecModule is stored as varchar(60).)
 
 
 
-=head2 SetExecModule VALUE
+=item SetExecModule VALUE
 
 
 Set ExecModule to VALUE. 
@@ -182,14 +158,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Argument
+=item Argument
 
 Returns the current value of Argument. 
 (In the database, Argument is stored as varchar(255).)
 
 
 
-=head2 SetArgument VALUE
+=item SetArgument VALUE
 
 
 Set Argument to VALUE. 
@@ -200,7 +176,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -209,7 +185,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -218,7 +194,7 @@ Returns the current value of Created.
 =cut
 
 
-=head2 LastUpdatedBy
+=item LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -227,7 +203,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=head2 LastUpdated
+=item LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -237,27 +213,27 @@ Returns the current value of LastUpdated.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         ExecModule => 
-               {read => 1, write => 1, sql_type => 12, length => 60,  is_blob => 0,  is_numeric => 0,  type => 'varchar(60)', default => ''},
+               {read => 1, write => 1, type => 'varchar(60)', default => ''},
         Argument => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
 
  }
 };
@@ -289,7 +265,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index d32eb7e..614ff37 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::ScripAction item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 5910797..fe0aa2d 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -122,7 +98,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -131,14 +107,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 Name
+=item Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=head2 SetName VALUE
+=item SetName VALUE
 
 
 Set Name to VALUE. 
@@ -149,14 +125,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Description
+=item Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=head2 SetDescription VALUE
+=item SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -167,14 +143,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ExecModule
+=item ExecModule
 
 Returns the current value of ExecModule. 
 (In the database, ExecModule is stored as varchar(60).)
 
 
 
-=head2 SetExecModule VALUE
+=item SetExecModule VALUE
 
 
 Set ExecModule to VALUE. 
@@ -185,14 +161,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Argument
+=item Argument
 
 Returns the current value of Argument. 
 (In the database, Argument is stored as varchar(255).)
 
 
 
-=head2 SetArgument VALUE
+=item SetArgument VALUE
 
 
 Set Argument to VALUE. 
@@ -203,14 +179,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ApplicableTransTypes
+=item ApplicableTransTypes
 
 Returns the current value of ApplicableTransTypes. 
 (In the database, ApplicableTransTypes is stored as varchar(60).)
 
 
 
-=head2 SetApplicableTransTypes VALUE
+=item SetApplicableTransTypes VALUE
 
 
 Set ApplicableTransTypes to VALUE. 
@@ -221,7 +197,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -230,7 +206,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -239,7 +215,7 @@ Returns the current value of Created.
 =cut
 
 
-=head2 LastUpdatedBy
+=item LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -248,7 +224,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=head2 LastUpdated
+=item LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -258,29 +234,29 @@ Returns the current value of LastUpdated.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         ExecModule => 
-               {read => 1, write => 1, sql_type => 12, length => 60,  is_blob => 0,  is_numeric => 0,  type => 'varchar(60)', default => ''},
+               {read => 1, write => 1, type => 'varchar(60)', default => ''},
         Argument => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         ApplicableTransTypes => 
-               {read => 1, write => 1, sql_type => 12, length => 60,  is_blob => 0,  is_numeric => 0,  type => 'varchar(60)', default => ''},
+               {read => 1, write => 1, type => 'varchar(60)', default => ''},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
 
  }
 };
@@ -312,7 +288,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index a8a3919..34f788d 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::ScripCondition item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index cff1a53..a394431 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::Scrip item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 178b66b..42ec5a7 100644 (file)
@@ -69,7 +69,7 @@ ok (require RT::SearchBuilder);
 package RT::SearchBuilder;
 
 use RT::Base;
-use DBIx::SearchBuilder "1.40";
+use DBIx::SearchBuilder "1.48";
 
 use strict;
 use vars qw(@ISA);
index 844ec29..f73ea3e 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -86,7 +62,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -129,7 +105,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -138,14 +114,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 Queue
+=item Queue
 
 Returns the current value of Queue. 
 (In the database, Queue is stored as int(11).)
 
 
 
-=head2 SetQueue VALUE
+=item SetQueue VALUE
 
 
 Set Queue to VALUE. 
@@ -156,7 +132,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 QueueObj
+=item QueueObj
 
 Returns the Queue Object which has the id returned by Queue
 
@@ -170,14 +146,14 @@ sub QueueObj {
        return($Queue);
 }
 
-=head2 Name
+=item Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=head2 SetName VALUE
+=item SetName VALUE
 
 
 Set Name to VALUE. 
@@ -188,14 +164,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Description
+=item Description
 
 Returns the current value of Description. 
 (In the database, Description is stored as varchar(255).)
 
 
 
-=head2 SetDescription VALUE
+=item SetDescription VALUE
 
 
 Set Description to VALUE. 
@@ -206,14 +182,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Type
+=item Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(16).)
 
 
 
-=head2 SetType VALUE
+=item SetType VALUE
 
 
 Set Type to VALUE. 
@@ -224,14 +200,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Language
+=item Language
 
 Returns the current value of Language. 
 (In the database, Language is stored as varchar(16).)
 
 
 
-=head2 SetLanguage VALUE
+=item SetLanguage VALUE
 
 
 Set Language to VALUE. 
@@ -242,14 +218,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 TranslationOf
+=item TranslationOf
 
 Returns the current value of TranslationOf. 
 (In the database, TranslationOf is stored as int(11).)
 
 
 
-=head2 SetTranslationOf VALUE
+=item SetTranslationOf VALUE
 
 
 Set TranslationOf to VALUE. 
@@ -260,14 +236,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Content
+=item Content
 
 Returns the current value of Content. 
 (In the database, Content is stored as blob.)
 
 
 
-=head2 SetContent VALUE
+=item SetContent VALUE
 
 
 Set Content to VALUE. 
@@ -278,7 +254,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 LastUpdated
+=item LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -287,7 +263,7 @@ Returns the current value of LastUpdated.
 =cut
 
 
-=head2 LastUpdatedBy
+=item LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -296,7 +272,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -305,7 +281,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -315,33 +291,33 @@ Returns the current value of Created.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         Queue => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Name => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         Description => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         Type => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         Language => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         TranslationOf => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Content => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
+               {read => 1, write => 1, type => 'blob', default => ''},
         LastUpdated => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
 
  }
 };
@@ -373,7 +349,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 3283806..37db840 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::Template item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index b17683e..2f075a2 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -86,7 +62,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -168,7 +144,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -177,14 +153,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 EffectiveId
+=item EffectiveId
 
 Returns the current value of EffectiveId. 
 (In the database, EffectiveId is stored as int(11).)
 
 
 
-=head2 SetEffectiveId VALUE
+=item SetEffectiveId VALUE
 
 
 Set EffectiveId to VALUE. 
@@ -195,14 +171,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Queue
+=item Queue
 
 Returns the current value of Queue. 
 (In the database, Queue is stored as int(11).)
 
 
 
-=head2 SetQueue VALUE
+=item SetQueue VALUE
 
 
 Set Queue to VALUE. 
@@ -213,7 +189,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 QueueObj
+=item QueueObj
 
 Returns the Queue Object which has the id returned by Queue
 
@@ -227,14 +203,14 @@ sub QueueObj {
        return($Queue);
 }
 
-=head2 Type
+=item Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(16).)
 
 
 
-=head2 SetType VALUE
+=item SetType VALUE
 
 
 Set Type to VALUE. 
@@ -245,14 +221,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 IssueStatement
+=item IssueStatement
 
 Returns the current value of IssueStatement. 
 (In the database, IssueStatement is stored as int(11).)
 
 
 
-=head2 SetIssueStatement VALUE
+=item SetIssueStatement VALUE
 
 
 Set IssueStatement to VALUE. 
@@ -263,14 +239,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Resolution
+=item Resolution
 
 Returns the current value of Resolution. 
 (In the database, Resolution is stored as int(11).)
 
 
 
-=head2 SetResolution VALUE
+=item SetResolution VALUE
 
 
 Set Resolution to VALUE. 
@@ -281,14 +257,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Owner
+=item Owner
 
 Returns the current value of Owner. 
 (In the database, Owner is stored as int(11).)
 
 
 
-=head2 SetOwner VALUE
+=item SetOwner VALUE
 
 
 Set Owner to VALUE. 
@@ -299,14 +275,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Subject
+=item Subject
 
 Returns the current value of Subject. 
 (In the database, Subject is stored as varchar(200).)
 
 
 
-=head2 SetSubject VALUE
+=item SetSubject VALUE
 
 
 Set Subject to VALUE. 
@@ -317,14 +293,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 InitialPriority
+=item InitialPriority
 
 Returns the current value of InitialPriority. 
 (In the database, InitialPriority is stored as int(11).)
 
 
 
-=head2 SetInitialPriority VALUE
+=item SetInitialPriority VALUE
 
 
 Set InitialPriority to VALUE. 
@@ -335,14 +311,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 FinalPriority
+=item FinalPriority
 
 Returns the current value of FinalPriority. 
 (In the database, FinalPriority is stored as int(11).)
 
 
 
-=head2 SetFinalPriority VALUE
+=item SetFinalPriority VALUE
 
 
 Set FinalPriority to VALUE. 
@@ -353,14 +329,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Priority
+=item Priority
 
 Returns the current value of Priority. 
 (In the database, Priority is stored as int(11).)
 
 
 
-=head2 SetPriority VALUE
+=item SetPriority VALUE
 
 
 Set Priority to VALUE. 
@@ -371,14 +347,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 TimeEstimated
+=item TimeEstimated
 
 Returns the current value of TimeEstimated. 
 (In the database, TimeEstimated is stored as int(11).)
 
 
 
-=head2 SetTimeEstimated VALUE
+=item SetTimeEstimated VALUE
 
 
 Set TimeEstimated to VALUE. 
@@ -389,14 +365,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 TimeWorked
+=item TimeWorked
 
 Returns the current value of TimeWorked. 
 (In the database, TimeWorked is stored as int(11).)
 
 
 
-=head2 SetTimeWorked VALUE
+=item SetTimeWorked VALUE
 
 
 Set TimeWorked to VALUE. 
@@ -407,14 +383,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Status
+=item Status
 
 Returns the current value of Status. 
 (In the database, Status is stored as varchar(10).)
 
 
 
-=head2 SetStatus VALUE
+=item SetStatus VALUE
 
 
 Set Status to VALUE. 
@@ -425,14 +401,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 TimeLeft
+=item TimeLeft
 
 Returns the current value of TimeLeft. 
 (In the database, TimeLeft is stored as int(11).)
 
 
 
-=head2 SetTimeLeft VALUE
+=item SetTimeLeft VALUE
 
 
 Set TimeLeft to VALUE. 
@@ -443,14 +419,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Told
+=item Told
 
 Returns the current value of Told. 
 (In the database, Told is stored as datetime.)
 
 
 
-=head2 SetTold VALUE
+=item SetTold VALUE
 
 
 Set Told to VALUE. 
@@ -461,14 +437,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Starts
+=item Starts
 
 Returns the current value of Starts. 
 (In the database, Starts is stored as datetime.)
 
 
 
-=head2 SetStarts VALUE
+=item SetStarts VALUE
 
 
 Set Starts to VALUE. 
@@ -479,14 +455,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Started
+=item Started
 
 Returns the current value of Started. 
 (In the database, Started is stored as datetime.)
 
 
 
-=head2 SetStarted VALUE
+=item SetStarted VALUE
 
 
 Set Started to VALUE. 
@@ -497,14 +473,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Due
+=item Due
 
 Returns the current value of Due. 
 (In the database, Due is stored as datetime.)
 
 
 
-=head2 SetDue VALUE
+=item SetDue VALUE
 
 
 Set Due to VALUE. 
@@ -515,14 +491,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Resolved
+=item Resolved
 
 Returns the current value of Resolved. 
 (In the database, Resolved is stored as datetime.)
 
 
 
-=head2 SetResolved VALUE
+=item SetResolved VALUE
 
 
 Set Resolved to VALUE. 
@@ -533,7 +509,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 LastUpdatedBy
+=item LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -542,7 +518,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=head2 LastUpdated
+=item LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -551,7 +527,7 @@ Returns the current value of LastUpdated.
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -560,7 +536,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -569,14 +545,14 @@ Returns the current value of Created.
 =cut
 
 
-=head2 Disabled
+=item Disabled
 
 Returns the current value of Disabled. 
 (In the database, Disabled is stored as smallint(6).)
 
 
 
-=head2 SetDisabled VALUE
+=item SetDisabled VALUE
 
 
 Set Disabled to VALUE. 
@@ -588,59 +564,59 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         EffectiveId => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Queue => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Type => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         IssueStatement => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Resolution => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Owner => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Subject => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => '[no subject]'},
+               {read => 1, write => 1, type => 'varchar(200)', default => '[no subject]'},
         InitialPriority => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         FinalPriority => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Priority => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         TimeEstimated => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         TimeWorked => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Status => 
-               {read => 1, write => 1, sql_type => 12, length => 10,  is_blob => 0,  is_numeric => 0,  type => 'varchar(10)', default => ''},
+               {read => 1, write => 1, type => 'varchar(10)', default => ''},
         TimeLeft => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Told => 
-               {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, write => 1, type => 'datetime', default => ''},
         Starts => 
-               {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, write => 1, type => 'datetime', default => ''},
         Started => 
-               {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, write => 1, type => 'datetime', default => ''},
         Due => 
-               {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, write => 1, type => 'datetime', default => ''},
         Resolved => 
-               {read => 1, write => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, write => 1, type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         Disabled => 
-               {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
+               {read => 1, write => 1, type => 'smallint(6)', default => '0'},
 
  }
 };
@@ -672,7 +648,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
diff --git a/rt/lib/RT/TicketCustomFieldValue.pm b/rt/lib/RT/TicketCustomFieldValue.pm
deleted file mode 100644 (file)
index 7176472..0000000
+++ /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 (file)
index 270c593..0000000
+++ /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 (file)
index 2174afe..0000000
+++ /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 (file)
index 8cbaca5..0000000
+++ /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;
-
index 354f4c5..b6b3491 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::Ticket item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 67c5cd0..ca491a6 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# CONTRIBUTION SUBMISSION POLICY:
 # 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-# 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -69,6 +45,7 @@ RT::Transaction
 
 package RT::Transaction;
 use RT::Record; 
+use RT::Ticket;
 
 
 use vars qw( @ISA );
@@ -85,21 +62,18 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
-  varchar(64) 'ObjectType'.
-  int(11) 'ObjectId'.
+  int(11) 'EffectiveTicket'.
+  int(11) 'Ticket'.
   int(11) 'TimeTaken'.
   varchar(20) 'Type'.
   varchar(40) 'Field'.
   varchar(255) 'OldValue'.
   varchar(255) 'NewValue'.
-  varchar(255) 'ReferenceType'.
-  int(11) 'OldReference'.
-  int(11) 'NewReference'.
-  varchar(255) 'Data'.
+  varchar(100) 'Data'.
 
 =cut
 
@@ -109,30 +83,24 @@ Create takes a hash of values and creates a row in the database:
 sub Create {
     my $self = shift;
     my %args = ( 
-                ObjectType => '',
-                ObjectId => '0',
+                EffectiveTicket => '0',
+                Ticket => '0',
                 TimeTaken => '0',
                 Type => '',
                 Field => '',
                 OldValue => '',
                 NewValue => '',
-                ReferenceType => '',
-                OldReference => '',
-                NewReference => '',
                 Data => '',
 
                  @_);
     $self->SUPER::Create(
-                         ObjectType => $args{'ObjectType'},
-                         ObjectId => $args{'ObjectId'},
+                         EffectiveTicket => $args{'EffectiveTicket'},
+                         Ticket => $args{'Ticket'},
                          TimeTaken => $args{'TimeTaken'},
                          Type => $args{'Type'},
                          Field => $args{'Field'},
                          OldValue => $args{'OldValue'},
                          NewValue => $args{'NewValue'},
-                         ReferenceType => $args{'ReferenceType'},
-                         OldReference => $args{'OldReference'},
-                         NewReference => $args{'NewReference'},
                          Data => $args{'Data'},
 );
 
@@ -140,7 +108,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -149,50 +117,64 @@ Returns the current value of id.
 =cut
 
 
-=head2 ObjectType
+=item EffectiveTicket
 
-Returns the current value of ObjectType
-(In the database, ObjectType is stored as varchar(64).)
+Returns the current value of EffectiveTicket
+(In the database, EffectiveTicket is stored as int(11).)
 
 
 
-=head2 SetObjectType VALUE
+=item SetEffectiveTicket VALUE
 
 
-Set ObjectType to VALUE. 
+Set EffectiveTicket to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, ObjectType will be stored as a varchar(64).)
+(In the database, EffectiveTicket will be stored as a int(11).)
 
 
 =cut
 
 
-=head2 ObjectId
+=item Ticket
 
-Returns the current value of ObjectId
-(In the database, ObjectId is stored as int(11).)
+Returns the current value of Ticket
+(In the database, Ticket is stored as int(11).)
 
 
 
-=head2 SetObjectId VALUE
+=item SetTicket VALUE
 
 
-Set ObjectId to VALUE. 
+Set Ticket to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, ObjectId will be stored as a int(11).)
+(In the database, Ticket will be stored as a int(11).)
+
+
+=cut
+
+
+=item TicketObj
+
+Returns the Ticket Object which has the id returned by Ticket
 
 
 =cut
 
+sub TicketObj {
+       my $self = shift;
+       my $Ticket =  RT::Ticket->new($self->CurrentUser);
+       $Ticket->Load($self->__Value('Ticket'));
+       return($Ticket);
+}
 
-=head2 TimeTaken
+=item TimeTaken
 
 Returns the current value of TimeTaken. 
 (In the database, TimeTaken is stored as int(11).)
 
 
 
-=head2 SetTimeTaken VALUE
+=item SetTimeTaken VALUE
 
 
 Set TimeTaken to VALUE. 
@@ -203,14 +185,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Type
+=item Type
 
 Returns the current value of Type. 
 (In the database, Type is stored as varchar(20).)
 
 
 
-=head2 SetType VALUE
+=item SetType VALUE
 
 
 Set Type to VALUE. 
@@ -221,14 +203,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Field
+=item Field
 
 Returns the current value of Field. 
 (In the database, Field is stored as varchar(40).)
 
 
 
-=head2 SetField VALUE
+=item SetField VALUE
 
 
 Set Field to VALUE. 
@@ -239,14 +221,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 OldValue
+=item OldValue
 
 Returns the current value of OldValue. 
 (In the database, OldValue is stored as varchar(255).)
 
 
 
-=head2 SetOldValue VALUE
+=item SetOldValue VALUE
 
 
 Set OldValue to VALUE. 
@@ -257,14 +239,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 NewValue
+=item NewValue
 
 Returns the current value of NewValue. 
 (In the database, NewValue is stored as varchar(255).)
 
 
 
-=head2 SetNewValue VALUE
+=item SetNewValue VALUE
 
 
 Set NewValue to VALUE. 
@@ -275,79 +257,25 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ReferenceType
-
-Returns the current value of ReferenceType. 
-(In the database, ReferenceType is stored as varchar(255).)
-
-
-
-=head2 SetReferenceType VALUE
-
-
-Set ReferenceType to VALUE. 
-Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, ReferenceType will be stored as a varchar(255).)
-
-
-=cut
-
-
-=head2 OldReference
-
-Returns the current value of OldReference. 
-(In the database, OldReference is stored as int(11).)
-
-
-
-=head2 SetOldReference VALUE
-
-
-Set OldReference to VALUE. 
-Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, OldReference will be stored as a int(11).)
-
-
-=cut
-
-
-=head2 NewReference
-
-Returns the current value of NewReference. 
-(In the database, NewReference is stored as int(11).)
-
-
-
-=head2 SetNewReference VALUE
-
-
-Set NewReference to VALUE. 
-Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, NewReference will be stored as a int(11).)
-
-
-=cut
-
-
-=head2 Data
+=item Data
 
 Returns the current value of Data. 
-(In the database, Data is stored as varchar(255).)
+(In the database, Data is stored as varchar(100).)
 
 
 
-=head2 SetData VALUE
+=item SetData VALUE
 
 
 Set Data to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Data will be stored as a varchar(255).)
+(In the database, Data will be stored as a varchar(100).)
 
 
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -356,7 +284,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -366,37 +294,31 @@ Returns the current value of Created.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
-        ObjectType => 
-               {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
-        ObjectId => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, type => 'int(11)', default => ''},
+        EffectiveTicket => 
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
+        Ticket => 
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         TimeTaken => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, write => 1, type => 'int(11)', default => '0'},
         Type => 
-               {read => 1, write => 1, sql_type => 12, length => 20,  is_blob => 0,  is_numeric => 0,  type => 'varchar(20)', default => ''},
+               {read => 1, write => 1, type => 'varchar(20)', default => ''},
         Field => 
-               {read => 1, write => 1, sql_type => 12, length => 40,  is_blob => 0,  is_numeric => 0,  type => 'varchar(40)', default => ''},
+               {read => 1, write => 1, type => 'varchar(40)', default => ''},
         OldValue => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         NewValue => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
-        ReferenceType => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
-        OldReference => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
-        NewReference => 
-               {read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, write => 1, type => 'varchar(255)', default => ''},
         Data => 
-               {read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+               {read => 1, write => 1, type => 'varchar(100)', default => ''},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
 
  }
 };
@@ -428,7 +350,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 86f8a2d..23a475a 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::Transaction item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
diff --git a/rt/lib/RT/URI/freeside.pm b/rt/lib/RT/URI/freeside.pm
new file mode 100644 (file)
index 0000000..ff1d38d
--- /dev/null
@@ -0,0 +1,285 @@
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 2004 Kristian Hoffmann <khoff@fire2wire.com>
+# Based on the original RT::URI::base and RT::URI::fsck_com_rt.
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+package RT::URI::freeside;
+
+use RT::URI::base;
+use strict;
+use vars qw(@ISA $IntegrationType $URL);
+@ISA = qw/RT::URI::base/;
+
+
+=head1 NAME
+
+RT::URI::freeside
+
+=head1 DESCRIPTION
+
+URI handler for freeside URIs.  See http://www.sisd.com/freeside/ 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' and $table eq 'cust_main') {
+    my $name = $rec->{'last'} . ', ' . $rec->{'first'};
+    $name = $rec->{'company'} . " ($name)" if $rec->{'company'};
+    $label = "$pkey: $name";
+  } 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;
+      $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 (file)
index 0000000..9ca0630
--- /dev/null
@@ -0,0 +1,138 @@
+# 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 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 small_custview {
+
+  return &FS::CGI::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 (file)
index 0000000..a8a731f
--- /dev/null
@@ -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('CGI.small_custview', @_);
+
+}
+
+1;
index c28d89f..cbc10f5 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -85,7 +61,7 @@ sub _Init {
 
 
 
-=head2 Create PARAMHASH
+=item Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
 
@@ -194,7 +170,7 @@ sub Create {
 
 
 
-=head2 id
+=item id
 
 Returns the current value of id. 
 (In the database, id is stored as int(11).)
@@ -203,14 +179,14 @@ Returns the current value of id.
 =cut
 
 
-=head2 Name
+=item Name
 
 Returns the current value of Name. 
 (In the database, Name is stored as varchar(200).)
 
 
 
-=head2 SetName VALUE
+=item SetName VALUE
 
 
 Set Name to VALUE. 
@@ -221,14 +197,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Password
+=item Password
 
 Returns the current value of Password. 
 (In the database, Password is stored as varchar(40).)
 
 
 
-=head2 SetPassword VALUE
+=item SetPassword VALUE
 
 
 Set Password to VALUE. 
@@ -239,14 +215,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Comments
+=item Comments
 
 Returns the current value of Comments. 
 (In the database, Comments is stored as blob.)
 
 
 
-=head2 SetComments VALUE
+=item SetComments VALUE
 
 
 Set Comments to VALUE. 
@@ -257,14 +233,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Signature
+=item Signature
 
 Returns the current value of Signature. 
 (In the database, Signature is stored as blob.)
 
 
 
-=head2 SetSignature VALUE
+=item SetSignature VALUE
 
 
 Set Signature to VALUE. 
@@ -275,14 +251,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 EmailAddress
+=item EmailAddress
 
 Returns the current value of EmailAddress. 
 (In the database, EmailAddress is stored as varchar(120).)
 
 
 
-=head2 SetEmailAddress VALUE
+=item SetEmailAddress VALUE
 
 
 Set EmailAddress to VALUE. 
@@ -293,14 +269,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 FreeformContactInfo
+=item FreeformContactInfo
 
 Returns the current value of FreeformContactInfo. 
 (In the database, FreeformContactInfo is stored as blob.)
 
 
 
-=head2 SetFreeformContactInfo VALUE
+=item SetFreeformContactInfo VALUE
 
 
 Set FreeformContactInfo to VALUE. 
@@ -311,14 +287,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Organization
+=item Organization
 
 Returns the current value of Organization. 
 (In the database, Organization is stored as varchar(200).)
 
 
 
-=head2 SetOrganization VALUE
+=item SetOrganization VALUE
 
 
 Set Organization to VALUE. 
@@ -329,14 +305,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 RealName
+=item RealName
 
 Returns the current value of RealName. 
 (In the database, RealName is stored as varchar(120).)
 
 
 
-=head2 SetRealName VALUE
+=item SetRealName VALUE
 
 
 Set RealName to VALUE. 
@@ -347,14 +323,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 NickName
+=item NickName
 
 Returns the current value of NickName. 
 (In the database, NickName is stored as varchar(16).)
 
 
 
-=head2 SetNickName VALUE
+=item SetNickName VALUE
 
 
 Set NickName to VALUE. 
@@ -365,14 +341,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Lang
+=item Lang
 
 Returns the current value of Lang. 
 (In the database, Lang is stored as varchar(16).)
 
 
 
-=head2 SetLang VALUE
+=item SetLang VALUE
 
 
 Set Lang to VALUE. 
@@ -383,14 +359,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 EmailEncoding
+=item EmailEncoding
 
 Returns the current value of EmailEncoding. 
 (In the database, EmailEncoding is stored as varchar(16).)
 
 
 
-=head2 SetEmailEncoding VALUE
+=item SetEmailEncoding VALUE
 
 
 Set EmailEncoding to VALUE. 
@@ -401,14 +377,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 WebEncoding
+=item WebEncoding
 
 Returns the current value of WebEncoding. 
 (In the database, WebEncoding is stored as varchar(16).)
 
 
 
-=head2 SetWebEncoding VALUE
+=item SetWebEncoding VALUE
 
 
 Set WebEncoding to VALUE. 
@@ -419,14 +395,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ExternalContactInfoId
+=item ExternalContactInfoId
 
 Returns the current value of ExternalContactInfoId. 
 (In the database, ExternalContactInfoId is stored as varchar(100).)
 
 
 
-=head2 SetExternalContactInfoId VALUE
+=item SetExternalContactInfoId VALUE
 
 
 Set ExternalContactInfoId to VALUE. 
@@ -437,14 +413,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ContactInfoSystem
+=item ContactInfoSystem
 
 Returns the current value of ContactInfoSystem. 
 (In the database, ContactInfoSystem is stored as varchar(30).)
 
 
 
-=head2 SetContactInfoSystem VALUE
+=item SetContactInfoSystem VALUE
 
 
 Set ContactInfoSystem to VALUE. 
@@ -455,14 +431,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 ExternalAuthId
+=item ExternalAuthId
 
 Returns the current value of ExternalAuthId. 
 (In the database, ExternalAuthId is stored as varchar(100).)
 
 
 
-=head2 SetExternalAuthId VALUE
+=item SetExternalAuthId VALUE
 
 
 Set ExternalAuthId to VALUE. 
@@ -473,14 +449,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 AuthSystem
+=item AuthSystem
 
 Returns the current value of AuthSystem. 
 (In the database, AuthSystem is stored as varchar(30).)
 
 
 
-=head2 SetAuthSystem VALUE
+=item SetAuthSystem VALUE
 
 
 Set AuthSystem to VALUE. 
@@ -491,14 +467,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Gecos
+=item Gecos
 
 Returns the current value of Gecos. 
 (In the database, Gecos is stored as varchar(16).)
 
 
 
-=head2 SetGecos VALUE
+=item SetGecos VALUE
 
 
 Set Gecos to VALUE. 
@@ -509,14 +485,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 HomePhone
+=item HomePhone
 
 Returns the current value of HomePhone. 
 (In the database, HomePhone is stored as varchar(30).)
 
 
 
-=head2 SetHomePhone VALUE
+=item SetHomePhone VALUE
 
 
 Set HomePhone to VALUE. 
@@ -527,14 +503,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 WorkPhone
+=item WorkPhone
 
 Returns the current value of WorkPhone. 
 (In the database, WorkPhone is stored as varchar(30).)
 
 
 
-=head2 SetWorkPhone VALUE
+=item SetWorkPhone VALUE
 
 
 Set WorkPhone to VALUE. 
@@ -545,14 +521,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 MobilePhone
+=item MobilePhone
 
 Returns the current value of MobilePhone. 
 (In the database, MobilePhone is stored as varchar(30).)
 
 
 
-=head2 SetMobilePhone VALUE
+=item SetMobilePhone VALUE
 
 
 Set MobilePhone to VALUE. 
@@ -563,14 +539,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 PagerPhone
+=item PagerPhone
 
 Returns the current value of PagerPhone. 
 (In the database, PagerPhone is stored as varchar(30).)
 
 
 
-=head2 SetPagerPhone VALUE
+=item SetPagerPhone VALUE
 
 
 Set PagerPhone to VALUE. 
@@ -581,14 +557,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Address1
+=item Address1
 
 Returns the current value of Address1. 
 (In the database, Address1 is stored as varchar(200).)
 
 
 
-=head2 SetAddress1 VALUE
+=item SetAddress1 VALUE
 
 
 Set Address1 to VALUE. 
@@ -599,14 +575,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Address2
+=item Address2
 
 Returns the current value of Address2. 
 (In the database, Address2 is stored as varchar(200).)
 
 
 
-=head2 SetAddress2 VALUE
+=item SetAddress2 VALUE
 
 
 Set Address2 to VALUE. 
@@ -617,14 +593,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 City
+=item City
 
 Returns the current value of City. 
 (In the database, City is stored as varchar(100).)
 
 
 
-=head2 SetCity VALUE
+=item SetCity VALUE
 
 
 Set City to VALUE. 
@@ -635,14 +611,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 State
+=item State
 
 Returns the current value of State. 
 (In the database, State is stored as varchar(100).)
 
 
 
-=head2 SetState VALUE
+=item SetState VALUE
 
 
 Set State to VALUE. 
@@ -653,14 +629,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Zip
+=item Zip
 
 Returns the current value of Zip. 
 (In the database, Zip is stored as varchar(16).)
 
 
 
-=head2 SetZip VALUE
+=item SetZip VALUE
 
 
 Set Zip to VALUE. 
@@ -671,14 +647,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Country
+=item Country
 
 Returns the current value of Country. 
 (In the database, Country is stored as varchar(50).)
 
 
 
-=head2 SetCountry VALUE
+=item SetCountry VALUE
 
 
 Set Country to VALUE. 
@@ -689,14 +665,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Timezone
+=item Timezone
 
 Returns the current value of Timezone. 
 (In the database, Timezone is stored as varchar(50).)
 
 
 
-=head2 SetTimezone VALUE
+=item SetTimezone VALUE
 
 
 Set Timezone to VALUE. 
@@ -707,14 +683,14 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 PGPKey
+=item PGPKey
 
 Returns the current value of PGPKey. 
 (In the database, PGPKey is stored as text.)
 
 
 
-=head2 SetPGPKey VALUE
+=item SetPGPKey VALUE
 
 
 Set PGPKey to VALUE. 
@@ -725,7 +701,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =cut
 
 
-=head2 Creator
+=item Creator
 
 Returns the current value of Creator. 
 (In the database, Creator is stored as int(11).)
@@ -734,7 +710,7 @@ Returns the current value of Creator.
 =cut
 
 
-=head2 Created
+=item Created
 
 Returns the current value of Created. 
 (In the database, Created is stored as datetime.)
@@ -743,7 +719,7 @@ Returns the current value of Created.
 =cut
 
 
-=head2 LastUpdatedBy
+=item LastUpdatedBy
 
 Returns the current value of LastUpdatedBy. 
 (In the database, LastUpdatedBy is stored as int(11).)
@@ -752,7 +728,7 @@ Returns the current value of LastUpdatedBy.
 =cut
 
 
-=head2 LastUpdated
+=item LastUpdated
 
 Returns the current value of LastUpdated. 
 (In the database, LastUpdated is stored as datetime.)
@@ -762,77 +738,77 @@ Returns the current value of LastUpdated.
 
 
 
-sub _CoreAccessible {
+sub _ClassAccessible {
     {
      
         id =>
-               {read => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
+               {read => 1, type => 'int(11)', default => ''},
         Name => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         Password => 
-               {read => 1, write => 1, sql_type => 12, length => 40,  is_blob => 0,  is_numeric => 0,  type => 'varchar(40)', default => ''},
+               {read => 1, write => 1, type => 'varchar(40)', default => ''},
         Comments => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
+               {read => 1, write => 1, type => 'blob', default => ''},
         Signature => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
+               {read => 1, write => 1, type => 'blob', default => ''},
         EmailAddress => 
-               {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
+               {read => 1, write => 1, type => 'varchar(120)', default => ''},
         FreeformContactInfo => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'blob', default => ''},
+               {read => 1, write => 1, type => 'blob', default => ''},
         Organization => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         RealName => 
-               {read => 1, write => 1, sql_type => 12, length => 120,  is_blob => 0,  is_numeric => 0,  type => 'varchar(120)', default => ''},
+               {read => 1, write => 1, type => 'varchar(120)', default => ''},
         NickName => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         Lang => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         EmailEncoding => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         WebEncoding => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         ExternalContactInfoId => 
-               {read => 1, write => 1, sql_type => 12, length => 100,  is_blob => 0,  is_numeric => 0,  type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, type => 'varchar(100)', default => ''},
         ContactInfoSystem => 
-               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, type => 'varchar(30)', default => ''},
         ExternalAuthId => 
-               {read => 1, write => 1, sql_type => 12, length => 100,  is_blob => 0,  is_numeric => 0,  type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, type => 'varchar(100)', default => ''},
         AuthSystem => 
-               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, type => 'varchar(30)', default => ''},
         Gecos => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         HomePhone => 
-               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, type => 'varchar(30)', default => ''},
         WorkPhone => 
-               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, type => 'varchar(30)', default => ''},
         MobilePhone => 
-               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, type => 'varchar(30)', default => ''},
         PagerPhone => 
-               {read => 1, write => 1, sql_type => 12, length => 30,  is_blob => 0,  is_numeric => 0,  type => 'varchar(30)', default => ''},
+               {read => 1, write => 1, type => 'varchar(30)', default => ''},
         Address1 => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         Address2 => 
-               {read => 1, write => 1, sql_type => 12, length => 200,  is_blob => 0,  is_numeric => 0,  type => 'varchar(200)', default => ''},
+               {read => 1, write => 1, type => 'varchar(200)', default => ''},
         City => 
-               {read => 1, write => 1, sql_type => 12, length => 100,  is_blob => 0,  is_numeric => 0,  type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, type => 'varchar(100)', default => ''},
         State => 
-               {read => 1, write => 1, sql_type => 12, length => 100,  is_blob => 0,  is_numeric => 0,  type => 'varchar(100)', default => ''},
+               {read => 1, write => 1, type => 'varchar(100)', default => ''},
         Zip => 
-               {read => 1, write => 1, sql_type => 12, length => 16,  is_blob => 0,  is_numeric => 0,  type => 'varchar(16)', default => ''},
+               {read => 1, write => 1, type => 'varchar(16)', default => ''},
         Country => 
-               {read => 1, write => 1, sql_type => 12, length => 50,  is_blob => 0,  is_numeric => 0,  type => 'varchar(50)', default => ''},
+               {read => 1, write => 1, type => 'varchar(50)', default => ''},
         Timezone => 
-               {read => 1, write => 1, sql_type => 12, length => 50,  is_blob => 0,  is_numeric => 0,  type => 'varchar(50)', default => ''},
+               {read => 1, write => 1, type => 'varchar(50)', default => ''},
         PGPKey => 
-               {read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
+               {read => 1, write => 1, type => 'text', default => ''},
         Creator => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         Created => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
         LastUpdatedBy => 
-               {read => 1, auto => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => '0'},
+               {read => 1, auto => 1, type => 'int(11)', default => '0'},
         LastUpdated => 
-               {read => 1, auto => 1, sql_type => 11, length => 0,  is_blob => 0,  is_numeric => 0,  type => 'datetime', default => ''},
+               {read => 1, auto => 1, type => 'datetime', default => ''},
 
  }
 };
@@ -864,7 +840,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
index 7b71c56..d58f696 100755 (executable)
@@ -1,14 +1,8 @@
-# BEGIN BPS TAGGED BLOCK {{{
+# BEGIN LICENSE BLOCK
 # 
-# COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC 
-#                                          <jesse@bestpractical.com>
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
 # 
-# (Except where explicitly superseded by other copyright notices)
-# 
-# 
-# LICENSE:
+# (Except where explictly superceded by other copyright notices)
 # 
 # This work is made available to you under the terms of Version 2 of
 # the GNU General Public License. A copy of that license should have
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 # General Public License for more details.
 # 
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/copyleft/gpl.html.
-# 
-# 
-# CONTRIBUTION SUBMISSION POLICY:
-# 
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
 # 
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license 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 LICENSE BLOCK
 # Autogenerated by DBIx::SearchBuilder factory (by <jesse@bestpractical.com>)
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -88,7 +64,7 @@ sub _Init {
 }
 
 
-=head2 NewItem
+=item NewItem
 
 Returns an empty new RT::User item
 
@@ -125,7 +101,7 @@ _Vendor is for 3rd-party vendor add-ons, while _Local is for site-local customiz
 
 These overlay files can contain new subs or subs to replace existing subs in this module.
 
-Each of these files should begin with the line 
+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);
 
diff --git a/rt/lib/RTx/Statistics.pm b/rt/lib/RTx/Statistics.pm
new file mode 100755 (executable)
index 0000000..8b9d6e4
--- /dev/null
@@ -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 (file)
index 0000000..5f086a2
--- /dev/null
@@ -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.in b/rt/lib/t/00smoke.t.in
deleted file mode 100644 (file)
index 288dd4a..0000000
+++ /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 (file)
index d132330..0000000
+++ /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;
-
index 4504cc7..4cc1318 100644 (file)
@@ -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 (file)
index c2e3277..0000000
+++ /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";
index 94ad3e9..597ad10 100644 (file)
@@ -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 (file)
index 25c26e7..0000000
+++ /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;
index c384eed..973d9d2 100644 (file)
@@ -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 (file)
index 39ab0d2..0000000
+++ /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 (file)
index a6b3d74..0000000
+++ /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/regression/00placeholder b/rt/lib/t/regression/00placeholder
deleted file mode 100644 (file)
index 0afc604..0000000
+++ /dev/null
@@ -1 +0,0 @@
-1;
diff --git a/rt/sbin/rt-setup-database b/rt/sbin/rt-setup-database
deleted file mode 100644 (file)
index 58f882f..0000000
+++ /dev/null
@@ -1,619 +0,0 @@
-#!/usr/bin/perl -w
-# BEGIN LICENSE BLOCK
-# 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-# 
-# (Except where explictly superceded by other copyright notices)
-# 
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-# 
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
-# 
-# 
-# END LICENSE BLOCK
-
-use strict;
-use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
-use vars
-  qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
-
-use lib "/opt/rt3/lib";
-
-#This drags in  RT's config.pm
-# We do it in a begin block because RT::Handle needs to know the type to do its
-# inheritance
-use RT;
-use Carp;
-use RT::User;
-use RT::CurrentUser;
-use RT::Template;
-use RT::ScripAction;
-use RT::ACE;
-use RT::Group;
-use RT::User;
-use RT::Queue;
-use RT::ScripCondition;
-use RT::CustomField;
-use RT::Scrip;
-
-RT::LoadConfig();
-use Term::ReadKey;
-use Getopt::Long;
-
-my %args;
-
-GetOptions(
-    \%args,
-    'prompt-for-dba-password', 'force', 'debug',
-    'action=s',                'dba=s', 'dba-password=s', 'datafile=s',
-    'datadir=s'
-);
-
-$| = 1;    #unbuffer that output.
-
-require RT::Handle;
-my $Handle = RT::Handle->new($RT::DatabaseType);
-$Handle->BuildDSN;
-my $dbh;
-
-if ( $args{'prompt-for-dba-password'} ) {
-    $args{'dba-password'} = get_dba_password();
-    chomp( $args{'dba-password'} );
-}
-
-unless ( $args{'action'} ) {
-    help();
-    die;
-}
-if ( $args{'action'} eq 'init' ) {
-    $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
-      || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
-    print "Now creating a database for RT.\n";
-    if ($RT::DatabaseType ne 'Oracle' ||
-        $args{'dba'} ne $RT::DatabaseUser) {
-    create_db();
-    } else {
-        print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
-    }
-
-    $dbh->disconnect;
-    $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
-      || die $DBI::errstr;
-
-    print "Now populating database schema.\n";
-    insert_schema();
-    print "Now inserting database ACLs\n";
-    insert_acl() unless ($RT::DatabaseType eq 'Oracle');
-    print "Now inserting RT core system objects\n";
-    insert_initial_data();
-    print "Now inserting RT data\n";
-    insert_data( $RT::EtcPath . "/initialdata" );
-}
-elsif ( $args{'action'} eq 'drop' ) {
-    unless ( $dbh =
-         DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
-    {
-        warn $DBI::errstr;
-        warn "Database doesn't appear to exist. Aborting database drop.";
-        exit(0);
-    }
-    drop_db();
-}
-elsif ( $args{'action'} eq 'insert' ) {
-    insert_data( $args{'datafile'} );
-}
-elsif ($args{'action'} eq 'acl') {
-    $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
-      || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
-     insert_acl($args{'datadir'});
-}
-elsif ($args{'action'} eq 'schema') {
-    $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
-      || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
-        insert_schema($args{'datadir'});
-}
-
-else {
-    print STDERR '$0 called with an invalid --action parameter';
-    exit(-1);
-}
-
-# {{{ sub insert_schema
-sub insert_schema {
-        my $base_path = (shift || $RT::EtcPath);
-    my (@schema);
-    print "Creating database schema.\n";
-
-    if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
-       no warnings 'unopened';
-
-        open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
-        open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
-
-        my $statement = "";
-        foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
-            $line =~ s/\#.*//g;
-            $line =~ s/--.*//g;
-            $statement .= $line;
-            if ( $line =~ /;(\s*)$/ ) {
-                $statement =~ s/;(\s*)$//g;
-                push @schema, $statement;
-                $statement = "";
-            }
-        }
-
-       local $SIG{__WARN__} = sub {};
-       my $is_local = 0; # local/etc/schema needs to be nonfatal. 
-        foreach my $statement (@schema) {
-           if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
-            print STDERR "SQL: $statement\n" if defined $args{'debug'};
-            my $sth = $dbh->prepare($statement) or die $dbh->errstr;
-            unless ( $sth->execute or $is_local ) {
-                die "Problem with statement:\n $statement\n" . $sth->errstr;
-            }
-        }
-
-    }
-    else {
-        die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
-    }
-    print "schema sucessfully inserted\n";
-
-}
-
-# }}}
-
-# {{{ sub drop_db
-sub drop_db {
-    return if ( $RT::DatabaseType eq 'SQLite' );
-    if ( $RT::DatabaseType eq 'Oracle' ) {
-        print <<END;
-
-To delete the tables and sequences of the RT Oracle database by running 
-    \@etc/drop.Oracle 
-through SQLPlus.
-
-END
-        return;
-    }  
-    unless ( $args{'force'} ) {
-        print <<END;
-
-About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
-WARNING: This will erase all data in $RT::DatabaseName.
-
-END
-        exit unless _yesno();
-
-    }
-
-    print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
-
-    $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
-}
-
-# }}}
-
-# {{{ sub create_db
-sub create_db {
-    print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
-    if ( $RT::DatabaseType eq 'SQLite' ) {
-        return;
-    }
-    elsif ( $RT::DatabaseType eq 'Pg' ) {
-        $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
-        if ($DBI::errstr) {
-            $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
-        }
-    }
-    elsif ($RT::DatabaseType eq 'Oracle') {
-        insert_acl();
-    }
-    elsif ( $RT::DatabaseType eq 'Informix' ) {
-       $ENV{DB_LOCALE} = 'en_us.utf8';
-        $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
-    }
-    else {
-        $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
-    }
-}
-
-# }}}
-
-sub get_dba_password {
-    print
-"In order to create a new database and grant RT access to that database,\n";
-    print "this script needs to connect to your "
-      . $RT::DatabaseType
-      . " instance on "
-      . $RT::DatabaseHost . " as "
-      . $args{'dba'} . ".\n";
-    print
-"Please specify that user's database password below. If the user has no database\n";
-    print "password, just press return.\n\n";
-    print "Password: ";
-    ReadMode('noecho');
-    my $password = ReadLine(0);
-    ReadMode('normal');
-    return ($password);
-}
-
-# {{{ sub _yesno
-sub _yesno {
-    print "Proceed [y/N]:";
-    my $x = scalar(<STDIN>);
-    $x =~ /^y/i;
-}
-
-# }}}
-
-# {{{ insert_acls
-sub insert_acl {
-
-        my $base_path = (shift || $RT::EtcPath);
-
-    if ( $RT::DatabaseType =~ /^oracle$/i ) {
-        do $base_path . "/acl.Oracle"
-          || die "Couldn't find ACLS for Oracle\n" . $@;
-    }
-    elsif ( $RT::DatabaseType =~ /^pg$/i ) {
-        do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
-    }
-    elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
-        do $base_path . "/acl.mysql"
-          || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
-    }
-    elsif ( $RT::DatabaseType =~ /^informix$/i ) {
-        do $base_path . "/acl.Informix"
-          || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@;
-    }
-    elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
-        return;
-    }
-    else {
-        die "Unknown RT database type";
-    }
-
-    my @acl = acl($dbh);
-    foreach my $statement (@acl) {
-        print STDERR $statement if $args{'debug'};
-        my $sth = $dbh->prepare($statement) or die $dbh->errstr;
-        unless ( $sth->execute ) {
-            die "Problem with statement:\n $statement\n" . $sth->errstr;
-        }
-    }
-}
-
-# }}}
-
-=head2 get_system_dsn
-
-Returns a dsn suitable for database creates and drops
-and user creates and drops
-
-=cut
-
-sub get_system_dsn {
-
-    my $dsn = $Handle->DSN;
-
-    #with mysql, you want to connect sans database to funge things
-    if ( $RT::DatabaseType eq 'mysql' ) {
-        $dsn =~ s/dbname=$RT::DatabaseName//;
-
-        # with postgres, you want to connect to database1
-    }
-    elsif ( $RT::DatabaseType eq 'Pg' ) {
-        $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
-    }
-    elsif ( $RT::DatabaseType eq 'Informix' ) {
-       # with Informix, you want to connect sans database:
-       $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
-    }
-    return $dsn;
-}
-
-sub insert_initial_data {
-
-    RT::InitLogging();
-
-    #connect to the db, for actual RT work
-    require RT::Handle;
-    $RT::Handle = RT::Handle->new();
-    $RT::Handle->Connect();
-
-    #Put together a current user object so we can create a User object
-    my $CurrentUser = new RT::CurrentUser();
-
-    print "Checking for existing system user ($CurrentUser)...";
-    my $test_user = RT::User->new($CurrentUser);
-    $test_user->Load('RT_System');
-    if ( $test_user->id ) {
-        print "found!\n\nYou appear to have a functional RT database.\n"
-          . "Exiting, so as not to clobber your existing data.\n";
-        exit(-1);
-
-    }
-    else {
-        print "not found.  This appears to be a new installation.\n";
-    }
-
-    print "Creating system user...";
-    my $RT_System = new RT::User($CurrentUser);
-
-    my ( $val, $msg ) = $RT_System->_BootstrapCreate(
-        Name     => 'RT_System',
-        RealName => 'The RT System itself',
-        Comments =>
-'Do not delete or modify this user. It is integral to RT\'s internal database structures',
-        Creator => '1' );
-
-    unless ($val) {
-        print "$msg\n";
-        exit(1);
-    }
-    print "done.\n";
-    $RT::Handle->Disconnect();
-
-}
-
-# load some sort of data into the database
-
-sub insert_data {
-    my $datafile = shift;
-
-    #Connect to the database and get RT::SystemUser and RT::Nobody loaded
-    RT::Init;
-
-    my $CurrentUser = RT::CurrentUser->new();
-    $CurrentUser->LoadByName('RT_System');
-
-    if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
-
-        print "Creating Superuser  ACL...";
-
-        my $superuser_ace = RT::ACE->new($CurrentUser);
-        $superuser_ace->_BootstrapCreate(
-                             PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
-                             PrincipalType => 'Group',
-                             RightName     => 'SuperUser',
-                             ObjectType    => 'RT::System',
-                             ObjectId      => '1' );
-
-    }
-
-    # Slurp in stuff to insert from the datafile. Possible things to go in here:-
-    # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
-
-    require $datafile
-      || die "Couldn't find initial data for import\n" . $@;
-
-    if (@Groups) {
-        print "Creating groups...";
-        foreach $item (@Groups) {
-            my $new_entry = RT::Group->new($CurrentUser);
-            my ( $return, $msg ) = $new_entry->_Create(%$item);
-            print "(Error: $msg)" unless ($return);
-            print $return. ".";
-        }
-        print "done.\n";
-    }
-    if (@Users) {
-        print "Creating users...";
-        foreach $item (@Users) {
-            my $new_entry = new RT::User($CurrentUser);
-            my ( $return, $msg ) = $new_entry->Create(%$item);
-            print "(Error: $msg)" unless ($return);
-            print $return. ".";
-        }
-        print "done.\n";
-    }
-    if (@Queues) {
-        print "Creating queues...";
-        for $item (@Queues) {
-            my $new_entry = new RT::Queue($CurrentUser);
-            my ( $return, $msg ) = $new_entry->Create(%$item);
-            print "(Error: $msg)" unless ($return);
-            print $return. ".";
-        }
-        print "done.\n";
-    }
-    if (@ACL) {
-        print "Creating ACL...";
-        for my $item (@ACL) {
-
-           my ($princ, $object);
-
-           # Global rights or Queue rights?
-           if ($item->{'Queue'}) {
-                $object = RT::Queue->new($CurrentUser);
-                $object->Load( $item->{'Queue'} );
-           } else {
-               $object = $RT::System;
-           }
-
-           # Group rights or user rights?
-           if ($item->{'GroupDomain'}) {
-                $princ = RT::Group->new($CurrentUser);
-               if ($item->{'GroupDomain'} eq 'UserDefined') {
-                  $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
-               } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
-                  $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
-               } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
-                        $item->{'Queue'}) {
-                  $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
-                                             Queue => $object->id);
-               } else {
-                  $princ->Load( $item->{'GroupId'} );
-               }
-           } else {
-               $princ = RT::User->new($CurrentUser);
-               $princ->Load( $item->{'UserId'} );
-           }
-
-           # Grant it
-           my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
-                                                     Right => $item->{'Right'},
-                                                     Object => $object );
-
-            if ($return) {
-                print $return. ".";
-            }
-            else {
-                print $msg . ".";
-
-            }
-
-        }
-        print "done.\n";
-    }
-    if (@CustomFields) {
-        print "Creating custom fields...";
-        for $item (@CustomFields) {
-            my $new_entry = new RT::CustomField($CurrentUser);
-            my $values    = $item->{'Values'};
-            delete $item->{'Values'};
-            my $q     = $item->{'Queue'};
-            my $q_obj = RT::Queue->new($CurrentUser);
-            $q_obj->Load($q);
-            if ( $q_obj->Id ) {
-                $item->{'Queue'} = $q_obj->Id;
-            }
-            elsif ( $q == 0 ) {
-                $item->{'Queue'} = 0;
-            }
-            else {
-                print "(Error: Could not find queue " . $q . ")\n"
-                  unless ( $q_obj->Id );
-                next;
-            }
-            my ( $return, $msg ) = $new_entry->Create(%$item);
-
-            foreach my $value ( @{$values} ) {
-                my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
-                print "(Error: $emsg)\n" unless ($eval);
-            }
-
-            print "(Error: $msg)\n" unless ($return);
-            print $return. ".";
-        }
-
-        print "done.\n";
-    }
-
-    if (@ScripActions) {
-        print "Creating ScripActions...";
-
-        for $item (@ScripActions) {
-            my $new_entry = RT::ScripAction->new($CurrentUser);
-            my $return    = $new_entry->Create(%$item);
-            print $return. ".";
-        }
-
-        print "done.\n";
-    }
-
-    if (@ScripConditions) {
-        print "Creating ScripConditions...";
-
-        for $item (@ScripConditions) {
-            my $new_entry = RT::ScripCondition->new($CurrentUser);
-            my $return    = $new_entry->Create(%$item);
-            print $return. ".";
-        }
-
-        print "done.\n";
-    }
-
-    if (@Templates) {
-        print "Creating templates...";
-
-        for $item (@Templates) {
-            my $new_entry = new RT::Template($CurrentUser);
-            my $return    = $new_entry->Create(%$item);
-            print $return. ".";
-        }
-        print "done.\n";
-    }
-    if (@Scrips) {
-        print "Creating scrips...";
-
-        for $item (@Scrips) {
-            my $new_entry = new RT::Scrip($CurrentUser);
-            my ( $return, $msg ) = $new_entry->Create(%$item);
-            if ($return) {
-                print $return. ".";
-            }
-            else {
-                print "(Error: $msg)\n";
-            }
-        }
-        print "done.\n";
-    }
-    $RT::Handle->Disconnect();
-
-}
-
-=head2 ACLEquivGroupId
-
-Given a userid, return that user's acl equivalence group
-
-=cut
-
-sub ACLEquivGroupId {
-    my $username = shift;
-    my $user     = RT::User->new($RT::SystemUser);
-    $user->Load($username);
-    my $equiv_group = RT::Group->new($RT::SystemUser);
-    $equiv_group->LoadACLEquivalenceGroup($user);
-    return ( $equiv_group->Id );
-}
-
-sub help {
-
-    print <<EOF;
-
-$0: Set up RT's database
-
---action        init    Initialize the database
-                drop    Drop the database. 
-                        This will ERASE ALL YOUR DATA
-                insert  Insert data into RT's database. 
-                        By default, will use RT's installation data.
-                        To use a local or supplementary datafile, specify it
-                        using the '--datafile' option below.
-                        
-                acl     Initialize only the database ACLs
-                        To use a local or supplementary datafile, specify it
-                        using the '--datadir' option below.
-                        
-                schema  Initialize only the database schema
-                        To use a local or supplementary datafile, specify it
-                        using the '--datadir' option below.
-
---datafile /path/to/datafile
---datadir /path/to/              Used to specify a path to find the local
-                                database schema and acls to be installed.
-
-
---dba                           dba's username
---dba-password                  dba's password
---prompt-for-dba-password       Ask for the database administrator's password interactively
-
-
-EOF
-
-}
-
-1;
index c8e63a9..98d965c 100644 (file)
@@ -160,6 +160,9 @@ elsif ( $args{'action'} eq 'drop' ) {
     }
     drop_db();
 }
+elsif ( $args{'action'} eq 'insert_initial' ) {
+    insert_initial_data();
+}
 elsif ( $args{'action'} eq 'insert' ) {
     insert_data( $args{'datafile'} || ($args{'datadir'}."/content") );
 }
@@ -583,7 +586,7 @@ sub insert_data {
             my ($return,$msg) = $new_entry->Create(%$item);
             unless ($return) {
                 print "(Error: $msg)\n";
-                next;
+                exit;
             }
             print $return. ".";
         }
@@ -599,7 +602,7 @@ sub insert_data {
             my ($return,$msg) = $new_entry->Create(%$item);
             unless ($return) {
                 print "(Error: $msg)\n";
-                next;
+                exit;
             }
             print $return. ".";
         }
@@ -677,7 +680,9 @@ $0: Set up RT's database
 --action        init    Initialize the database
                 drop    Drop the database.
                         This will ERASE ALL YOUR DATA
-                insert  Insert data into RT's database.
+                insert_initial 
+                        Insert RT's core system objects
+                insert  Insert data into RT's database. 
                         By default, will use RT's installation data.
                         To use a local or supplementary datafile, specify it
                         using the '--datafile' option below.
diff --git a/rt/sbin/rt-test-dependencies b/rt/sbin/rt-test-dependencies
deleted file mode 100644 (file)
index c1591b1..0000000
+++ /dev/null
@@ -1,278 +0,0 @@
-#!/usr/bin/perl
-# BEGIN LICENSE BLOCK
-# 
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
-# 
-# (Except where explictly superceded by other copyright notices)
-# 
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-# 
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-# 
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
-# 
-# 
-# END LICENSE BLOCK
-
-#
-# This is just a basic script that checks to make sure that all
-# the modules needed by RT before you can install it.
-#
-
-use strict;
-no warnings qw(numeric redefine);
-use Getopt::Long;
-use CPAN;
-my %args;
-my %deps;
-GetOptions(\%args,'install', 'with-MYSQL', 'with-POSTGRESQL|with-pg|with-pgsql', 'with-SQLITE', 'with-ORACLE', 'with-FASTCGI', 'with-SPEEDYCGI', 'with-MODPERL1', 'with-MODPERL2' ,'with-DEV');
-
-if (!keys %args) {
-    help();
-    exit(0);
-}
-if ($args{'with-MODPERL2'}) {
-    warn_modperl2();
-}
-
-$args{'with-MASON'} = 1;
-$args{'with-CORE'} = 1;
-$args{'with-DEV'} =1; 
-$args{'with-CLI'} =1; 
-$args{'with-MAILGATE'} =1; 
-if ($] < 5.007) {
-$args{'with-I18N-COMPAT'} = 1;
-}
-
-sub warn_modperl2 {
-    print <<'.';
-        NOTE: mod_perl 2.0 isn't quite ready for prime_time just yet;
-        Best Practical Solutions strongly recommends that sites use
-        Apache 1.3 or FastCGI. If you MUST use mod_perl 2.0 (or 1.99),
-        please read the mailing list archives before asking for help.
-.
-    sleep 5;
-}
-
-
-sub help {
-
-    print <<'.';
-
-By default, testdeps determine whether you have 
-installed all the perl modules RT needs to run.
-
-       --install               Install missing modules
-
-The following switches will tell the tool to check for specific dependencies
-
-       --with-mysql            Database interface for MySQL
-       --with-postgresql       Database interface for PostgreSQL 
-       --with-sqlite           Database interface and driver for SQLite (unsupported)
-       --with-oracle           Database interface for oracle (unsupported)
-
-       --with-fastcgi          Libraries needed to support the fastcgi handler
-       --with-speedycgi        Libraries needed to support the speedycgi handler
-       --with-modperl1         Libraries needed to support the modperl 1 handler
-       --with-modperl2         Libraries needed to support the modperl 2 handler
-
-       --with-dev              Tools needed for RT development
-.
-}
-
-
-sub _ {
-    map { /(\S+)\s*(\S*)/; $1 => ($2 ? $2 :'') } split ( /\n/, $_[0] );
-}
-
-$deps{'CORE'} = [ _( << '.') ];
-Digest::MD5 2.27
-DBI 1.37
-Test::Inline
-Class::ReturnValue 0.40
-DBIx::SearchBuilder 0.97
-Text::Template
-File::Spec 0.8
-HTML::Entities 
-Net::Domain
-Log::Dispatch 2.0
-Locale::Maketext 1.06
-Locale::Maketext::Lexicon 0.32
-Locale::Maketext::Fuzzy
-MIME::Entity 5.108
-Mail::Mailer 1.57
-Net::SMTP
-Text::Wrapper 
-Time::ParseDate
-File::Temp
-Term::ReadKey
-Text::Autoformat
-Text::Quoted 1.3
-Scalar::Util
-.
-
-$deps{'MASON'} = [ _( << '.') ];
-Params::Validate 0.02
-Cache::Cache
-Exception::Class
-HTML::Mason 1.16
-MLDBM
-Errno
-FreezeThaw
-Digest::MD5 2.27
-CGI::Cookie 1.20
-Storable 2.08
-Apache::Session 1.53
-.
-
-$deps{'MAILGATE'} = [ _( << '.') ];
-HTML::TreeBuilder
-HTML::FormatText
-Getopt::Long
-LWP::UserAgent
-.
-
-$deps{'CLI'} = [ _( << '.') ];
-Getopt::Long 2.24
-.
-
-$deps{'DEV'} = [ _( << '.') ];
-Regexp::Common
-Time::HiRes 
-Test::Inline 
-WWW::Mechanize
-.
-
-$deps{'FASTCGI'} = [ _( << '.') ];
-CGI 2.92
-FCGI
-CGI::Fast 
-.
-
-$deps{'SPEEDYCGI'} = [ _( << '.') ];
-CGI 2.92
-CGI::SpeedyCGI
-.
-
-
-$deps{'MODPERL1'} = [ _( << '.') ];
-CGI 2.92
-Apache::Request
-Apache::DBI 0.92
-.
-
-$deps{'MODPERL2'} = [ _( << '.') ];
-CGI 2.92
-Apache::DBI
-.
-
-$deps{'I18N-COMPAT'} = [ _( << '.') ];
-Text::Iconv
-Encode::compat 0.04
-.
-
-$deps{'MYSQL'} = [ _( << '.') ];
-DBD::mysql 2.1018
-.
-$deps{'ORACLE'} = [ _( << '.') ];
-DBD::Oracle
-.
-$deps{'POSTGRESQL'} = [ _( << '.') ];
-DBD::Pg
-.
-
-print "perl:\n";
-print "\t5.8.0";
-eval {require 5.008};
-if ($@) {
-print "...missing.\n";
-        eval {require 5.006001};
-        if ($@) {
-            print " RT is known to be non-functional on versions of perl older than 5.6.1. Please upgrade to 5.8.0 or newer";
-            die;
-        } else {
-            print " RT is not supported on perl 5.6.1\n";
-        }
-} else {
-        print "...found\n";
-
-}
-
-
-foreach my $type (keys %args)  {
-next unless ($type =~ /^with-(.*?)$/);
-my $type = $1;
-print "$type dependencies:\n";
-       my @deps = (@{$deps{$type}});
-       while (@deps) {
-               my $module = shift @deps;
-               my $version = shift @deps;
-my $ret;
-       $ret =test_dep($module, $version);      
-
-if ($args{'install'} && !$ret) {
-       resolve_dep($module);           
-}
-}
-}
-sub test_dep {
-       my $module = shift;
-       my $version = shift;
-
-       print "\t$module $version";
-       eval "use $module $version" ;
-       if ($@) {
-               my $error = $@;
-               $error =~ s/\n(.*)$//s;
-               print "...MISSING\n";
-                       print "\t\t$error\n" if $error =~ /this is only/;
-
-               return undef;
-       } else {
-               print "...found\n";
-return 1;
-       }
-}
-
-sub resolve_dep {
-       my $module = shift;
-       use CPAN;
-       CPAN::Shell->install($module);          
-       
-}
-
-
-sub print_help {
-    print << "EOF";
-
-$0 FLAG DBTYPE
-
-
-$0 is a tool for RT that will tell you if you've got all
-the modules RT depends on properly installed.
-
-Flags: (only one flag is valid for a given run)
-
--quiet will check to see if we've got everything we need
-       and will exit with a return code of (1) if we don't.
-
--warn will tell you what isn't properly installed
-
--fix will use CPANPLUS.pm or CPAN.pm to magically make everything better
-
-DBTYPE is one of:
-       oracle, pg, mysql
-
-EOF
-
-    exit(0);
-}
diff --git a/test/cgi-test b/test/cgi-test
new file mode 100755 (executable)
index 0000000..85074d2
--- /dev/null
@@ -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 (executable)
index 0000000..b073cee
--- /dev/null
@@ -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;
+
+}
+