Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/debian/changelog b/debian/changelog
index 83447b7..6b27e21 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,1376 +1,1377 @@
cdrtool (6.7.0) unstable; urgency=low
* IMPORTANT!
* You must update radius configuration with sql.conf or reload the
radius_accounting.proc in the radius database depending on your setup
* You must apply changes from setup/mysql/alter_tables.mysql
* CSV file import formats have been adjusted, to see the current format
export the rating files to CSV after apply the changes to mysql
* The increment and min_duration have been moved from billing_rates to
destinations table
[ CDR Storage ]
* Added FromHeader radius attribute
* Added UserAgent radius attribute
* Added SIP-Application-Type radius attribute
[ WEB Output]
* Added Genexis SIP UA image
* Added Arris_TM722b SIP UA image
* Updated Webstar SIP UA images
* Turn off magic quotes at runtime
* Decode Radius text encoding
* Improve parsing of user-agent and server headers in sip trace
* Print node names instead of IP address in SIP trace table header
* Show Destination names when group CDRs by dest id
[ Rating Engine ]
* Added increment, min_duration, max_duration, max_price for each destination
* Removed increment, min_duration from billing_customers
* Removed increment, min_duration from billing_rates
* Removed increment, min_duration from billing_rates_history
* Fixed billing tables indexes to contain reseller_id
+ * Added normalization for SIP MESSAGE method
* Added max_price and max_duration for each rate
* Improved rating engine logging
* Added note about setting impersonate field to 0.0
* Clean-up start procedure of rating engine
* Fixed rating file import checks
* Return 0 if mysql failure during query in prepaid table
instead of the error text
* Check sanity of imported columns from csv files
* Log when loading broken destinations from destination table
* Log and mail missing rates and destinations
* Added max_sessions for prepaid calls
[ NGNPro Client ]
* You must upgrade to Freeradius-XS 1.1.7-4
* Use admin credentials when update customers in administrator panels
* Deactivate PSTN rights if is the first payment
* Fixed init of quota for accounts in SIP Thor backend
* Display results of actions on SIP accounts
* Added max_sessions column to prepaid table
* Added change_privacy_access_number, check_privacy_access_number,
reject_anonymous_access_number
* Check if Reseller is allowed to perform Prepaid Changes
* Added Credit Card Processing
* Added translation files for SIP Settings page
* Add prepaid users to group prepaid
- -- Adrian Georgescu <ag@ag-projects.com> Tue, 15 Dec 2009 19:59:07 +0100
+ -- Adrian Georgescu <ag@ag-projects.com> Wed, 16 Dec 2009 11:52:51 +0100
cdrtool (6.9.9) unstable; urgency=low
* Added database keepalive features to workaround the 2006 mysql error
* Add description for how to customize cdrtool classes
* Added Fring UA images
* Added example in config about subclassing opensips class
* Replace geolocation lookup code with a function
* Hide Dialable URI if contains wildcards
* Hide barring tab and display more balance history entries
* Added comment about purpose of sip_ files
* Added sip enrollment
* Added sip settings pages using digest and x509 auth
* Improved look and feel of SIP settings page
* Fixed overwriting of DNS record type
* Fixed display of geolocation in sip devices list
* Print home node unknown if lookup fails
* Use only first name when composing email notifications
* Save owner with DNS fancy records
* Fixed pstn rule selection box
-- Adrian Georgescu <ag@ag-projects.com> Mon, 09 Nov 2009 13:29:09 +0100
cdrtool (6.9.8) unstable; urgency=low
* Use the debian provided geoip database and library. See docs/Install.txt
for notes related to obtaining city level information
* Fixed pstn gateway rule selection box
* Added instructions for how to subclass the generic CDRS_opensips class
in global.inc.complex.sample
* Fixed no-answer timeout
-- Adrian Georgescu <ag@ag-projects.com> Wed, 21 Oct 2009 14:24:01 +0200
cdrtool (6.9.7) unstable; urgency=low
* Added geoip.dat to debian package
-- Adrian Georgescu <ag@ag-projects.com> Wed, 14 Oct 2009 16:14:10 +0200
cdrtool (6.9.6) unstable; urgency=low
* Improved SIP settings page
* Renamed quota reset to quota deblock
* Show ENUM range selection in numbers
* Save email address used for SIP account registration
* Fix display of SIP prepaid balance
* Use fixed public contact from NGPro
* Added more functions to the sip API
* Remove adressbook export
* Move geoip.dat in main directory
* Added instructions for requesting client certificate
* Added sip enrollment and sip settings using X.509
* Rename variables and function names
* Added links to export of client certificate in info tab
* Moved Reject to Accept tab
* Added Diversion tab
* Removed Diversions from main settings page
* Added Enrollment configuration template
* Added more one shot functions for SIP account information
* Moved user interface to renderUI library
* Refactor sip_settings to handle X.509 client certificates
* Renamed some variables for clarity
-- Adrian Georgescu <ag@ag-projects.com> Tue, 13 Oct 2009 23:14:08 +0200
cdrtool (6.9.5) unstable; urgency=low
* Normalize.php script can chose a differet month
* Allow argument to normalize.php script to chose a different month
* Allow set of enum_generator for a reseller depending on ngnpro engine
* Fix typo in sip_settings.php page
-- Adrian Georgescu <ag@ag-projects.com> Wed, 16 Sep 2009 15:36:32 +0200
cdrtool (6.9.4) unstable; urgency=low
* Allow argument to normalize.php script to chose a differet month
-- Adrian Georgescu <ag@ag-projects.com> Thu, 10 Sep 2009 11:39:00 +0200
cdrtool (6.9.3) unstable; urgency=low
* Fixed insert into prepaid table
* Fixed url in PSTN gateways
* Fixed column name in sip_trace table when purging
* Fixed encoding of html links to sip and media trace
* Fixed building statistics with online devices in OpenSIPS datasource
-- Adrian Georgescu <ag@ag-projects.com> Sun, 06 Sep 2009 19:16:41 +0200
cdrtool (6.9.2) unstable; urgency=low
* Added labbels for all SIP Thor network entities
-- Adrian Georgescu <ag@ag-projects.com> Sat, 29 Aug 2009 10:07:18 +0200
cdrtool (6.9.1) unstable; urgency=low
* Added labbels for SIP Thor nodes
* Updated freeradius pacthes from Norman Brandinger
* Export X509 certificate associated with the SIP account
-- Adrian Georgescu <ag@ag-projects.com> Wed, 19 Aug 2009 16:13:21 +0200
cdrtool (6.9.0) unstable; urgency=low
* Display geographic location using geo ip
* Fixed variable names that did not match the config
* Fixed logging of imported rows from csv files
* Created two standard profiles (OpenSIPS and SipThor) for subscriber
login, see class DomainAuthLocal extends OpenSIPS_DomainAuth in
global.inc
* Fixed export of CSV with prepaid history
* Hide called address in missed session list
* Refactored PSTN provisioning, NGNPro >= 4.1.5 is required
* Show timezone in SIP accounts list
* Display prepaid balance in SIP accounts list
* Do not change quota when toggle the prepaid flag
* Specify a default timezone per engine
* Cache returns from lookup sip_proxy from Thor network
-- Adrian Georgescu <ag@ag-projects.com> Wed, 15 Jul 2009 23:45:32 +0200
cdrtool (6.8.1) unstable; urgency=low
* Do not send emails if no missed calls
* Fixed search by username
* Fixed import of billing_profiles
* Notify also diverted sessions
* Fix display of CDRs when logged in as SIP account, also show incoming
calls
-- Adrian Georgescu <ag@ag-projects.com> Thu, 18 Jun 2009 08:49:20 +0200
cdrtool (6.8.0) unstable; urgency=low
* Added reseller fields to all database tables to allow different login
accounts to access their own data based on the account impersonate field
* Allow import of CSV files from subdirectories (read updated 'Data
partitioning for multiple resellers' section from RATING.txt)
* Added prepaid_history table to rating table web management
* Added increment and min_duration for each rate entry
* Removed gateway, domain and subscriber columns from billing_profiles table
* Removed gateway, domain and subscriber columns from billing_rates table
* Removed gateway, domain and subscriber columns from billing_rates_history table
* Updated CSV format for all rating tables to support the above changes,
see setup/csv for updated examples of CSV files or export your live data
from CDRTool rating tables page to see the new fields
* Removed obsolete prepaid.call_lock and prepaid.call_in_progress columns
* Removed obsolete customers.country_code column
* Removed obsolete tables ratesNGN and profilesNGN
* Renamed colummn prepaid_history.number with prepaid_history.description
* Show SIP trace also in text only format
* Do not log zero priced calls in prepaid_history table
* Improved SIP prepaid provisioning screens
* Skip math calculation for some rating tables
* Added export balance history from SIP prepaid page
* Updated ngnpro client to work with server version 4.1.1
* Make stronger random passwords for SIP accounts
* Improved NGNPro LCR functionality
* Added scripts/OpenSIPS/notifyLastSessions.php to send email
notifications containing the last sessions recived in the last 24 hours.
You must set notifyLastSessions per data source to tru to enable this
feature and put the susbcriber into missed_calls group
* You must apply the changes from setup/mysql/alter_tables.mysql
* You must add an integer `reseller_id` column to opensips.trusted and
opensips.domain tables
* Changed column prepaid_cards.value to decimal
* Droped unused columns from prepaid table
* Added a script to test the speed of the rating engine
-- Adrian Georgescu <ag@ag-projects.com> Sun, 24 May 2009 17:31:15 +0200
cdrtool (6.7.8) unstable; urgency=low
* Updated NGNPro client to work with server version 4.1.0
* Added functions for managing gateway rules
* Fixed LCR provisioning bugs
* Fixed gateway links from Carriers page
* Make stronger random passwords for SIP accounts
-- Adrian Georgescu <ag@ag-projects.com> Mon, 20 Apr 2009 11:53:33 +0200
cdrtool (6.7.7) unstable; urgency=low
* Fixed missing ENUMtld and Gateway in ShowPrice function
-- Adrian Georgescu <ag@ag-projects.com> Wed, 08 Apr 2009 23:08:45 +0200
cdrtool (6.7.6) unstable; urgency=low
* Removed obsolete prepaid_lock feature
* Apply the changes from setup/mysql/alter_tables.mysql
* Floor the call duration to make the prepaid DebitBalance use the same
duration as ShowPrice used by normalization process
-- Adrian Georgescu <ag@ag-projects.com> Sat, 04 Apr 2009 16:31:21 +0200
cdrtool (6.7.5) unstable; urgency=low
* Fixed display of rating info
-- Adrian Georgescu <ag@ag-projects.com> Thu, 26 Mar 2009 14:31:06 +0100
cdrtool (6.7.4) unstable; urgency=low
* Fixed reload of rating engine (thanks to Dan Bogos)
-- Adrian Georgescu <ag@ag-projects.com> Mon, 16 Mar 2009 18:49:11 +0100
cdrtool (6.7.3) unstable; urgency=low
* Logging text improvements
* Fixed bug with accessing $perm object when is not defined
* Removed cached destinations on engine startup
* Update Radius patches from Norm Brandinger norm@goes.com
* Allow end-users to see more CDR details
* Move ip address of sip proxy a bit to the left
* Add sip_proxy to the statistics array even if no accounts are online
* Added php5-gd to dependencies
* Disable unnecessary mysql queries when quota is disabled
* Better html text in sip login page
* Added info about radius clients.conf (patch from aseques@gmail.com)
* Removed pacth already applied to freeradius main tree
* HTML changes in provisioning screens
* Added information about how to generate debian package
* Correct mysql upgrade queries
-- Adrian Georgescu <ag@ag-projects.com> Sat, 14 Mar 2009 23:50:44 +0100
cdrtool (6.7.2) unstable; urgency=low
* Fixed number of columns in cdr when rating is disabled
* Improve installation and rating documentation
* Print errors when configuration items are missing from global.inc
* Clean up call search page
* Removed MediaProxy 1.0 sessions
* Renamed in global.inc SourceIPRealmTranslation to domainTranslation_SourceIP
* Print stream duration instead of stop time in media trace
* Display a limited view of network topology for non-admin accounts
* Fix login name used for logging
[ NGNPro related changes ]
* Added screen for sip login reminder
* Added section about login account setup
* Improve download instructions
* Added falback billing profiles in sip settings page
* Added vcard function
* Do not use reseller in the sip settings page link if account belongs to
another reseller.
* Added https key mapping
* Display both sip_proxy and media_relay roles in SIP Thor circle
-- Adrian Georgescu <ag@ag-projects.com> Sun, 18 Jan 2009 13:11:58 +0100
cdrtool (6.7.1) unstable; urgency=low
* Simplified the sample configuration file global.inc.simple.sample
* Improved installation documentation
-- Adrian Georgescu <ag@ag-projects.com> Fri, 19 Dec 2008 09:51:13 +0100
cdrtool (6.7.0) unstable; urgency=low
* Migrated to OpenSIPS, this version is incompatible with OpenSER, do not
upgrade to this version if you still use OpenSER
* Added support for Call Control prepaid application from
http://download.ag-projects.com/CallControl
* Normalize the duration of prepaid CDRs to match the duration calculated
by Call Control, this avoids price missmatch between debit price and
the price calculated during normalization
* Updated OpenSIPS radius dictionary
* Updated Freeradius server setup procedure wiht OpenSIPS and MediaProry
dictionaries
* Added checks for values in rating tables changed from the web page
* Improved retrieval and display of SIP traces from OpenSIPS
* Improved retrieval and display of Media traces from MediaProxy
* Reset the imported records counter before importing each csv file
* Added new fields in billing_rates connectCostIn and durationRateIn to
store the purchased price, the rating info now contains two prices for
each call. The CSV format of rating table has been changed to support
provisioning of purchased prices. Updated sample csv file for
billing_rates.
* Improved rating documentationin general
* Moved E164 class to cdr_generic.php and allow specification of which
E164 class should be used for each datasource
* Updated sample configuration files in setup/global.inc.*.sample
* Added new columns to prepaid_history to store information about sessions
that lead to debit balance
* Log duration of prepaid sessions in prepaid_history table
* Removed unused trafficRate from the rating engine and mysql tables
* Added CallerId to SIP accounts in ENUM generator
* Added support for new LCR engine from NGNPro4
* Removed one empty column from ENUM records that had no mappings
* Added domain column to the prepaid table
* To upgrade you must apply the changes from doc/Upgrade.txt and
setup/mysql/alter_tables.mysql
-- Adrian Georgescu <ag@ag-projects.com> Wed, 17 Dec 2008 14:13:54 +0100
cdrtool (6.6.10) unstable; urgency=low
* Added Force=1 parameter to DebitBalance(), see PREPAID.txt for more info
* Return none when destination id cannot be found (unlimited max session time)
* More consistent return codes and logging messages for prepaid calls
* Improve prepaid documentation
-- Adrian Georgescu <ag@ag-projects.com> Sat, 04 Oct 2008 12:36:04 +0200
cdrtool (6.6.9) unstable; urgency=low
* Purge old entries from prepaid_history table
* Fixed wrong return to call control engine becuase sessionDoesNotExist
variable was not initialized properly at each request
* Print the call id in the syslog when calculating rate
-- Adrian Georgescu <ag@ag-projects.com> Wed, 01 Oct 2008 00:11:35 +0200
cdrtool (6.6.8) unstable; urgency=low
* Log debit balance to prepaid_history table
-- Adrian Georgescu <ag@ag-projects.com> Tue, 30 Sep 2008 05:19:58 +0200
cdrtool (6.6.7) unstable; urgency=low
* Do not debit balance for inexistent prepaid sessions
-- Adrian Georgescu <ag@ag-projects.com> Sun, 28 Sep 2008 00:20:09 +0200
cdrtool (6.6.5) unstable; urgency=low
* Fixed purging of expired prepaid sessions
-- Adrian Georgescu <ag@ag-projects.com> Fri, 26 Sep 2008 19:01:49 +0200
cdrtool (6.6.4) unstable; urgency=low
* Added links to Prepaid calls and quota in the main menu
* Fixed showFooter() when exporting csv files from rating page
* Fixed prepaid table mysql schema
-- Adrian Georgescu <ag@ag-projects.com> Sun, 21 Sep 2008 01:39:00 +0200
cdrtool (6.6.3) unstable; urgency=low
* Cache rate values from billing_rates table during MaxSessionTime to
avoid mysql queries for the same values
-- Adrian Georgescu <ag@ag-projects.com> Fri, 12 Sep 2008 18:27:15 +0200
cdrtool (6.6.2) unstable; urgency=low
* Fixed concurrent prepaid calls handling
-- Adrian Georgescu <ag@ag-projects.com> Tue, 09 Sep 2008 18:50:05 +0200
cdrtool (6.6.1) unstable; urgency=low
* Fixed update of existing prepaid session
* Return error text for maxsessiontime and debitbalance
* Callid in maxsessiontime and debitbalance is mandatory
* Preserve search criteria after delete sip account
* Better logs for prepaid calls
-- Adrian Georgescu <ag@ag-projects.com> Tue, 09 Sep 2008 11:20:24 +0200
cdrtool (6.6.0) unstable; urgency=low
* Allow simultaneous calls for prepaid accounts, see the Simultaneous
prepaid calls section in doc/PREPAID.txt
* Added pseudo code examples for call control engine
* Apply the changes from setup/mysql/alter_tables.mysql
* Added interface to kill ongoing sessions in Prepaid page
-- Adrian Georgescu <ag@ag-projects.com> Mon, 08 Sep 2008 13:37:20 +0200
cdrtool (6.5.8) unstable; urgency=low
* Fixed wsdl for deleteGatewayGroup function
-- Adrian Georgescu <ag@ag-projects.com> Sun, 31 Aug 2008 17:19:06 +0200
cdrtool (6.5.7) unstable; urgency=low
* Check custom properties for customer port before loading
* Added defaultDomain filter to sample ngnpro_engines.inc
* Better error logging when rating tables are incorrectly provisioned
* Show XCAP root in sip settings email notification
* Fixed duplicate ENUM number is sip settings page
-- Adrian Georgescu <ag@ag-projects.com> Thu, 28 Aug 2008 14:36:45 +0200
cdrtool (6.5.6) unstable; urgency=low
* Updated normalize logic for the case where the session received a broken
BYE that did not generate a STOP while MediaProxy generated an UPDATE
-- Adrian Georgescu <ag@ag-projects.com> Fri, 22 Aug 2008 17:07:51 +0200
cdrtool (6.5.5) unstable; urgency=low
* Fixed bug that allowed for some CDRs to remain in 'in progress' state.
by setting ConnectInfo_stop explicitly to '' empty string. You must
update radius configuration with sql.conf or reload the
radius_accounting.proc in the radius database depending on your setup
* Search by un-normalized calls with duration
* Do not normalized calls in the web if more than 500 un-normalized calls.
* Fixed sample config files
* Better error printing when connection to databases fail
* Print error when cannot connect to radius primary or secondary database
* Fixed log of database class used for normalization
-- Adrian Georgescu <ag@ag-projects.com> Thu, 21 Aug 2008 10:05:55 +0200
cdrtool (6.5.4) unstable; urgency=low
* Fixed save and retrieve of sip proxy server when add DNS records for sip
* Fixed wrong password reset because of web browsers that cache password fields
* Preserve sip account quota value when toggling access to pstn
* Added group by Canonical URI in CDR search screen
* Fixed name of group used for blocking accounts (blocked instead of block)
* Removed unused siplewire functionality
-- Adrian Georgescu <ag@ag-projects.com> Thu, 14 Aug 2008 12:06:06 +0200
cdrtool (6.5.3) unstable; urgency=low
* Log id of inserted record into rating tables
* Fixed broken links between group by SIP Proxy and search calls
* Show quota reset even if not blocked
* The soap server returns the object created for all add() functions
* Added DNS complex records for sip, msrp, xmpp, thor
* Added NGNPro fancy records management (email and url forwarding)
* Added currency setting per soap engine and reseller
* Many bug fixes in dns management port
* Detect reseller zero in sip settings page
* Show progress of loading quota
* Added management for extra groups in Sip settings
-- Adrian Georgescu <ag@ag-projects.com> Mon, 21 Jul 2008 15:34:44 +0200
cdrtool (6.5.2) unstable; urgency=low
* Fixed name of OpenSERQuota class in global.inc sample files
* Fixed MySQL 2006 error for ReloadQuota function
* Added NGNPro DNS port
* Fixed required phone_images.php in sip settings page
* Fixed REMOTE_ADDR variable
* Fixed path of logo images
* Show method used for blocking accounts by quota (db/soap)
-- Adrian Georgescu <ag@ag-projects.com> Sun, 06 Jul 2008 19:53:59 +0200
cdrtool (6.5.1) unstable; urgency=low
* Added support for MediaProxy 2.0 accounting
* Added prepaid card generator in Rating section
* Rewrite ratingEngine daemon to be non blocking
* Added IP access list to the ratingEngine $RatingEngine['allow']
* Added new setting in global.inc called 'mediaDispatcher' to point to the
media dispatcher of MediaProxy 2, used to display media sessions
* Added ShowClients command to rating engine
* Removed support for php4
* Fixed error related to MySQL gone away (2006)
* Support prepaid calls to work with increment and min_duration settings
* Added web links between sip trace and media trace pages
* Fixed ShowPrice rating engine command
* Added /etc/cdrtool config directory
Move CDRTool/local to /etc/cdrtool/local
* Moved all library *_lib.phtml files to library/*.php
* Changed csv import directory from /var/www/CDRTool/csv to /var/spool/cdrtool
* Document split_rating_table feature in Rating.txt
* Gateway attribute is now mandatory for MaxSessionTime and DebitBalance commands
* Fixed update of Enum number selections
* Moved setting $servers from status/config/media_servers.php to global.inc
in each data source as 'mediaServers'
* Default for split_rating_table is false (global.inc)
* Added change password for sip accounts selection
* Lowercase the email addresses and sip usernames
* Update cron job, moved env LANG=C into php script
* No mysql changes
* After upgrading the software to this version run these actions:
sudo mv /var/www/CDRTool/global.inc /etc/cdrtool/
sudo mv /var/www/CDRTool/provisioning/ngnpro_engines.inc /etc/cdrtool/
sudo rm -r /var/www/CDRTool/provisioning
sudo mv /var/www/CDRTool/local/images/* /var/www/CDRTool/images/
sudo mv /var/www/CDRTool/local /etc/cdrtool/
sudo rm -r /etc/cdrtool/local/images
Edit /etc/cdrtool/global.inc and replace ser with openser in the line:
$CDRToolModules=array("ser","asterisk","rating");
and remove rating from it, example:
$CDRToolModules=array("openser","asterisk");
Set "UserQuotaClass"=> "OpenSERQuota" in global.inc (it was SERQuota)
Copy the value of $servers from /var/www/CDRTool/status/config/media_servers.php
to /etc/cdrtool/global.inc $DATASOURCES['ser_radius']['mediaServers']
sudo rm -r /var/www/CDRTool/status/config
Add "db_class_siponline" => "DB_openser" to ser radius data source
Remove memcache from monit configuration
-- Adrian Georgescu <ag@ag-projects.com> Sun, 29 Jun 2008 15:16:11 +0200
cdrtool (6.5.0) unstable; urgency=low
* Completely removed memcache
* Fixed update of rating history records from cvs files
* Fixed pattern matching for cvs files containing rates_history
* Update filename convention for rating csv files
* Improve prepaid documentation
* Improved Web comments in sip settings page
* Anonymise prepaid balance update log
* Hide billing profiles for non-resellers
* Many fixes in the way various sip settings can be changed when logged in
as subscriber customer or reseller
* Added service sip to prepaid generator
* Fixed customer properties update
* Fixed update of sip properties
* Added anonymous access number
* Leave control to deblock user blocked by quota
* Reset quota if account becomes prepaid
* Added logic to change PSTN settings only if allowed by customer properties
* Display previously blocked users in quota notification
* Fixed sip trace hop to other node by passing totag
* Only destination table requires reload after changing data in rating tables
* Do not cache prepaid balance, use mysql instead
* Return error when maxsessiontime command of rating engine encounters one
* Do not require reload after update of prepaid table
* display to and from tags, hide enum tld if none
* Removed unused functions and show in trace the msg size
* Toggle prepaid without using groups
* Removed EnableNetworkRating setting from global.inc
* No mysql changes
-- Adrian Georgescu <ag@ag-projects.com> Fri, 23 May 2008 10:06:51 +0200
cdrtool (6.4.1) unstable; urgency=low
* Fixed trace ip:port when SDP has lines in reverse order
* Show sip trace in web window title
* Find if an IP is Thor node by querying the network
* Log quota value
* Added totag to getTrace
* Renamed soap function getTrace to getSipTrace
* Added toTag to getTrace from SIPThor
* Added readonly rights for login accounts to deny changes to rates or
re-normalization process
* Move in global.inc $CDRTool['rating']['reportMissingRates'] to
$RatingEngine['reportMissingRates']
* Removed unused library
* Show home node if not logged in as subscriber
* Removed Prio field not used by PDNS
* Show Owner field in search form for SIP and ENUM records
-- Adrian Georgescu <ag@ag-projects.com> Sun, 06 Apr 2008 12:04:30 +0200
cdrtool (6.4.0) unstable; urgency=high
* Refactor quota system to work with millions of sip accounts
* Removed memcache usage for quota
* Fixed voicemail divert setting
* Separate unblock quota from init quota operation
Added domain and notified columns to quota_usage
Show quota_usage table in rating section
* Fixed syncronization of quota and blocked by quota
* Use normalized lock during init of quota to avoid race conditions
* Added quota_usage table
Must apply mysql changes from setup/mysql/alter_tables.mysql
* Allow for empty prefixes in PSTN routes
* Fixes in update of impersonate field
-- Adrian Georgescu <ag@ag-projects.com> Mon, 24 Mar 2008 19:32:57 +0100
cdrtool (6.3.2) unstable; urgency=low
* Log for how many account the CDRs are loaded to rebuild the quota count
-- Adrian Georgescu <ag@ag-projects.com> Thu, 20 Mar 2008 11:30:06 +0100
cdrtool (6.3.1) unstable; urgency=low
* Filter CDRs for local domains when init quota counters
* A subaccount cannot change his own impersonate field
* Replaced _serialize function with previos pear soap version to fix empty
array reprezentation
-- Adrian Georgescu <ag@ag-projects.com> Wed, 19 Mar 2008 14:22:26 +0100
cdrtool (6.3.0) unstable; urgency=low
* Boost the speed of the rating engine from 100% to 1000% depending on
the complexity of the rating tables
* Fixed set of delimiter from global.inc setting when exporting csv files
* Added GetEntityProfiles command to rating engine
* Return json encoded for GetPrepaidHistory
* Added script to create partial billing_rates tables
* Moved $CDRTool['rating'] settings to $RatingEngine in global.inc
* Added $RatingEngine['split_rating_table'] option to query rating data in
multiple tables, each table name is autogenerated from the
billing_rates.name field
* Fixed set of timeout for each SOAP connection
* Adjust some column lengths
Must apply setup/mysql/alter_tables.mysql
-- Adrian Georgescu <ag@ag-projects.com> Sat, 08 Mar 2008 15:56:12 +0100
cdrtool (6.2.3) unstable; urgency=low
* Print information about the normalize lock in syslog and to stdout
* Log if rating engine takes more time to complete. Must set
$RatingEngine['log_delay'] to a value in micro seconds in global.inc and
restart ratingEngine
* Small html changes in sip settings page
* Renamed RatingEngine object to differ from the same setting name from global.inc
-- Adrian Georgescu <ag@ag-projects.com> Sat, 01 Mar 2008 13:09:09 +0100
cdrtool (6.2.2) unstable; urgency=low
* Added setting in global.inc $RatingEngine['prepaid_lock'] to enable
multiple parallel calls using the same prepaid account, this is done by
disabling the lock (setting it to 0 or false). Disable the lock only if
you accept that the balance might become negative
* Added email filter to Sip->getAccounts()
* Log the call start time in the head of rating info to eliminate
the confusion when rating calls that span multiple profiles
* Show total call duration in rate info head
* Use <voice-mailbox> instead of absolute URIs for call diversions
* Removed unnecessary code used in the past for replication of changes to remote soap engine
* Fixed replication of customer account to remote platform
* Add active_master to replicatedDatabases in global.inc
* Added web link between sip target and sip_accounts
* Rewrite of mysql monitoring functions
* Added DeleteBalance and DeleteBalance History commands to rating engine
* Use colors in mysql replication status
* Added posibility to replicate customer entries in Customer Port
* Added getPrepaidHistory function to the rating engine
* Replaced false with 0 and true with 1 for the return values from rating engine
* Show database ip address when replication monitor returns an error
* Fixed name of sip_ports settings
* Added setup/crontabs directory for non debian installations
-- Adrian Georgescu <ag@ag-projects.com> Wed, 27 Feb 2008 13:00:21 +0100
cdrtool (6.2.1) unstable; urgency=low
* Correct update of radius records when mediaproxy field is NULL or !timeout
You must reload the sql stored procedures from
setup/radius/OpenSER/radius_accounting.proc or
update Freeradius with setup/radius/OpenSER/sql.conf
when not using the stored procedures
* Log add balance to prepaid_history table
-- Adrian Georgescu <ag@ag-projects.com> Tue, 12 Feb 2008 19:32:04 +0100
cdrtool (6.2.0) unstable; urgency=high
* Fixed corrupted phone_images.php file
-- Adrian Georgescu <ag@ag-projects.com> Tue, 12 Feb 2008 14:08:18 +0100
cdrtool (6.1.9) unstable; urgency=low
* Fixed memory leak related to calling the same soap function several times
* Added separate access numbers for FUNV, FBUS, FNOL, FNOA
* Fixed load of customer properties in ngnpro client
* Support multiple clusters for mysql replication monitor
* Fixed vulnerability related to missing BYE and MediaProxy
radius Update action (reported by Inaki Baz Castil)
You must reload the sql stored procedures from
setup/radius/OpenSER/radius_accounting.proc or
update Freeradius with setup/radius/OpenSER/sql.conf
when not using the stored procedures
* Specify if output of soap functions is html formatted or not
-- Adrian Georgescu <ag@ag-projects.com> Tue, 12 Feb 2008 03:34:45 +0100
cdrtool (6.1.8) unstable; urgency=low
* Fixed mrtg buildStatistics call in cron.d (env LANG=C)
* Added web page to show replications status and dynamic
instructions about how to fix it
* Replace $CDRTool['mysql_clusters'] in global.inc with
$replicated_databases (see setup/global.inc.in for syntax)
* Delete DB1 and DB2 definitions from global.inc
-- Adrian Georgescu <ag@ag-projects.com> Wed, 06 Feb 2008 08:58:37 +0100
cdrtool (6.1.7) unstable; urgency=low
* Set mobile_number per SIP account
* Add sip_accounts_lite to soap engine settings
* Update voicemail full name when sip account firstname or lastname changes
* Create templates_c dir required by smarty
* Added templates directory for sending welcome note
-- Adrian Georgescu <ag@ag-projects.com> Mon, 04 Feb 2008 18:46:30 +0100
cdrtool (6.1.6) unstable; urgency=low
* Fixed load of customer profiles for prepaid calls when
no default customer defined
-- Adrian Georgescu <ag@ag-projects.com> Sun, 03 Feb 2008 00:28:15 +0100
cdrtool (6.1.5) unstable; urgency=low
* Added lock icon when SIP UA registered using TLS contact
* Added more information about renormalizing prepaid calls
* Show port number and transport protocol in sip trace
* Group trace message per IP instead of ip:port
* Added welcome message per soap engine
* Show error message when ngnpro client is not configured
* Fixed link from sip domains to sip aliases page
* Fixed ENUM soap port authentication in sip settings page
* Replaced DB_ser with DB_openser
Edit global.inc and replace DB_ser with DB_openser
* Increased curl timeout for bulk SOAP operations
* Return price in debit balance
-- Adrian Georgescu <ag@ag-projects.com> Thu, 31 Jan 2008 16:11:56 +0100
cdrtool (6.1.4) unstable; urgency=low
* Remove sample mrtg files
-- Adrian Georgescu <ag@ag-projects.com> Thu, 10 Jan 2008 10:13:00 +0100
cdrtool (6.1.3) unstable; urgency=high
* Fixed calculation of rating profiles when using having specific
gateways/domains/subscribers
* Fixed default port alocation for NGNPro version < 3
* Allow set of reseller 0.0 (admin) in login account
* Show SipThor node in Sip settings title bar
* Fixed management of presence rules
* Fixed link from Sip accounts to sip settings page
-- Adrian Georgescu <ag@ag-projects.com> Wed, 09 Jan 2008 22:17:11 +0100
cdrtool (6.1.2) unstable; urgency=high
* Fixed detection of CDR duration when minimumDuration was set
-- Adrian Georgescu <ag@ag-projects.com> Tue, 08 Jan 2008 11:18:35 +0100
cdrtool (6.1.1) unstable; urgency=low
* Corrected default login account sql insert statement
-- Adrian Georgescu <ag@ag-projects.com> Mon, 07 Jan 2008 21:44:39 +0100
cdrtool (6.1.0) unstable; urgency=low
* Fixed update of billing_rates entries from csv files
* Overwrite defaults from sip_settings class per NGNPro connection
* Added contrib directory
* Added freeradius patch for freeradius cvs version from Norm Brandinger
* Log progress of importing rating files
* Show rating engine daemon uptime in syslog
* Many fixes in provisioning client
* Lookup customers and rates in MySQL, there is no memory limit anymore
* Importing rating does not lock the rating engine anymore
* Fixed billing of calls over the minimum duration
-- Adrian Georgescu <ag@ag-projects.com> Sun, 06 Jan 2008 11:52:19 +0100
cdrtool (6.0) unstable; urgency=low
* Fixed links from Group-by output to discrete CDRs
* After renormalization, re-cache the monthly usage only for the accounts
that had CDRs changed during renormalization
* Fixed application type for rating engine (default application is audio)
* Added info to ENUM generator
* Fixed node IP parsing in sip trace
* Fixed default login account creation
* Don't log ENUM tld in rate info if is set to none
* Added RatingProfiles management in sip settings page
* Added prepaid_cards management in Rating tables page
* Fixed name of domain table when SIP Thor data-source is enabled
* Added link in the trace window to jump to a different SIP Proxy
* Import/export delimiter for rating tables can be adjusted, default is ,
* Must apply database structure changes from setup/mysql/alter_tables.mysql
* NGNPro client completed
* Save settings before existing provisioning pages
-- Adrian Georgescu <ag@ag-projects.com> Tue, 18 Dec 2007 10:42:06 +0100
cdrtool (5.3.3) unstable; urgency=low
* Fixed ENUM tld match and logging
* Show visual ENUM usage
* Provisioning engine update
-- Adrian Georgescu <ag@ag-projects.com> Tue, 23 Oct 2007 23:09:27 +0200
cdrtool (5.3.2) unstable; urgency=low
* Fixed memory leak
-- Adrian Georgescu <ag@ag-projects.com> Tue, 16 Oct 2007 20:12:55 +0200
cdrtool (5.3.1) unstable; urgency=low
* Removed reference to soapFilter which was unused
* Skip ENUM tld 'none' in discounts
* Update presence functions in provisioning client
* Fixed initalization of gatewaygroups
* Add function for showing customer text box
* Allow specification of custom port names and classes per reseller
* Fixed phplib typo that caused crash on php5
-- Adrian Georgescu <ag@ag-projects.com> Tue, 16 Oct 2007 19:13:26 +0200
cdrtool (5.3.0) unstable; urgency=low
* Added ENUM tld based discounts in the rating engine
* Added ENUM tld management in the Rating page
* Renamed field radius.Framed-Protocol to radius.ENUMtld to accomodate
discounts based on ENUM top level domain. This functionality requires
changes to the database structures and reconfigure several components:
See doc/Upgrade.txt and setup/mysql/alter_tables.mysql for more information
* Removed unused SOAP directory
* Added NGNPro client provisioning library
* Added sip settings page
* Added new rights for provisioning tasks
* Change main menu item names
-- Adrian Georgescu <ag@ag-projects.com> Thu, 11 Oct 2007 10:12:38 +0200
cdrtool (5.2.6) unstable; urgency=low
* Log in syslog the duration of reloading of the rating tables and
of the sip accounts into memcacche
-- Adrian Georgescu <ag@ag-projects.com> Tue, 04 Sep 2007 19:06:31 +0200
cdrtool (5.2.5) unstable; urgency=low
* Replaced multiple SOAP definitions from global.inc with a pointer
to an entry from the $soapEngines defined in soap_engines.inc
* Fixed read of HTTP vars in sip trace, public traces are now visible
when global vars are turned off
-- Adrian Georgescu <ag@ag-projects.com> Mon, 03 Sep 2007 12:13:57 +0200
cdrtool (5.2.4) unstable; urgency=low
* Skip billing_rates.trafficRate absent from csv import
* Update rating documentation
* Added link to FreeRADIUS-CDRTool by Dan-Cristian Bogos
* Fixed sip trace purging
* Fix for virtualhost allowing .htaccess customization for cdrtool
-- Adrian Georgescu <ag@ag-projects.com> Tue, 21 Aug 2007 10:02:49 +0200
cdrtool (5.2.3) unstable; urgency=low
* Allow usernames without domain part in quota check
* Added publish of SIP domain statistics using SIP SIMPLE
-- Adrian Georgescu <ag@ag-projects.com> Tue, 31 Jul 2007 13:14:43 +0200
cdrtool (5.2.2) unstable; urgency=low
* Added SIP trace for SIP Thor datasource
-- Adrian Georgescu <ag@ag-projects.com> Thu, 19 Jul 2007 11:53:24 +0200
cdrtool (5.2.1) unstable; urgency=low
* Synced UPDATE queries for current and older month (in SQL stored procedures)
(you must apply changes from setup/mysql/alter_tables.mysql)
-- Adrian Georgescu <ag@ag-projects.com> Tue, 03 Jul 2007 17:25:52 +0200
cdrtool (5.2.0) unstable; urgency=low
* Eliminate the race condition that appears when normalization process is
performed between the time radius database is updated when a BYE arrives
and the MediaProxy updates the same radius record.
-- Adrian Georgescu <ag@ag-projects.com> Tue, 03 Jul 2007 14:51:40 +0200
cdrtool (5.1.9) unstable; urgency=low
* Removed print of sql query
-- Adrian Georgescu <ag@ag-projects.com> Thu, 28 Jun 2007 18:26:27 +0200
cdrtool (5.1.8) unstable; urgency=low
* Fixed saving the agreement with license page
-- Adrian Georgescu <ag@ag-projects.com> Thu, 28 Jun 2007 10:21:39 +0200
cdrtool (5.1.7) unstable; urgency=low
* Added SOAP/XML management for LCR
* Save when the user agrees with the usage license to avoid display the
license agreement at each login
-- Adrian Georgescu <ag@ag-projects.com> Sun, 24 Jun 2007 13:16:32 +0200
cdrtool (5.1.6) unstable; urgency=low
* Fixed calculus of historical rates when rate is found in alternative
profile
-- Adrian Georgescu <ag@ag-projects.com> Tue, 19 Jun 2007 09:35:39 +0200
cdrtool (5.1.5) unstable; urgency=low
* Hide price when grouping results for login accounts without showPrice right
-- Adrian Georgescu <ag@ag-projects.com> Thu, 07 Jun 2007 10:01:32 +0200
cdrtool (5.1.4) unstable; urgency=low
* Fixed a typo in cdrlib.phtml
-- Adrian Georgescu <ag@ag-projects.com> Mon, 14 May 2007 08:47:24 +0200
cdrtool (5.1.3) unstable; urgency=low
* Fixed pagination of exported records
-- Adrian Georgescu <ag@ag-projects.com> Wed, 2 May 2007 12:30:27 +0200
cdrtool (5.1.2) unstable; urgency=low
* Show status of multiple mysql clusters
Must migrate global.inc setting $CDRTool['replicated_databases'] to $CDRTool['mysql_clusters']
See setup/global.inc.in for example
-- Adrian Georgescu <ag@ag-projects.com> Tue, 1 May 2007 10:16:37 +0200
cdrtool (5.1.1) unstable; urgency=low
* Updated sample data for rating tables
* Updated mysql schema
* Fixed bug in csv import script
-- Adrian Georgescu <ag@ag-projects.com> Sun, 29 Apr 2007 11:35:09 +0200
cdrtool (5.1.0) unstable; urgency=low
* Added historical rating engine, requires changes in database
structure (you must apply changes from setup/mysql/alter_tables.mysql)
* Must add in global.inc "AccountsDBClass" => "DB_ser" for
each SER data-source, there is no default anymore
* Added a sample provisioning interface (MSP only)
* Added function to obfuscate caller Ids, this changes the login rights
The login accounts rights must be updated in the Accounts page
* Added capability to do accounting for SIP URIs (e.g. paid helpdesk)
* Improved compatibility with Firefox in media sesessions screen
* Added rights for login accounts to show pricing and caller id
* Removed unused NGN rating (billing_customers table has been modified,
please update your CSV file import tools)
* Modify index of rates tables to accomodate multiple applications
(you must apply changes from setup/mysql/alter_tables.mysql)
* Default application set to 'audio', application field is now mandatory
when importing rates CSV files
* Added SIP Thor enable per data-source
* Use separate name spaces for memcache keys per data source
* Allow quota checks for multiple OpenSER data-sources
* Show memory usage in syslog
* Print sql errors in syslog
-- Adrian Georgescu <ag@ag-projects.com> Sun, 15 Apr 2007 10:19:07 +0200
cdrtool (5.0.10) unstable; urgency=low
* Fixed reload of prepaid accounts from rating page to reload only the
prepaid account that has changed (when PHP register_globals if off)
* Fixed logging in prepaid history table
-- Adrian Georgescu <ag@ag-projects.com> Wed, 31 Jan 2007 17:04:48 +0100
cdrtool (5.0.9) unstable; urgency=low
* Added /var/www/CDRTool/scripts/replicationStatus.php
script to easily check the database replication process
Add to global.inc $CDRTool['replicated_databases']=array('DB1','DB2');
and defined DB1 and DB2 connections to the mysql databases that
replicate to each other. The connections must use the same IP
addresses and usernames used during the setup of the replication process
-- Adrian Georgescu <ag@ag-projects.com> Sat, 20 Jan 2007 16:14:09 +0100
cdrtool (5.0.8) unstable; urgency=low
* Enable failover between CDR databases for normalization process
db_class can be an array with database connection classes
* Removed db_class_readonly from global.inc
-- Adrian Georgescu <ag@ag-projects.com> Sat, 20 Jan 2007 12:03:19 +0100
cdrtool (5.0.7) unstable; urgency=low
* Fixed version to be compliant with debian native version numbering
-- Adrian Georgescu <ag@ag-projects.com> Wed, 10 Jan 2007 13:54:33 +0100
cdrtool (5.0-6) unstable; urgency=low
* Avoid unnecessary sql OR clause in CDR search that caused slow queries
-- Adrian Georgescu <ag@ag-projects.com> Mon, 8 Jan 2007 08:37:02 +0100
cdrtool (5.0-5) unstable; urgency=low
* Fixed determination of previous year in update_raddact_record_mediaproxy
Consolidate SET statements to minimize binary log entries. Fixed bug in
update_raddact_record_mediaproxy, the record values were reset after
execution of update on the first table causing failure to update the
previous table. setup/radius/OpenSER/radius_accounting.proc must be
reloaded into the MySQL server:
mysql -u root -p -h sipdb radius < setup/radius/OpenSER/radius_accounting.proc
* Use same CDR structure for MaxSessionTime and DebitBalance
* Change logging for prepaid actions to fit one line
* Normalize tries previous month if no record has been updated
* Mention how to see prepaid account status
* Updated rating docs
* Moved E164 class to phplib/local.inc
* Show accounts with quota that exceeded a certain treshold
scripts/SER/quotaShowAccounts.php treshhold
* Added documentation for the Quota system (doc/QuotaSystem.txt)
* Use normalization lock per table using GET_LOCK() server function
http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
This provided faster web response when multiple clients access the
interface
* Fixed confirmation for delete operations in rating tables when global
vars are turned off in php.ini
-- Adrian Georgescu <ag@ag-projects.com> Thu, 4 Jan 2007 15:32:52 +0100
cdrtool (5.0-4) unstable; urgency=low
* Better logging of prepaid calls in progress
* Added reloadPrepaidAccounts function to avoid reload of all rating tables
when only one prepaid account has changed
* Updated rating documentation
* Corrected some lintian errors for debian packaging
-- Adrian Georgescu <ag@ag-projects.com> Wed, 27 Dec 2006 11:53:09 +0100
cdrtool (5.0-3) unstable; urgency=low
* Fixed re-normalization of calls in previous monthly tables
* Fixed cached storage after re-normalize calls for previous months
* Clean ups in import csv files functions
-- Adrian Georgescu <ag@ag-projects.com> Thu, 21 Dec 2006 12:45:13 +0100
cdrtool (5.0-2) unstable; urgency=low
* The Import script for the rating files automatically detects if
a file has been previously imported and skips it
* Hide non-existent radius tables from datasource selection
* Fixed indexes of rating tables (see alter_tables.mysql)
* Fixed errors present in CSV import functions
* Added Country_code per customer, can be used within normalization class
-- Adrian Georgescu <ag@ag-projects.com> Thu, 21 Dec 2006 12:37:31 +0100
cdrtool (5.0-1) unstable; urgency=low
* Added scripts to display quota usage and settings
* Avoid duplicate tables in data source selection
-- Adrian Georgescu <ag@ag-projects.com> Mon, 18 Dec 2006 16:51:08 +0100
cdrtool (5.0) unstable; urgency=low
* Added support for MediaProxy accounting using Radius. Upgrade to
mediaproxy version >=1.8.0 is required to use this feature
* Added MySQL stored procedure for auto-rotation of radacct table using
radacctYYYMM table name format. This avoids manual purging of accounting
tables and the need for optimization of indexes, which can lock database
tables for writing and cause downtime. Requires modification of sql.conf
based on setup/radius/OpenSER/radius_accounting.conf, recompilation of
Freeradius server with CLIENT_MULTI_RESULTS flag enabled for mysql
connections and mediaproxy >= 1.8.0. See the documentation from
setup/radius/OpenSER/radius_accounting.proc for more information
* Added new patch for freeradius setup/radius/freeradius/freeradius.patch
* Replaced the E164Format function with a customizable class from
global.inc: $CDRTool['normalize']['E164Class'] that default instantiates
E164_Europe. See E164_Europe or E164_US in cdrlib.phtml for examples of
how to customize the behaviour of the normalization function
* Remove unused settings from global.inc: CountryNumberLength,
EnableSIPOnline, MinPstnNumLen, RotateThisMonth, RotateTables,
normalize0SecCalls and memcache (only if the OS is Debian, which reads
it from /etc/default/cdrtool)
* Set many defaults in the code to avoid explicit definitions in global.inc
* Default pagination is now 15 records, which better fits one 1024/768 screen
* Allow OpenSER subscriber login in CDRTool based on ha1 encrypted passwords
* Corrected the disposition mysql field in the Asterisk cdr table
* Added invisible property for datasources in global.inc,
useful to hide it from web elements like sip_trace datasource that
cannot be directly accessed
* Fixed media sessions statistics, IP traffic must be multiplied by
two when both SIP UAs are outside the MediaProxy network
* Fixed rating bug when either increment and minduration = 1
* Fixed pid file name of ratingEngine in /etc/init.d/cdrtool
* Updated term and conditions page displayed after login
* Log when quota has been exceeded
* Fixed logging of normalized totals
* More clear syslog logging statements
* Update rotate table logic
-- Adrian Georgescu <ag@ag-projects.com> Thu, 14 Dec 2006 20:33:26 +0100
cdrtool (4.8) unstable; urgency=low
* Log updates of rating tables
* Better display of log entries
* Log access to Registrar, Media and Usage pages
* Show the name of data sources in Log page
* Fixed display of timezone in all main menu items
* CDRTool version number points to the changelog
* It is possible to mark individual sip traces as public
-- Adrian Georgescu <ag@ag-projects.com> Mon, 27 Nov 2006 16:23:59 +0100
cdrtool (4.7-8) unstable; urgency=low
* Show cached usage only if showUsageFromCache is set in global.inc
* Fixed relative link to style.css in login screen
* Show friendly names for SIP Proxy machines in sip trace (see setup/global.inc.in)
* Fixed filtering of media sessions for login accounts with domain filter
* Added arrows to indicate SIP trace packet direction
* Show small SIP UA images for incomming messages in SIP trace
* Adjust trace to src/dst siptrace module fixes in OpenSER >= 1.1
-- Adrian Georgescu <ag@ag-projects.com> Sat, 25 Nov 2006 12:44:55 +0100
cdrtool (4.7-7) unstable; urgency=low
* Write sql trace in daily tables (changed sql.conf in freeradius configuration)
* Display platform total usage in OpenSER datasource (from cache)
* Show transport protocol in SIP online
* Fixed show domain details in sip online
* Filter statistics depending on CDRTool login rights
* Add links to statistrics screen from main menu
* removed /var/www/CDRTool/scripts/generateHTMLUsageIndexFile.php
/var/www/CDRTool/status/usage/index.phtml is generated on the fly
* Fixed relative link to style.css
-- Adrian Georgescu <ag@ag-projects.com> Sun, 19 Nov 2006 13:41:15 +0100
cdrtool (4.7-6) unstable; urgency=low
* Log the real SIP-Proxy-IP in radacct in place of NAS-IP-Address. Requires
update of Freeradius server configuration (sql.conf and dictionary.ser)
and OpenSER configuration
* Added per customer settings to specify the minimum charged duration and
time increments. For example it is possible to charge a minimum of 30
seconds and round up the duration to every next 10 seconds. Requires
two new columns in billing_customers table (see alter_tables.mysql)
* Fixed bugs in quotaCheck process, added quotaCompare.php script to check
sync problems between the CDR usage and cached usage, removed cache for
inbound traffic usage
* Added new SIP UA pictures (Nokia, Scientific-Atlanta Webstar, Ekiga)
* Fixed errors in building statistics for online totals
* Authorized access to the Usage web page.
/var/www/CDRTool/status/usage/index.html must be deleted
/var/www/CDRTool/status/usage/index.phtml must be generated using
/var/www/CDRTool/scripts/generateHTMLUsageIndexFile.php script
* Show memcache capacity usage
-- Adrian Georgescu <ag@ag-projects.com> Thu, 16 Nov 2006 19:49:50 +0100
cdrtool (4.7-5) unstable; urgency=low
* Fixed Reload of rating tables
* Show SIP Proxy IP in CDR results screen
* Fixed quota reset
-- Adrian Georgescu <ag@ag-projects.com> Wed, 1 Nov 2006 14:27:28 +0100
cdrtool (4.7-4) unstable; urgency=low
* Fixed debian package deps to install by default php5-memcache
* Allow login passwords greater than 10 characters
* Minor HTML changes in OpenSER CDR display
* Comment out double definitions present in freeradius server
* Enabled SSL in example virtual host
* Adjust maximum size of network traffic generated by MediaProxy
* Consolidate media statistics per caller domain and add totals
* Added 2 new settings to global.inc to filter zones for statistics
-- Adrian Georgescu <ag@ag-projects.com> Tue, 31 Oct 2006 12:12:59 +0100
cdrtool (4.7-3) unstable; urgency=low
* Increased default memcache and php memory to 128MB
* Fixed statistics to work with domains that have no online users
* Corrected wrong example of memcache location in sample configuration file
* Added style.css file to debian package
-- Adrian Georgescu <ag@ag-projects.com> Thu, 26 Oct 2006 12:20:57 +0200
cdrtool (4.7-2) unstable; urgency=low
* Added monthly usage caches for SIP domains and platform total in and out
* Improved display of SIP traces
* Added new screenshots
* Removed _darcs directory from the debian package
* Use DB_ser database class if DB_online not defined
* Add checks to mysql database creation script
* Log balance updates from the web in prepaid_history table
-- Adrian Georgescu <ag@ag-projects.com> Sun, 22 Oct 2006 14:57:35 +0200
cdrtool (4.7-1) unstable; urgency=low
* Renamed database class used by quota check system
* Updated rating documentation
-- Adrian Georgescu <ag@ag-projects.com> Wed, 11 Oct 2006 09:27:28 +0200
cdrtool (4.7-0) unstable; urgency=low
* Improved the speed of normalization and quota processes with a factor > 10
This requires the installation of a memcache server and setup of
global.inc to point to the memcache server (default is 127.0.0.1:11212)
* Added memcache to cdrtool startup script and /etc/default/cdrtool
* A new database table cdrtool.memcache has been created to store data in mysql
when a memcache server is not available (see setup/mysql/alter_tables.mysql)
* Added graphical statistics for SIP online subscriber and MediaProxy usage
Added a new cron script scripts/buildStatistics.php
* Hide Sendmail button if CDR query has no description
* Quota checks is now much faster by reusing incremental learned data
* Cache SIP subscribers and domains into memcache
* Cache destinations into memcache
* Calls can be rated if the duration is greater than a minimum duration
-- Adrian Georgescu <ag@ag-projects.com> Fri, 29 Sep 2006 18:18:58 +0200
cdrtool (4.6-9) unstable; urgency=low
* Added note about the dependency on the PHP SOAP library in doc/INSTALL.txt
-- Adrian Georgescu <ag@ag-projects.com> Tue, 19 Sep 2006 22:42:21 +0200
cdrtool (4.6-8) unstable; urgency=low
* Fixed variable names in phplib to avoid the need for registering
global variables in php.ini
* Tested CDRTool successfully against apache2/php5
* Create CDRTool/local/images directory during installation
* Remove sample apache configuration, is available in apache2
* Updated apache 2 virtual host definition
-- Adrian Georgescu <ag@ag-projects.com> Sun, 3 Sep 2006 12:26:56 +0200
cdrtool (4.6-7) unstable; urgency=low
* Update sample configuration for new installations
* Update documentation of rating engine
* Perform speeds test for the postpaid and prepaid applications
(results are available in the rating engine documentation)
* Removed Event-Timestamp from dictionary.ser, it is present now
in freeradius standard distribution
* Simplify default configuration
* Remove unused declaration of table_missed
* Replaced $cdr with $this->cdrtool and removed it from global.inc
* Allocate more memory at start time from phplib
-- Adrian Georgescu <ag@ag-projects.com> Fri, 25 Aug 2006 14:01:21 +0200
cdrtool (4.6-6) unstable; urgency=low
* Migrate from CVS to darcs version control
* Set executable flag for php scripts after installation
* Hide _darcs directory in apache
* Log the loading of prepaid accounts during start-up
-- Adrian Georgescu <ag@ag-projects.com> Thu, 10 Aug 2006 15:57:20 +0200
cdrtool (4.6-5) unstable; urgency=low
* Fixed search in query logs by string
-- Adrian Georgescu <ag@ag-projects.com> Tue, 8 Aug 2006 12:14:17 +0200
cdrtool (4.6-4) unstable; urgency=low
* Added script to reload rating engine scripts/reloadRatingTables.php
* cdrtool init.d script can reload the rating engine
* Added sample configuration for monit in setup/monit/monitrc
* Update description of 488 code
-- Adrian Georgescu <ag@ag-projects.com> Mon, 7 Aug 2006 10:49:19 +0200
cdrtool (4.6-3) unstable; urgency=low
* Properly put ratingEngine in background releasing the terminal
-- Adrian Georgescu <ag@ag-projects.com> Sun, 06 Aug 2006 18:05:05 +0200
cdrtool (4.6-2) unstable; urgency=low
* debian packaging improvements
-- Adrian Georgescu <ag@ag-projects.com> Sun, 06 Aug 2006 17:12:05 +0200
cdrtool (4.6-1) unstable; urgency=low
* Created CDRTool debian package
* Added filter for listing the query log, users with admin right may
select and edit all queries
* Added a new index in the log table (see setup/mysql/alter_tables.mysql)
-- Adrian Georgescu <ag@ag-projects.com> Sat, 5 Aug 2006 11:17:56 +0200
diff --git a/library/cdr_generic.php b/library/cdr_generic.php
index 189bcb9..303b88a 100644
--- a/library/cdr_generic.php
+++ b/library/cdr_generic.php
@@ -1,2593 +1,2599 @@
<?
$tz=$CDRTool['provider']['timezone'];
putenv("TZ=$tz");
class CDRS {
var $CDR_class = 'CDR';
var $intAccessCode = '00';
var $natAccessCode = '0';
var $maxrowsperpage = 15;
var $status = array();
var $normalizedField = 'Normalized';
var $DestinationIdField = 'DestinationId';
var $BillingIdField = 'UserName';
var $defaults = array();
var $whereUnnormalized = '';
var $skipNormalize = false;
var $reNormalize = false;
var $usageKeysForDeletionFromCache = array();
var $localDomains = array();
var $trustedPeers = array();
var $maxCDRsNormalizeWeb = 500;
var $E164_class = 'E164_Europe';
var $quotaEnabled = false;
var $CDRNormalizationFields=array('id' => 'RadAcctId',
'callId' => 'AcctSessionId',
'username' => 'UserName',
'domain' => 'Realm',
'gateway' => 'NASIPAddress',
'duration' => 'AcctSessionTime',
'startTime' => 'AcctStartTime',
'stopTime' => 'AcctStopTime',
'inputTraffic' => 'AcctInputOctets',
'outputTraffic' => 'AcctOutputOctets',
'aNumber' => 'CallingStationId',
'cNumber' => 'CalledStationId',
'timestamp' => 'timestamp',
'BillingPartyId' => 'UserName',
'ResellerId' => 'BillingId',
'price' => 'Price',
'DestinationId' => 'DestinationId'
);
function _readCDRNormalizationFieldsFromDB() {
foreach (array_keys($this->CDRNormalizationFields) as $field) {
$mysqlField=$this->CDRNormalizationFields[$field];
$CDRStructure[$mysqlField] = $this->CDRdb->f($mysqlField);
}
return $CDRStructure;
}
function _readCDRFieldsFromDB() {
foreach (array_keys($this->CDRFields) as $field) {
$mysqlField=$this->CDRFields[$field];
$CDRStructure[$mysqlField] = $this->CDRdb->f($mysqlField);
}
return $CDRStructure;
}
function CDRS($cdr_source) {
global $CDRTool;
global $DATASOURCES;
global $RatingEngine;
if (!$cdr_source) {
$log="Error: cdr_source not defined\n";
syslog(LOG_NOTICE, $log);
return 0;
}
if (!$DATASOURCES[$cdr_source] || !$DATASOURCES[$cdr_source]['db_class']) {
$log="Error: no such datasource defined ($cdr_source) \n";
syslog(LOG_NOTICE, $log);
return 0;
}
$this->initDefaults();
$this->cdrtool = new DB_CDRTool();
$this->cdr_source = $cdr_source;
$this->CDRTool = $CDRTool;
$this->rating_settings = $RatingEngine;
$this->DATASOURCES = $DATASOURCES;
$this->cdrtool->Halt_On_Error="no";
$this->table = $this->DATASOURCES[$this->cdr_source]['table'];
// init names of CDR fields
foreach (array_keys($this->CDRFields) as $field) {
$mysqlField=$this->CDRFields[$field];
$_field=$field."Field";
$this->$_field=$mysqlField;
}
if ($this->DATASOURCES[$this->cdr_source]['rating']) {
$this->ratingEnabled = 1;
$this->rating = $this->DATASOURCES[$this->cdr_source]['rating'];
if ($this->DATASOURCES[$this->cdr_source]['showRate']) $this->showRate = $this->DATASOURCES[$this->cdr_source]['showRate'];
if ($this->DATASOURCES[$this->cdr_source]['rateField']) $this->rateField = $this->DATASOURCES[$this->cdr_source]['rateField'];
if ($this->DATASOURCES[$this->cdr_source]['priceField']) $this->priceField = $this->DATASOURCES[$this->cdr_source]['priceField'];
}
if ($this->DATASOURCES[$this->cdr_source]['UserQuotaClass']) {
$this->quotaEnabled = 1;
$this->quota_init_flag = $this->cdr_source.':quotaCheckInit';
$this->quota_reset_flag = $this->cdr_source.':reset_quota_for';
}
// connect to the CDR database(s)
if(!$this->DATASOURCES[$this->cdr_source]['db_class']) {
$log=sprintf("Error: \$DATASOURCES['%s']['db_class'] is not defined",$this->cdr_source);
syslog(LOG_NOTICE, $log);
return 0;
}
$_dbClass = $this->DATASOURCES[$this->cdr_source]['db_class'];
if (is_array($_dbClass)) {
if ($_dbClass[0]) $this->primary_database = $_dbClass[0];
if ($_dbClass[1]) $this->secondary_database = $_dbClass[1];
} else {
$this->primary_database = $_dbClass;
}
if(!class_exists($this->primary_database)) {
$log=sprintf("Error: database class '%s' is not defined",$this->primary_database);
syslog(LOG_NOTICE, $log);
return 0;
}
$this->CDRdb = new $this->primary_database;
// check db connectivity
if (!$this->CDRdb->query('SELECT 1')) {
$log=sprintf("Error: failed to connect to the primary CDR database %s\n",$this->primary_database);
syslog(LOG_NOTICE, $log);
if ($this->secondary_database) {
$this->CDRdb = new $this->secondary_database;
if (!$this->CDRdb->query('SELECT 1')) {
$log=sprintf("Error: failed to connect to the secondary CDR database %s\n",$this->secondary_database);
syslog(LOG_NOTICE, $log);
return 0;
} else {
$this->CDRdb1 = new $this->secondary_database;
$this->db_class = $this->secondary_database;
}
} else {
return 0;
}
} else {
$this->CDRdb1 = new $this->primary_database;
$this->db_class = $this->primary_database;
}
if ($this->DATASOURCES[$this->cdr_source]['DestinationIdField']) {
$this->DestinationIdField = $this->DATASOURCES[$this->cdr_source]['DestinationIdField'];
}
if ($this->DATASOURCES[$this->cdr_source]['normalizedField']) {
$this->normalizedField=$this->DATASOURCES[$this->cdr_source]['normalizedField'];
}
if (strlen($this->DATASOURCES[$this->cdr_source]['intAccessCode'])) {
$this->intAccessCode=$this->DATASOURCES[$this->cdr_source]['intAccessCode'];
}
if (strlen($this->DATASOURCES[$this->cdr_source]['natAccessCode'])) {
$this->natAccessCode = $this->DATASOURCES[$this->cdr_source]['natAccessCode'];
}
if ($this->DATASOURCES[$this->cdr_source]['db_subscribers']) {
if (class_exists($this->DATASOURCES[$this->cdr_source]['db_subscribers'])) {
$this->AccountsDB = new $this->DATASOURCES[$this->cdr_source]['db_subscribers'];
$this->db_subscribers = $this->DATASOURCES[$this->cdr_source]['db_subscribers'];
} else {
$log=sprintf("Error: subscribers database class %s is not defined",$this->DATASOURCES[$this->cdr_source]['db_subscribers']);
syslog(LOG_NOTICE, $log);
return 0;
}
} else if (class_exists('DB_opensips')) {
$this->AccountsDB = new DB_opensips();
$this->db_subscribers = 'DB_opensips';
} else {
$log=sprintf("Error: subscribers database is not defined, please define 'db_subscribers' in datasource '%s'",$this->cdr_source);
syslog(LOG_NOTICE, $log);
return 0;
}
if ($this->DATASOURCES[$this->cdr_source]['BillingIdField']) {
$this->BillingIdField = $this->DATASOURCES[$this->cdr_source]['BillingIdField'];
}
if ($this->DATASOURCES[$this->cdr_source]['E164_class']) {
if (class_exists($this->DATASOURCES[$this->cdr_source]['E164_class'])) {
$this->E164_class=$this->DATASOURCES[$this->cdr_source]['E164_class'];
} else {
printf ("Error: E164 class '%s' defined in datasource %s does not exist, using default '%s'",$this->DATASOURCES[$this->cdr_source]['E164_class'],$this->cdr_source,$this->E164_class);
}
}
if ($this->DATASOURCES[$this->cdr_source]['sipTrace']) {
$this->sipTrace=$this->DATASOURCES[$this->cdr_source]['sipTrace'];
}
if ($this->DATASOURCES[$this->cdr_source]['mediaTrace']) {
$this->mediaTrace=$this->DATASOURCES[$this->cdr_source]['mediaTrace'];
}
if ($this->DATASOURCES[$this->cdr_source]['domain_table']) {
$this->domain_table = $this->DATASOURCES[$this->cdr_source]['domain_table'];
}
if ($this->DATASOURCES[$this->cdr_source]['skipNormalize']) {
$this->skipNormalize = $this->DATASOURCES[$this->cdr_source]['skipNormalize'];
}
if ($this->DATASOURCES[$this->cdr_source]['enableThor']) {
$this->enableThor = $this->DATASOURCES[$this->cdr_source]['enableThor'];
}
if (is_array($this->CDRTool['normalize']['CS_CODES'])) $this->CS_CODES=array_keys($this->CDRTool['normalize']['CS_CODES']);
$this->missed_calls = $this->DATASOURCES[$this->cdr_source]['missed_calls'];
$this->traceInURL = $this->DATASOURCES[$this->cdr_source]['traceInURL'];
$this->traceOutURL = $this->DATASOURCES[$this->cdr_source]['traceOutURL'];
$this->protocolTraceURL = $this->DATASOURCES[$this->cdr_source]['protocolTraceURL'];
$spath = explode("/",$_SERVER["PHP_SELF"]);
$last = count($spath)-1;
$this->scriptFile=$spath[$last];
$this->next = $_REQUEST["next"];
$this->export = $_REQUEST["export"];
$this->trace = $_REQUEST["trace"];
if ($this->export) {
$this->maxrowsperpage=10000000;
} else {
if ($_REQUEST["maxrowsperpage"]) {
$this->maxrowsperpage = $_REQUEST["maxrowsperpage"];
} else {
$this->maxrowsperpage = "25";
}
}
$this->LoadDisconnectCodes();
$this->LoadDestinations();
$this->LoadENUMtlds();
$this->LoadDomains();
$this->LoadTrustedPeers();
$this->getCDRtables();
$this->initOK=1;
}
function initDefaults() {
if (is_readable('/etc/default/cdrtool')) {
$defaultContentLines=explode("\n",file_get_contents('/etc/default/cdrtool'));
foreach ($defaultContentLines as $_line) {
list($defaults_key, $defaults_value)=explode("=",$_line);
if (strlen($defaults_value)) $this->defaults[trim($defaults_key)]=trim($defaults_value);
}
}
}
function LoadDomains() {
}
function LoadTrustedPeers() {
}
function LoadAccounts() {
}
function LoadDestinations() {
$_destinations = array();
$_destinations_sip = array();
$this->destinations_count = 0;
$this->destinations_sip_count = 0;
$query=sprintf("select `value` from memcache where `key` = 'destinations'");
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->cdrtool->num_rows()) {
$this->cdrtool->next_record();
$mc_destinations=$this->cdrtool->f('value');
$_dests=explode("\n",$mc_destinations);
if (!strlen($mc_destinations)) {
$log=sprintf("Error: cached destinations key contains no data");
syslog(LOG_NOTICE,$log);
}
foreach ($_dests as $_dest) {
if (!strlen($_dest)) continue;
$this->destinations_count++;
$_els=explode("=",$_dest) ;
$_els_parts=explode(";",$_els[0]);
list($_reseller,$_entity)=explode("_",$_els_parts[0]);
$_destinations[$_reseller][$_entity][$_els_parts[1]]=$_els[1];
}
$query=sprintf("select `value` from memcache where `key` = 'destinations_sip'");
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->cdrtool->num_rows()) {
$this->cdrtool->next_record();
$mc_destinations_sip = $this->cdrtool->f('value');
$_SipDests=explode("\n",$mc_destinations_sip);
foreach ($_SipDests as $_dest) {
if (!strlen($_dest)) continue;
$this->destinations_sip_count++;
$_els=explode("=",$_dest) ;
$_els_parts=explode(";",$_els[0]);
list($_reseller,$_entity)=explode("_",$_els_parts[0]);
$_destinations_sip[$_reseller][$_entity][$_els_parts[1]]=$_els[1];
}
}
} else {
$query="select * from destinations";
if ($this->CDRTool['filter']['aNumber']) {
$faNumber=$this->CDRTool['filter']['aNumber'];
$query .= " where subscriber = '$faNumber' or
(subscriber = '' and domain = '' and gateway = '') ";
} else if ($this->CDRTool['filter']['domain']) {
$fdomain=$this->CDRTool['filter']['domain'];
$query .= " where domain = '$fdomain' or
(subscriber = '' and domain = '' and gateway = '') ";
} else if ($this->CDRTool['filter']['gateway']) {
$fgateway=$this->CDRTool['filter']['gateway'];
$query .= " where gateway = '$fgateway' or
(subscriber = '' and domain = '' and gateway = '') ";
}
$this->cdrtool->query($query);
if (!$this->cdrtool->num_rows()) {
$log=sprintf("Error: could not find any entries in the destinations table");
syslog(LOG_NOTICE,$log);
return 0;
}
$destinations_cache = "\n";
$destinations_sip_cache = "\n";
while($this->cdrtool->next_record()) {
$reseller_id = intval(trim($this->cdrtool->Record['reseller_id']));
$gateway = trim($this->cdrtool->Record['gateway']);
$domain = trim($this->cdrtool->Record['domain']);
$subscriber = trim($this->cdrtool->Record['subscriber']);
$dest_id = trim($this->cdrtool->Record['dest_id']);
$name = trim($this->cdrtool->Record['dest_name']);
$name_print = $this->cdrtool->Record['dest_name']." (".$dest_id.")";
if (strstr($dest_id,'@')) {
// SIP destination
if ($subscriber) {
$_destinations_sip[$reseller_id][$subscriber][$dest_id]=$name;
$destinations_sip_cache.=$reseller_id."_".$subscriber.";".$dest_id."=".$name."\n";
$this->destinations_sip_count++;
} else if ($domain) {
$_destinations_sip[$reseller_id][$domain][$dest_id]=$name;
$destinations_sip_cache.=$reseller_id."_".$domain.";".$dest_id."=".$name."\n";
$this->destinations_sip_count++;
} else if ($gateway) {
$_destinations_sip[$reseller_id][$gateway][$dest_id]=$name;
$destinations_sip_cache.=$reseller_id."_".$gateway.";".$dest_id."=".$name."\n";
$this->destinations_sip_count++;
} else if ($dest_id) {
$_destinations_sip[$reseller_id]["default"][$dest_id]=$name;
$destinations_sip_cache.=$reseller_id."_"."default;".$dest_id."=".$name."\n";
$this->destinations_sip_count++;
}
} else {
// PSTN destination
if (!is_numeric($dest_id)) {
$log=sprintf("Error: cannot load non-numeric destination '%s' from row id %d",$dest_id,$this->cdrtool->Record['id']);
syslog(LOG_NOTICE,$log);
continue;
}
if ($subscriber) {
$this->destinations_subscriber_count++;
$_destinations[$reseller_id][$subscriber][$dest_id]=$name;
$destinations_cache.=$reseller_id."_".$subscriber.";".$dest_id."=".$name."\n";
$this->destinations_count++;
} else if ($domain) {
$this->destinations_domain_count++;
$_destinations[$reseller_id][$domain][$dest_id]=$name;
$destinations_cache.=$reseller_id."_".$domain.";".$dest_id."=".$name."\n";
$this->destinations_count++;
} else if ($gateway) {
$this->destinations_gateway_count++;
$_destinations[$reseller_id][$gateway][$dest_id]=$name;
$destinations_cache.=$reseller_id."_".$gateway.";".$dest_id."=".$name."\n";
$this->destinations_count++;
} else if ($dest_id) {
$this->destinations_default_count++;
$_destinations[$reseller_id]["default"][$dest_id]=$name;
$destinations_cache.=$reseller_id."_"."default;".$dest_id."=".$name."\n";
$this->destinations_count++;
}
}
}
if (!strlen($destinations_cache)) {
$log=sprintf("Error: cached destinations key contains no data");
syslog(LOG_NOTICE,$log);
return 0;
}
$query=sprintf("select `value` from memcache where `key` = 'destinations'");
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->cdrtool->num_rows()) {
$query=sprintf("update memcache set value = '%s' where `key` = 'destinations'",addslashes($destinations_cache));
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$log=sprintf("Cached %d total, %d default, %d gateway, %d domain, %d subscriber destinations",
$this->destinations_count,
$this->destinations_default_count,
$this->destinations_gateway_count,
$this->destinations_domain_count,
$this->destinations_subscriber_count
);
syslog(LOG_NOTICE, $log);
} else {
$query=sprintf("insert into memcache (`key`,`value`) values ('destinations','%s')",addslashes($destinations_cache));
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$log=sprintf("Cached %d total, %d default, %d gateway, %d domain, %d subscriber destinations",
$this->destinations_count,
$this->destinations_default_count,
$this->destinations_gateway_count,
$this->destinations_domain_count,
$this->destinations_subscriber_count
);
syslog(LOG_NOTICE, $log);
}
$query=sprintf("select `value` from memcache where `key` = 'destinations_sip'");
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->cdrtool->num_rows()) {
$query=sprintf("update memcache set value = '%s' where `key` = 'destinations_sip'",addslashes($destinations_sip_cache));
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$log=sprintf("Updated %d SIP destinations in cache key destinations_sip",$this->destinations_sip_count);
syslog(LOG_NOTICE, $log);
} else {
$query=sprintf("insert into memcache (`key`,`value`) values ('destinations_sip','%s')",$destinations_sip_cache);
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$log=sprintf("Inserted %d SIP destinations in cache key destinations_sip",$this->destinations_sip_count);
syslog(LOG_NOTICE, $log);
}
}
$this->destinations = &$_destinations;
$this->destinations_sip = &$_destinations_sip;
if (is_array($this->destinations)) {
foreach (array_keys($this->destinations) as $_reseller) {
foreach ($this->destinations[$_reseller] as $key => $val) {
$this->destinations_length[$_reseller][$key] = max(array_map(strlen, array_keys($val)));
}
}
}
$c=$this->destinations_count + $this->destinations_sip_count;
//$log=sprintf("Loaded %d PSTN and %d SIP destinations into memory",$this->destinations_count,$this->destinations_sip_count);
//syslog(LOG_NOTICE, $log);
return $c;
}
function LoadENUMtlds() {
$_ENUMtlds =array();
$query="select * from billing_enum_tlds";
$this->cdrtool->query($query);
while($this->cdrtool->next_record()) {
$_ENUMtlds[trim($this->cdrtool->Record['enum_tld'])]=
array('discount' => trim($this->cdrtool->Record['discount']),
'e164_regexp' => trim($this->cdrtool->Record['e164_regexp'])
);
}
$this->ENUMtlds = &$_ENUMtlds;
$c=count($this->ENUMtlds);
return count($this->ENUMtlds);
}
function LoadDisconnectCodes() {
}
function initForm() {
}
function searchForm() {
}
function showTableHeader($begin_datetime,$end_datetime) {
}
function showTableHeaderStatistics($begin_datetime,$end_datetime) {
}
function showResultsMenu($hide_rows="") {
global $loginname;
if (!$this->export) {
print "
<form action=log.phtml method=post>
<table border=0 align=center>
<tr>
<td><a href=\"$this->url_edit\" >Refine search</a>
| <a href=\"$this->url_run\">Refresh</a>
</td>
";
$log_query=sprintf("insert into log
(date,login,ip,url,results,rerun,reedit,datasource,reseller_id)
values
(NOW(),'%s','%s','%s','%s','%s','%s','%s',%d)",
addslashes($loginname),
$_SERVER["REMOTE_ADDR"],
$this->url,
$this->rows,
$this->url_run,
$this->url_edit,
$this->cdr_source,
$this->CDRTool['filter']['reseller']
);
if ($this->cdrtool->query($log_query)) {
$this->cdrtool->query("select LAST_INSERT_ID() as lid");
$this->cdrtool->next_record();
$current_log=$this->cdrtool->f('lid');
}
if ($this->rows) {
print "<td> | <a href=\"$this->url_export\" target=_new>Export results to file</a></td>";
}
print "
<td valign=middle>
| <font color=blue>Want to share the results with others? </font>Give this query a name:
</td>
<td valign=middle>
<input type=text name=log_description value=\"$old_description\">
<input type=hidden name=current_log value=$current_log>
<input type=hidden name=task value=edit>
<input type=submit value=Save>
</td>
</form>
";
print "
</tr>
</table>
";
if (!$hide_rows) {
print "
<table width=100%>
<tr>
<td align=center>
";
if ($this->rows == 0) {
print "No records found.";
} else {
print "$this->rows records found. ";
}
print "</td>
</tr>
</table>
";
}
}
}
function showResultsMenuSubscriber($hide_rows="") {
global $loginname;
if (!$this->export) {
print "
<form action=log.phtml method=post>
<table border=0 align=center>
<tr>
<td>
<td>
<a href=\"$this->url_edit\">Refine search</a>
| <a href=\"$this->url_run\">Refresh</a>
</td>
";
$log_query=sprintf("insert into log
(date,login,ip,url,results,rerun,reedit,datasource,reseller_id)
values
(NOW(),'%s','%s','%s','%s','%s','%s','%s',%d)",
addslashes($loginname),
$_SERVER["REMOTE_ADDR"],
$this->url,
$this->rows,
$this->url_run,
$this->url_edit,
$this->cdr_source,
0
);
if ($this->cdrtool->query($log_query)) {
$this->cdrtool->query("select LAST_INSERT_ID() as lid");
$this->cdrtool->next_record();
$current_log=$this->cdrtool->f('lid');
}
if (!$this->CDRTool['filter']['aNumber']) {
if ($this->rows) {
print " | <a href=\"$this->url_export\" target=_new>Export results to file</a>";
}
print "
</td>
<td valign=middle>
| Save a description for this query:
</td>
<td valign=middle>
<input type=text name=log_description value=\"$old_description\">
<input type=hidden name=current_log value=$current_log>
<input type=hidden name=task value=edit>
<input type=submit value=Save>
</td>
</form>
";
}
print "
</tr>
</table>
";
if (!$hide_rows) {
print "
<table width=100%>
<tr>
<td align=center>
";
if ($this->rows == 0) {
print "No records found.";
} else {
print "$this->rows records found. ";
}
print "</td>
</tr>
</table>
";
}
}
}
function showDateTimeElements($f) {
print "
<tr>
<td valign=middle align=left>
<b>Start Time</b>
</td>
<td>
";
print "Date: ";
$f->show_element("begin_year","");
print "-";
$f->show_element("begin_month","");
print "-";
$f->show_element("begin_day","");
print " Time: ";
$f->show_element("begin_hour","");
print ":";
$f->show_element("begin_min","");
print "
</td>
</tr>
<tr>
</tr>
<tr>
<td valign=middle align=left>
<b>Stop Time</b>
</td>
<td>
";
print "Date: ";
$f->show_element("end_year","");
print "-";
$f->show_element("end_month","");
print "-";
$f->show_element("end_day","");
print " Time: ";
$f->show_element("end_hour","");
print ":";
$f->show_element("end_min","");
print "
</td>
</tr>
<tr>
</tr>
";
}
function showDataSources ($f) {
global $perm;
print "
<tr>
<td class=cdr valign=middle align=left>
<b>Data Source</b>
</td>
<td valign=middle>";
$f->show_element("cdr_source","");
if (count($this->tables) > 0) {
print " Table: ";
$this->f->show_element("cdr_table","");
}
print "
</td>
</tr>
<tr>
";
}
function showPagination($next,$maxrows) {
$PHP_SELF=$_SERVER["PHP_SELF"];
if (!$this->export) {
print "
<p>
<table border=0 align=center>
<tr>
<td>
";
if ($next!=0 ) {
$show_next=$this->maxrowsperpage-$next;
if ($show_next < 0) {
$mod_show_next = $show_next-2*$show_next;
}
$url_prev=$PHP_SELF.$this->url."&action=search&next=$mod_show_next";
print "<a href=\"$url_prev\">Previous</a> ";
}
print "
</td>
<td>
";
if ($this->rows>$this->maxrowsperpage && $this->rows!=$maxrows) {
$show_next = $this->maxrowsperpage + $this->next;
$url_next = $PHP_SELF.$this->url."&action=search&next=$show_next";
print "<a href=\"$url_next\">Next</a>";
}
print "
</td>
</tr>
</table>
";
}
}
function show() {
}
function dump() {
}
function unNormalize($where="",$table) {
// do not allow renormalization for readonly accounts
global $perm;
if (is_object($perm) && $perm->have_perm('readonly')) return false;
if (!$where) $where=" (1=1) ";
if (!$table) $table=$this->table;
if ($this->skipNormalize) {
return 0;
}
if (!$this->normalizedField) {
return 0;
}
$query=sprintf("update %s set %s = '0' where %s ",
$table,
$this->normalizedField,
$where
);
$c=0;
if ($this->CDRdb->query($query)) {
$c=$this->CDRdb->affected_rows();
$this->reNormalize=true;
}
return $c;
}
function buildWhereForUnnormalizedSessions () {
$this->whereUnnormalized = sprintf(" %s = '0'",$this->normalizedField);
if ($this->stopTimeField) $this->whereUnnormalized .= " and $this->stopTimeField != '0000-00-00 00:00:00' ";
if ($this->CDRFields['MediaTimeout']) {
/*
If we use MediaProxy information then eliminate all possible raise conditions
1. Session started and is in progress:
AcctStopTime = '0000-00-00 00:00:00'
AcctSessionTime = 0
MediaInfo is NULL
ConnectInfo_stop is NULL
2. Session closed with a negative response code ([4-6]XX):
AcctSessionTime = 0
AcctStopTime != '0000-00-00 00:00:00'
MediaInfo is NULL
ConnectInfo_stop is NULL
3. Session received a BYE:
ConnectInfo_stop is not NULL
AcctStopTime != '0000-00-00 00:00:00'
4. Media has timed-out:
MediaInfo = 'timeout'
ConnectInfo_stop is NULL
AcctStopTime != '0000-00-00 00:00:00'
5. MediaProxy update before BYE is received:
MediaInfo = ''
ConnectInfo_stop is NULL
AcctStopTime != '0000-00-00 00:00:00'
6. Mofified 5. for the case where the session received a broken BYE
that did not generate a STOP while MediaProxy generated an UPDATE
*/
$this->whereUnnormalized .= " and (ConnectInfo_stop is not NULL or MediaInfo is NULL or MediaInfo != '' or (UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(AcctStopTime) > 20)) ";
}
}
function getUnNormalized($where="",$table) {
if ($this->skipNormalize) {
return 1;
}
if (!$where) $where=" (1=1) ";
if (!$table) $table=$this->table;
$ReNormalize = $_REQUEST["ReNormalize"];
if ($ReNormalize) $this->unNormalize($where,$table);
if (!$this->normalizedField) {
return 1;
}
$this->buildWhereForUnnormalizedSessions();
$query=sprintf("select count(*) as c from %s where %s and %s",
$table,
$where,
$this->whereUnnormalized
);
if ($this->CDRdb->query($query)) {
$this->CDRdb->next_record();
$c=$this->CDRdb->f('c');
}
return $c;
}
function NormalizeCDRS($where="",$table="") {
$this->missing_destinations=array();
$b=time();
if (!$where) $where=" (1=1) ";
if (!$table) $table=$this->table;
if ($this->skipNormalize) {
return 1;
}
if (!$this->normalizedField) {
return 1;
}
$lockName=sprintf("%s:%s",$this->cdr_source,$table);
if (!$this->getNormalizeLock($lockName)) {
//printf("Cannot get obtain lock %s",$lockName);
return true;
}
$this->buildWhereForUnnormalizedSessions();
$query=sprintf("select *, UNIX_TIMESTAMP($this->startTimeField) as timestamp
from %s where %s and %s",
$table,
$where,
$this->whereUnnormalized
);
$this->status['cdr_to_normalize']=0;
$this->status['normalized']=0;
$this->status['normalize_failures']=0;
if (!$this->CDRdb->query($query)) {
$log=sprintf ("Database error: %s (%s)\n",$this->CDRdb->Error,$this->CDRdb->Errno);
syslog(LOG_NOTICE,$log);
print $log;
return false;
}
$this->status['cdr_to_normalize']=$this->CDRdb->num_rows();
//print "<p>$query";
if ($this->status['cdr_to_normalize'] > 0) {
if ($this->ratingEnabled) {
// Load rating tables
$this->RatingTables = new RatingTables();
$this->RatingTables->LoadRatingTables();
}
} else {
return 0;
}
$this->usageKeysForDeletionFromCache=array();
while ($this->CDRdb->next_record()) {
$Structure=$this->_readCDRNormalizationFieldsFromDB();
$found++;
$CDR = new $this->CDR_class(&$this, &$Structure);
if ($CDR->normalize("Save",$table)) {
$this->status['normalized']++;
} else {
$this->status['normalize_failures']++;
}
if ($this->reNormalize && !$this->usageKeysForDeletionFromCache[$CDR->BillingPartyId]) {
$this->usageKeysForDeletionFromCache[$CDR->BillingPartyId]++;
}
if ($this->status['cdr_to_normalize'] > 1000) {
if ($found > $progress*$this->status['cdr_to_normalize']/100) {
$progress++;
if ($progress%10==0) {
print "$progress% ";
flush();
}
}
}
}
if ($this->ratingEnabled && count($this->brokenRates) >0 ) {
if ($this->rating_settings['reportMissingRates']) {
if (count($this->brokenRates)) {
foreach (array_keys($this->brokenRates) as $dest) {
$missingRatesBodytext=$missingRatesBodytext."\nDestination id $dest (".$this->brokenRates[$dest]." calls)";
}
$to=$this->CDRTool['provider']['toEmail'];
$from=$this->CDRTool['provider']['fromEmail'];
mail($to, "Missing CDRTool rates",$missingRatesBodytext, "From: $from");
}
}
}
if (count($this->missing_destinations)) {
$to=$this->CDRTool['provider']['toEmail'];
$from=$this->CDRTool['provider']['fromEmail'];
$body='';
foreach($this->missing_destinations as $_dest) {
if (!$seen[$_dest]) {
$body.=sprintf("No destination for number %s\n",$_dest);
}
$seen[$_dest]++;
}
mail($to, "Missing CDRTool destinations",$body, "From: $from");
}
if ($this->status['cdr_to_normalize']>0) {
$d=time()-$b;
$log=sprintf("Normalization done in %d s, memory usage: %0.2f MB",$d,memory_get_usage()/1024/1024);
syslog(LOG_NOTICE,$log );
}
if (count($this->usageKeysForDeletionFromCache)) {
$this->resetQuota(array_keys($this->usageKeysForDeletionFromCache));
}
return 1;
}
function NormalizeNumber($Number,$type="destination",$subscriber="",$domain="",$gateway="",$CountryCode="",$ENUMtld="",$reseller_id=0) {
$this->CSCODE="";
$Number=strtolower(quoted_printable_decode($Number));
if ($pos = strpos($Number, "@")) {
// this is a SIP URI
$NumberStack['username'] = substr($Number,0,$pos);
if (strlen($NumberStack['username']) < 1) {
$NumberStack['username'] = "unknown";
}
$NumberStack['domain'] = substr($Number,$pos+1);
$NumberStack['delimiter'] = "@";
$pos = strpos($NumberStack['username'], ":");
if ($pos) {
$NumberStack['protocol'] = substr($NumberStack['username'],0,$pos+1);
$NumberStack['username'] = substr($NumberStack['username'],$pos+1);
}
if (preg_match("/^(.*)[=:;]/U",$NumberStack['domain'],$p)){
$NumberStack['domain'] = $p[1];
}
} else if (preg_match("/^([a-z0-9]+:)(.*)$/i",$Number,$m)) {
$oct=preg_split("/\./",$m[2]);
if(sizeof($oct)==4) {
// This is SIP address with no username
$NumberStack['username'] = "";
$NumberStack['domain'] = $m[2];
} else {
// This is SIP address with no domain
$NumberStack['username'] = $m[2];
$NumberStack['domain'] = "";
}
$NumberStack['protocol'] = $m[1];
$NumberStack['delimiter'] = "";
} else {
// This is a simple address like a phone number
$NumberStack['protocol'] = "";
$NumberStack['username'] = $Number;
$NumberStack['delimiter'] = "";
$NumberStack['domain'] = "";
}
if (preg_match("/^(.*)[=:;]/U",$NumberStack['domain'],$p)){
$NumberStack['domain'] = $p[1];
}
if ($type=="destination" && is_numeric($NumberStack['username'])) {
// strip custom prefix from destination
$usernameLength=strlen($NumberStack['username']);
if (is_array($this->CS_CODES)) {
foreach ($this->CS_CODES as $strip_prefix) {
$prefixLength = strlen($strip_prefix);
$restLength = $usernameLength-$prefixLength;
if ($restLength > 0 and preg_match("/^$strip_prefix(.*)$/",$NumberStack['username'],$m)) {
$NumberStack['username']=$m[1];
$this->CSCODE=$strip_prefix;
break;
}
}
}
if (!$CountryCode) $CountryCode=$this->CDRTool['normalize']['defaultCountryCode'];
$e164class=$this->E164_class;
$E164 = new $e164class($this->intAccessCode, $this->natAccessCode,$CountryCode,$this->ENUMtlds[$ENUMtld]['e164_regexp']);
$NumberStack['E164']=$E164->E164Format($NumberStack['username']);
}
if ($type=="destination" && $NumberStack['E164']) {
// lookup destination id for the E164 number
$dst_struct = $this->lookupDestination($NumberStack['E164'],$subscriber,$domain,$gateway,$reseller_id);
$NumberStack['DestinationId'] = $dst_struct[0];
$NumberStack['destinationName'] = $dst_struct[1];
$NumberStack['NumberPrint'] = "+".$NumberStack['E164'];
if (!$ENUMtld) {
$NumberStack['Normalized'] = $this->intAccessCode.
$NumberStack['E164'].
$NumberStack['delimiter'].
$NumberStack['domain'];
} else {
$NumberStack['Normalized'] =
$NumberStack['username'].
$NumberStack['delimiter'].
$NumberStack['domain'];
}
} else {
$dst_struct = $this->lookupDestination($Number,$subscriber,$domain,$gateway,$reseller_id);
$NumberStack['DestinationId'] = $dst_struct[0];
$NumberStack['destinationName'] = $dst_struct[1];
$NumberStack['NumberPrint'] = $NumberStack['username'].
$NumberStack['delimiter'].
$NumberStack['domain'];
$NumberStack['Normalized'] = $NumberStack['username'].
$NumberStack['delimiter'].
$NumberStack['domain'];
}
return $NumberStack;
}
function lookupDestination($destination,$subscriber="",$domain="",$gateway="",$reseller_id=0) {
if (!$destination) {
return;
}
if (is_numeric($destination)){
return $this->getPSTNDestinationId($destination,$subscriber,$domain,$gateway,$reseller_id);
} else {
return $this->getSIPDestinationId($destination,$subscriber,$domain,$gateway,$reseller_id);
}
}
function getSIPDestinationId($destination='',$subscriber='',$domain='',$gateway='',$reseller_id=0) {
if ($this->destinations_sip[$reseller_id][$subscriber]) {
$destinations_sip = $this->destinations_sip[$reseller_id][$subscriber];
$fCustomer="subscriber=$subscriber";
} else if ($this->destinations_sip[$reseller_id][$domain]) {
$destinations_sip = $this->destinations_sip[$reseller_id][$domain];
$fCustomer="domain=$domain";
} else if ($this->destinations_sip[$reseller_id][$gateway]) {
$destinations_sip = $this->destinations_sip[$reseller_id][$gateway];
$fCustomer="gateway=$gateway";
} else if ($this->destinations_sip[$reseller_id]['default']) {
$destinations_sip = $this->destinations_sip[$reseller_id]['default'];
$fCustomer="default";
} else if ($this->destinations_sip[0][$subscriber]) {
$destinations_sip = $this->destinations_sip[0][$subscriber];
$fCustomer="subscriber=$subscriber";
} else if ($this->destinations_sip[0][$domain]) {
$destinations_sip = $this->destinations_sip[0][$domain];
$fCustomer="domain=$domain";
} else if ($this->destinations_sip[0][$gateway]) {
$destinations_sip = $this->destinations_sip[0][$gateway];
$fCustomer="gateway=$gateway";
} else if ($this->destinations_sip[0]['default']) {
$destinations_sip = $this->destinations_sip[0]['default'];
$fCustomer="default";
}
if ($destinations_sip[$destination]) {
$ret=array($destination,$destinations_sip[$destination]);
return $ret;
} else {
return false;
}
}
function getPSTNDestinationId($destination='',$subscriber='',$domain='',$gateway='',$reseller_id=0) {
if ($this->destinations[$reseller_id][$subscriber]) {
$codes = $this->destinations[$reseller_id][$subscriber];
$maxLength = $this->destinations_length[$reseller_id][$subscriber];
$fCustomer="subscriber=$subscriber";
} else if ($this->destinations[$reseller_id][$domain]) {
$codes = $this->destinations[$reseller_id][$domain];
$maxLength = $this->destinations_length[$reseller_id][$domain];
$fCustomer="domain=$domain";
} else if ($this->destinations[$reseller_id][$gateway]) {
$codes = $this->destinations[$reseller_id][$gateway];
$maxLength = $this->destinations_length[$reseller_id][$gateway];
$fCustomer="gateway=$gateway";
} else if ($this->destinations[$reseller_id]['default']) {
$codes = $this->destinations[$reseller_id]['default'];
$maxLength = $this->destinations_length[$reseller_id]['default'];
$fCustomer="default";
}else if ($this->destinations[0][$subscriber]) {
$codes = $this->destinations[0][$subscriber];
$maxLength = $this->destinations_length[0][$subscriber];
$fCustomer="subscriber=$subscriber";
} else if ($this->destinations[0][$domain]) {
$codes = $this->destinations[0][$domain];
$maxLength = $this->destinations_length[0][$domain];
$fCustomer="domain=$domain";
} else if ($this->destinations[0][$gateway]) {
$codes = $this->destinations[0][$gateway];
$maxLength = $this->destinations_length[0][$gateway];
$fCustomer="gateway=$gateway";
} else if ($this->destinations_length[0]['default']){
$codes = $this->destinations[0]['default'];
$maxLength = $this->destinations_length[0]['default'];
$fCustomer="default";
}
if (!$destination)
return false;
if (count($codes)>0) {
$length = min(strlen($destination), $maxLength);
for ($i=$length; $i>0; $i--) {
$buf = substr($destination, 0, $i);
if ($codes[$buf]) {
$dest_name=$codes[$buf];
$ret=array($buf,$dest_name);
return $ret;
}
}
}
$log=sprintf("Error: cannot find destination id for %s, customer = %s, total destinations = %d\n",$destination,$fCustomer,count($codes));
syslog(LOG_NOTICE,$log);
$this->missing_destinations[]=$destination;
return false;
}
function import($file) {
}
function RadiusRecordRead($fp) {
$keepreading=1;
while ($keepreading) {
$contents = fgets($fp, 8192);
if (preg_match("/^$/",$contents)) {
$keepreading=0;
} else {
$record[]=$contents;
}
}
return $record;
}
function RadiusRecordParse($record) {
unset($radiusParsed);
if (!is_array($record)) {
return 0;
}
foreach ($record as $line) {
$line=trim($line);
foreach (array_keys($this->radiusAttributes) as $attribute) {
if (preg_match("/$attribute = (.*)$/",$line,$m)) {
$value=preg_replace("/\"/","",trim($m[1]));
$radiusParsed[$attribute]= $value;
}
}
}
return $radiusParsed;
}
function getCDRtables() {
if (!is_object($this->CDRdb)) return 0;
$_tables=$this->CDRdb->table_names();
$t=count($_tables);
if ($this->table) $this->tables[]=$this->table;
while ($t <> 0 ) {
$_table=$_tables[$t]["table_name"];
if ($_table=='radacct') $this->tables[]='radacct';
if (preg_match("/^(\w+)(\d{6})$/",$_table,$m)) {
if ($list_t >24) break;
$this->tables[]=$_table;
$list_t++;
}
$t--;
}
$this->tables=array_unique($this->tables);
}
function rotateTable($sourceTable,$month,$action) {
// create a new table tableYYYYMM and copy data from the main table into it
// if no month is supplied, the default is the previous month
if (!$month) $month=date('Ym', mktime(0, 0, 0, date("m")-1, "01", date("Y")));
if (!$sourceTable) $sourceTable=$this->table;
if (preg_match("/^(\w+)\d{6}$/",$sourceTable,$m)) {
$destinationTable=$m[1].$month;
} else {
$destinationTable=$sourceTable.$month;
}
print("rotateTable($sourceTable,$month,$destinationTable)\n");
if ($sourceTable == $destinationTable) {
$log=sprintf("Error: cannot copy records to the same table %s.\n",$destinationTable);
syslog(LOG_NOTICE,$log);
print $log;
return 0;;
}
$createTableFile=$this->CDRTool['Path'].$this->createTableFile;
if (!$this->createTableFile || !is_readable($createTableFile)) {
$log=sprintf("Error: cannot locate mysql creation file\n");
syslog(LOG_NOTICE,$log);
print $log;
return 0;;
}
$lockFile="/var/lock/CDRTool_".$this->cdr_source."_rotateTable.lock";
$f=fopen($lockFile,"w");
if (flock($f, LOCK_EX + LOCK_NB, $w)) {
if ($w) {
$log=sprintf("Another CDRTool rotate table is in progress. Aborting.\n");
syslog(LOG_NOTICE,$log);
print $log;
return 0;;
}
} else {
$log=sprintf("Another CDRTool rotate table is in progress. Aborting.\n");
syslog(LOG_NOTICE,$log);
print $log;
return 0;;
}
$b=time();
if (!preg_match("/^(\d{4})(\d{2})$/",$month,$m)) {
print "Error: Month $month must be in YYYYMM format\n";
return 0;
} else {
if ($m[2] > 12) {
print "Error: Month must be in YYYYMM format\n";
return 0;
}
$lastMonth=$month;
$startSQL=$m[1]."-".$m[2]."-01";
$stopSQL =date('Y-m-01', mktime(0, 0, 0, $m[2]+1, "01", $m[1]));
}
$query=sprintf("select count(*) as c from %s
where %s >='%s'
and %s < '%s'\n",
$sourceTable,
$this->CDRFields['startTime'],$startSQL,
$this->CDRFields['startTime'],$stopSQL);
if ($this->CDRdb->query($query)) {
$this->CDRdb->next_record();
$rowsSourceTable=$this->CDRdb->f('c');
$log=sprintf ("Source table %s has %d records in month %s\n",$sourceTable,$rowsSourceTable,$month);
syslog(LOG_NOTICE,$log);
print $log;
if (!$rowsSourceTable) return 1;
} else {
$log=sprintf ("Error: %s (%s)\n",$this->table,$this->CDRdb->Error);
syslog(LOG_NOTICE,$log);
print $log;
return 0;
}
$query=sprintf("select count(*) as c from %s\n", $destinationTable);
if ($this->CDRdb->query($query)) {
$this->CDRdb->next_record();
$rowsDestinationTable = $this->CDRdb->f('c');
$log=sprintf ("Destination table %s has %d records\n",$destinationTable,$rowsDestinationTable);
syslog(LOG_NOTICE,$log);
print $log;
if ($rowsDestinationTable != $rowsSourceTable) {
$log=sprintf ("Error: source table has %d records and destination table has %d records\n",$rowsSourceTable,$rowsDestinationTable);
syslog(LOG_NOTICE,$log);
print $log;
} else {
$log=sprintf ("Tables are in sync\n");
syslog(LOG_NOTICE,$log);
print $log;
}
} else {
$log=sprintf ("%s (%s)\n",$this->CDRdb->Error,$this->CDRdb->Errno);
syslog(LOG_NOTICE,$log);
print $log;
if ($this->CDRdb->Errno==1146) {
$destinationTableTmp=$destinationTable."_tmp";
$query=sprintf("drop table if exists %s",$destinationTableTmp);
print($query);
$this->CDRdb->query($query);
if ($query=file_get_contents($createTableFile)) {
$query=preg_replace("/CREATE TABLE.*/","CREATE TABLE $destinationTableTmp (",$query);
if (!$this->CDRdb->query($query)) {
$log=sprintf ("Error creating table %s: %s, %s\n",$destinationTableTmp,$this->CDRdb->Error,$query);
syslog(LOG_NOTICE,$log);
print $log;
return 0;
}
} else {
$log=sprintf ("Cannot read file %s\n",$createTableFile);
syslog(LOG_NOTICE,$log);
print $log;
return 0;
}
// if we reached this point we start to copy records
$query=sprintf("insert into %s
select * from %s
where %s >='%s'
and %s < '%s'",
$destinationTableTmp,
$sourceTable,
$this->CDRFields['startTime'],$startSQL,
$this->CDRFields['startTime'],$stopSQL);
print ($query);
return ;
if ($this->CDRdb->query($query)) {
$e=time();
$d=$e-$b;
$rps=0;
if ($this->CDRdb->affected_rows() && $d) $rps=$this->CDRdb->affected_rows()/$d;
$log=printf ("Copied %d CDRs into table %s in %d s @ %.0f rps\n",$this->CDRdb->affected_rows(),$destinationTableTmp,$d,$rps);
syslog(LOG_NOTICE,$log);
print $log;
$query="rename table $destinationTableTmp to $destinationTableTmp";
if (!$this->CDRdb->query($query)) {
printf ("Error renaming table %s to %s: %s\n",$destinationTableTmp,$destinationTable,$this->CDRdb->Error);
return 0;
}
} else {
printf ("Error copying records in table %s: %s\n",$destinationTable,$this->CDRdb->Error);
return 0;
}
}
}
}
function purgeTable($sourceTable,$month) {
// delete records for a given month with minimal locking of database
// this function is useful after archive of CDR data using rotate script
$begin=time();
if ($month) {
if (!preg_match("/^(\d{4})(\d{2})$/",$month,$m)) {
print "Error: Month must be in YYYYMM format\n";
return 0;
} else {
$beginDate=$m[1]."-".$m[2]."-01";
$endDate=date('Y-m-d', mktime(0, 0, 0, $m[2]+1, '01', $m[1]));
}
} else if (is_int($this->DATASOURCES[$this->cdr_source]['purgeCDRsAfter'])) {
$beginDate="1970-01-01";
$endDate=date('Y-m-d', mktime(0, 0, 0, Date('m'), Date('d')-$this->DATASOURCES[$this->cdr_source]['purgeCDRsAfter'], Date('Y')));
} else {
return 0;
}
if (!$sourceTable) $sourceTable=$this->table;
$query=sprintf("select min(%s) as min,max(%s) as max from %s where %s >= '%s' and %s < '%s' ",
$this->CDRFields['id'],$this->CDRFields['id'],$sourceTable,$this->CDRFields['startTime'],$beginDate,$this->CDRFields['startTime'],$endDate);
dprint($query);
if (!$this->CDRdb->query($query)) {
printf ("Error: %s",$this->CDRdb->Error);
return 0;
}
$this->CDRdb->next_record();
$min=$this->CDRdb->f('min');
$max=$this->CDRdb->f('max');
if (!$min || !$max) {
$log=sprintf("No CDRs found in %s between %s and %s\n",$sourceTable,$beginDate,$endDate);
print $log;
syslog(LOG_NOTICE,$log);
return 0;
}
$deleted=0;
$i=$min;
$interval=100;
$rows2delete=$max-$min;
$found = 0;
print "$rows2delete CDRs will be deleted between $min and $max, $interval at a time\n";
while ($i <= $max) {
$found=$found+$interval;
if ($i + $interval < $max) {
$top=$i;
} else {
$top=$max;
}
$query=sprintf("delete low_priority from %s
where %s <= '%d' and %s >= '%d'",
$sourceTable,
$this->CDRFields['id'],
$top,
$this->CDRFields['id'],
$min);
if ($this->CDRdb->query($query)) {
$deleted=$deleted+$this->CDRdb->affected_rows();
} else {
$log=sprintf("Error: %s (%s)",$this->CDRdb->Error,$this->CDRdb->Errno);
syslog(LOG_NOTICE,$log);
print $log;
return 0;
}
if ($found > $progress*$rows2delete/100) {
$progress++;
if ($progress%10==0) {
print "$progress% ";
flush();
}
}
print ".";
flush();
$i=$i+$interval;
}
print "\n";
$end = time();
$duration = $end-$begin;
$rps=0;
if ($deleted && $duration) $rps=$deleted/$duration;
$log=sprintf("%s CDRs of month %s deleted from %s in %d s @ %.0f rps\n",$deleted,$month,$sourceTable,$duration,$rps);
syslog(LOG_NOTICE,$log);
print $log;
return 1;
}
function cacheMonthlyUsage($accounts=array()) {
if (!$this->quotaEnabled) return true;
$saved_keys=0;
$failed_keys=0;
foreach (array_keys($accounts) as $_key) {
$query=sprintf("select id from quota_usage where datasource = '%s' and account = '%s'",$this->cdr_source,$_key);
if (!$this->cdrtool->query($query)){
$log=sprintf ("Database error: %s (%s)",$this->cdrtool->Error,$this->cdrtool->Errno);
syslog(LOG_NOTICE, $log);
print($log);
return false;
}
if ($this->cdrtool->num_rows()) {
// sync with quota_usage table
$query=sprintf("update quota_usage set
calls = calls + %d,
duration = duration + %d,
cost = cost + '%s',
traffic = traffic + '%s'
where account = '%s'
",
$accounts[$_key]['usage']['calls'],
$accounts[$_key]['usage']['duration'],
$accounts[$_key]['usage']['cost'],
$accounts[$_key]['usage']['traffic'],
$_key
);
if (!$this->cdrtool->query($query)){
$log=sprintf ("Database error: %s (%s)",$this->cdrtool->Error,$this->cdrtool->Errno);
syslog(LOG_NOTICE, $log);
$failed_keys++;
} else {
$saved_keys++;
}
} else {
$quota=$this->getQuota($_key);
$blocked=$this->getBlockedByQuotaStatus($_key);
list($_u,$_d)=explode("@",$_key);
$query=sprintf("insert into quota_usage
(datasource,account,domain,quota,calls,duration,cost,traffic,blocked,reseller_id)
values
('%s','%s','%s',%d,%d,'%s','%s','%s','%s',%d)
",
$this->cdr_source,
$_key,
$_d,
$quota,
$accounts[$_key]['usage']['calls'],
$accounts[$_key]['usage']['duration'],
$accounts[$_key]['usage']['cost'],
$accounts[$_key]['usage']['traffic'],
intval($blocked),
$this->localDomains[$_d]['reseller']
);
if (!$this->cdrtool->query($query)){
$log=sprintf ("Database error: %s (%s)",$this->cdrtool->Error,$this->cdrtool->Errno);
syslog(LOG_NOTICE, $log);
$failed_keys++;
} else {
$saved_keys++;
}
}
}
$this->status['cached_keys']['saved_keys'] = $this->status['cached_keys']['saved_keys'] + $saved_keys;
$this->status['cached_keys']['failed_keys'] = $this->status['cached_keys']['failed_keys'] + $failed_keys;
return 1;
}
function getNormalizeLock($lockname='') {
if (!$locker = new DB_Locker()) {
$log=sprintf("Error: cannot init locker database. ");
print $log;
syslog(LOG_NOTICE, $log);
return 0;
}
if (!$lockname) {
$log=sprintf("Error: no lockname provided. ");
print $log;
syslog(LOG_NOTICE, $log);
return 0;
}
unset($this->lock_connection_id);
register_shutdown_function("unLockNormalization",$locker,$lockname);
$query=sprintf("SELECT GET_LOCK('%s',0)",$lockname);
if ($locker->query($query)) {
$locker->next_record();
$return = $locker->Record[0];
$query=sprintf("SELECT IS_USED_LOCK('%s')",$lockname);
if ($locker->query($query)) {
$locker->next_record();
$this->lock_connection_id=$locker->Record[0];
}
if ($return == 0) {
$log=sprintf("Lock %s already aquired by another process with id %s ",$lockname,$this->lock_connection_id);
syslog(LOG_NOTICE, $log);
print "$log\n";
return 0;
} else {
$log=sprintf("Normalize lock id %s aquired for %s ",$this->lock_connection_id,$lockname);
syslog(LOG_NOTICE, $log);
//print "$log\n";
return 1;
}
} else {
$log=sprintf("Database error: failed to request mysql lock %s (%s)\n",$locker->Error,$locker->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return 0;
}
}
function getQuota($account) {
}
function getBlockedByQuotaStatus($account) {
}
function resetQuota($accounts=array()) {
if (!$this->quotaEnabled) return true;
$_reset_array=array_unique($accounts);
foreach ($_reset_array as $_el) {
if (strlen($_el)) $_accounts[]=$_el;
}
$_reset_array=$_accounts;
$log=sprintf("Next quota check will rebuild the counters for %s accounts",count($_reset_array));
syslog(LOG_NOTICE,$log );
$query=sprintf("delete from memcache where `key` in ('%s','%s')",$this->quota_init_flag,$this->quota_reset_flag);
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$query=sprintf("insert into memcache (`key`,`value`) values ('%s','%s')",$this->quota_reset_flag,json_encode($_reset_array));
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$query="delete from quota_usage where account in (";
$t=0;
foreach ($_reset_array as $_el) {
if ($t) $query.=",";
$query.= sprintf("'%s'",$_el);
$t++;
}
$query.=")";
if (!$this->cdrtool->query($query)) {
$log=sprintf ("Database error: %s (%s)",$this->cdrtool->Error,$this->cdrtool->Errno);
syslog(LOG_NOTICE,$log);
print $log;
return 0;
} else {
return 1;
}
}
}
class CDRS_unknown extends CDRS {
function searchForm() {
return;
}
}
class E164 {
// Class that helps normalization of a telephone number in E164 format
// Based on this normalization, CDRTool rating engine decides whether
// to consider the session a PSTN destination and rate it according
// to the PSTN rating plan
function E164($intAccessCode='00', $natAccessCode='0',$CountryCode='',$ENUMtldRegexp="") {
$this->regexp_international = "/^".$intAccessCode."([0-9]{5,})\$/";
$this->regexp_national = "/^".$natAccessCode."([0-9]{3,})\$/";
$this->CountryCode = trim($CountryCode);
$this->ENUMtldRegexp = trim($ENUMtldRegexp);
}
function E164Format($Number) {
//dprint "E164Format($Number,ENUMtldRegexp=$this->ENUMtldRegexp)";
// This function returns the full E164 format for a PSTN number without leading zero or +
// E164 = Country Code + Network Code + Subscriber Number
// Example: 31208015100 is an E164 number from Holland (country code 31)
// If nothing is returned by this function the session is considered an Internet destination
if (preg_match($this->regexp_international,$Number,$m)) {
return $m[1];
} else if (preg_match($this->regexp_national,$Number,$m)) {
// Add default country code
return $this->CountryCode.$m[1];
} else if (strlen($this->ENUMtldRegexp)) {
$_regexp="/^".$this->ENUMtldRegexp."\$/";
if (preg_match($_regexp,$Number,$m)) {
return $m[1];
}
}
return false;
}
}
class E164_Europe extends E164 {
function E164_Europe ($intAccessCode='00', $natAccessCode='0',$CountryCode='',$ENUMtldRegexp="([1-9][0-9]{7,})") {
$this->regexp_international = "/^".$intAccessCode."([1-9][0-9]{5,})\$/";
$this->regexp_national = "/^".$natAccessCode."([1-9][0-9]{3,})\$/";
$this->CountryCode = trim($CountryCode);
$this->ENUMtldRegexp = trim($ENUMtldRegexp);
}
}
class E164_US extends E164 {
function E164_US($intAccessCode='011', $natAccessCode='[1-9][0-9]{2}',$CountryCode='',$ENUMtldRegexp="([1-9][0-9]{7,})") {
$this->regexp_international = "/^".$intAccessCode."([1-9][0-9]{5,})\$/";
$this->regexp_national = "/^".$natAccessCode."([0-9]{3,})\$/";
$this->CountryCode = trim($CountryCode);
$this->ENUMtldRegexp = trim($ENUMtldRegexp);
}
}
class CDR {
// we need two db descriptors to update a CDR
// within same result set
var $idField = "RadAcctId";
var $callIdField = "AcctSessionId";
var $usernameField = "UserName";
var $domainField = "Realm";
var $gatewayField = "NASIPAddress";
var $gatewayPortField = "CiscoNASPort";
var $timestampField = "timestamp";
var $portIdField = "NASPortId";
var $portTypeField = "NASPortType";
var $startTimeField = "AcctStartTime";
var $stopTimeField = "AcctStopTime";
var $durationField = "AcctSessionTime";
var $inputTrafficField = "AcctInputOctets";
var $outputTrafficField = "AcctOutputOctets";
var $serviceTypeField = "ServiceType";
var $cNumberField = "CalledStationId";
var $aNumberField = "CallingStationId";
var $disconnectField = "H323DisconnectCause";
var $traceIn = "";
var $traceOut = "";
var $defaultApplicationType = "audio";
var $supportedApplicationTypes = array('audio',
'message',
'video',
'chat',
'file-transfer'
);
function CDR() {
}
function NormalizeDisconnect() {
$causePrint=$this->CDRS->disconnectCodesDescription[$this->disconnect]." (".$this->disconnect.")";
return $causePrint;
}
function traceOut () {
}
function traceIn () {
}
function show() {
}
function normalize($save="",$table="") {
if (!$table) $table = $this->CDRS->table;
if ($this->CDRS->CSCODE && $CarrierInfo = $this->CDRS->CDRTool['normalize']['CS_CODES'][$this->CDRS->CSCODE]) {
// We found a carrier so we set the BillingId
$this->BillingId = $CarrierInfo[BillingPartyId];
}
if ($save) {
if (!$this->id) {
return 0;
}
$query ="";
$query1 ="";
$query2 ="";
if ($this->CDRS->normalizedField) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s='1' ",$this->CDRS->normalizedField);
$updatedFields++;
}
if (strlen($this->durationNormalized) && $this->durationNormalized != $this->duration) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s ='%s' ",$this->CDRS->durationField,$this->durationNormalized);
$this->duration=$this->durationNormalized;
}
if ($this->CDRS->DestinationIdField) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->DestinationIdField,$this->DestinationId);
}
if ($this->CDRS->ResellerIdField) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->ResellerIdField,$this->ResellerId);
}
if ($this->usernameNormalized && $this->usernameNormalized!=$this->username) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->usernameField,addslashes($this->usernameNormalized));
}
if ($this->aNumberNormalized && $this->aNumberNormalized!=$this->aNumber) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->aNumberField,addslashes($this->aNumberNormalized));
$this->aNumber=$this->aNumberNormalized;
}
if ($this->CDRS->applicationTypeField && $this->applicationTypeNormalized) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->applicationTypeField,addslashes($this->applicationTypeNormalized));
$this->applicationType=$this->applicationTypeNormalized;
}
if ($this->domainNormalized && $this->domainNormalized != $this->domain) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->domainField,addslashes($this->domainNormalized));
$this->domainNumber=$this->domainNormalized;
$this->domain=$this->domainNormalized;
}
if ($this->cNumberNormalized && $this->cNumberNormalized!=$this->cNumber) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->cNumberField,addslashes($this->cNumberNormalized));
$this->cNumber=$this->cNumberNormalized;
}
if ($this->CDRS->BillingIdField && $this->BillingId) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->BillingIdField,addslashes($this->BillingId));
}
if ($this->CDRS->RemoteAddressField && $this->RemoteAddressNormalized && $this->RemoteAddressNormalized!= $this->RemoteAddress) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->RemoteAddressField,addslashes($this->RemoteAddressNormalized));
}
if ($this->CDRS->CanonicalURIField && $this->CanonicalURINormalized && $this->CanonicalURINormalized!= $this->CanonicalURI) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->CanonicalURIField,addslashes($this->CanonicalURINormalized));
}
- if ($this->CDRS->ratingEnabled && $this->duration) {
+ if ($this->stopTimeNormalized) {
+ if ($updatedFields) $query .= ", ";
+ $updatedFields++;
+ $query.=sprintf(" %s = '%s' ",$this->CDRS->stopTimeField,addslashes($this->stopTimeNormalized));
+ }
+
+ if ($this->CDRS->ratingEnabled && ($this->duration || $this->applicationType == 'message')) {
$Rate = new Rate($this->CDRS->rating_settings, $this->CDRS->cdrtool);
$RateDictionary=array(
'callId' => $this->callId,
'timestamp' => $this->timestamp,
'duration' => $this->duration,
'DestinationId' => $this->DestinationId,
'inputTraffic' => $this->inputTraffic,
'outputTraffic' => $this->outputTraffic,
'BillingPartyId' => $this->BillingPartyId,
'ResellerId' => $this->ResellerId,
'domain' => $this->domain,
'gateway' => $this->gateway,
'RatingTables' => &$this->CDRS->RatingTables,
'applicationType' => $this->applicationType,
'aNumber' => $this->aNumber,
'cNumber' => $this->cNumber,
'ENUMtld' => $this->ENUMtld
);
$Rate->calculate(&$RateDictionary);
$this->pricePrint = $Rate->pricePrint;
$this->price = $Rate->price;
$this->rateInfo = $Rate->rateInfo;
if (count($Rate->brokenRates)) {
$this->CDRS->brokenRates=array_merge($this->CDRS->brokenRates,array_keys($Rate->brokenRates));
}
if ($this->CDRS->priceField) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->priceField,$this->pricePrint);
if ($this->CDRS->rateField ) {
if ($updatedFields) $query .= ", ";
$updatedFields++;
$query.=sprintf(" %s = '%s' ",$this->CDRS->rateField,$this->rateInfo);
}
}
}
$query1 = sprintf("update %s set %s where %s = '%s'",$table,$query,$this->idField,$this->id);
if ($updatedFields) {
if ($this->CDRS->CDRdb1->query($query1)) {
if ($this->CDRS->CDRdb1->affected_rows()) {
if ($this->CallerIsLocal) {
if ($table == $this->CDRS->table) {
// cache usage only if current month
$_traffic=($this->inputTraffic+$this->outputTraffic)/2;
$_usage=array('calls' => 1,
'duration' => $this->duration,
'cost' => $Rate->price,
'traffic' => $_traffic
);
$this->cacheMonthlyUsage($_usage);
}
}
} else {
if (preg_match("/^(\w+)(\d{4})(\d{2})$/",$table,$m)) {
$previousTable=$m[1].date('Ym', mktime(0, 0, 0, $m[3]-1, "01", $m[2]));
$query2 = sprintf("update %s set %s where %s = '%s'",$previousTable,$query,$this->idField,$this->id);
if ($this->CDRS->CDRdb1->query($query2)) {
if ($this->CDRS->CDRdb1->affected_rows()) {
if ($this->CallerIsLocal) {
if ($previousTable == $this->CDRS->table) {
// cache usage only if current month
$_traffic=($this->inputTraffic+$this->outputTraffic)/2;
$_usage=array('calls' => 1,
'duration' => $this->duration,
'cost' => $Rate->price,
'traffic' => $_traffic
);
$this->cacheMonthlyUsage($_usage);
}
}
}
} else {
$log=sprintf ("Database error: %s (%s)",$this->CDRS->CDRdb1->Error,$this->CDRS->CDRdb1->Errno);
syslog(LOG_NOTICE, $log);
print($log);
return 0;
}
}
}
return 1;
} else {
$log=sprintf ("Database error for query %s: %s (%s)",$query1,$this->CDRS->CDRdb1->Error,$this->CDRS->CDRdb1->Errno);
syslog(LOG_NOTICE, $log);
print($log);
return 0;
}
}
} else {
if ($this->CDRS->BillingPartyIdField && $CarrierInfo['BillingPartyId']) {
$this->domain = $CarrierInfo['BillingDomain'];
}
if ($this->usernameNormalized && $this->usernameNormalized!=$this->username) {
$this->username=$this->usernameNormalized;
}
if ($this->aNumberNormalized && $this->aNumberNormalized!=$this->aNumber) {
$this->aNumber=$this->aNumberNormalized;
}
if ($this->domainNormalized && $this->domainNormalized != $this->domain) {
$this->domainNumber=$this->domainNormalized;
}
if ($this->cNumberNormalized && $this->cNumberNormalized!=$this->cNumber) {
$this->cNumber=$this->cNumberNormalized;
}
if ($this->CDRS->RemoteAddressField && $this->RemoteAddressNormalized && $this->RemoteAddressNormalized!= $this->RemoteAddress) {
$this->RemoteAddress=$this->RemoteAddressNormalized;
}
}
return 1;
}
function cacheMonthlyUsage($usage) {
if (!is_array($usage)) return ;
$accounts[$this->BillingPartyId]['usage']=$usage;
$this->CDRS->cacheMonthlyUsage($accounts);
}
function isCallerLocal() {
return 0;
}
function isCalleeLocal() {
return 0;
}
function obfuscateCallerId() {
global $obfuscateCallerId;
if ($obfuscateCallerId) {
}
}
function lookupRateFromNetwork(&$RateDictionary,&$fp) {
$this->rateInfo='';
$this->pricePrint='';
$this->price='';
$countEndofLines=0;
$cmd="ShowPrice";
foreach (array_keys(&$RateDictionary) as $key) {
$cmd .=" ".$key."=".$RateDictionary[$key]." ";
}
$this->price = 0;
$this->pricePrint = "";
$this->rateInfo = "";
if (fputs($fp,"$cmd\n") !== false) {
$i=0;
while ($i < 100) {
$i++;
$line = fgets($fp,1024);
if (!$line) {
syslog(LOG_NOTICE, "Error: lookupRateFromNetwork(): connection to network socket died");
break;
}
if (preg_match("/^\n/",$line) || preg_match("/^END/",$line)) {
break;
}
if ($i == 1) {
$this->price = trim($line);
$this->pricePrint = number_format($this->price,4);
continue;
}
$this->rateInfo.=$line;
}
}
}
function lookupGeoLocation ($ip) {
if ($_loc=geoip_record_by_name($ip)) {
return $_loc['country_name'].'/'.$_loc['city'];
} else if ($_loc=geoip_country_name_by_name($ip)) {
return $_loc;
} else {
return '';
}
}
}
function getLocalTime($timezone,$timestamp) {
global $CDRTool;
if (!$timezone || $timezone == $CDRTool['provider']['timezone']) {
return date("Y-m-d H:i:s", $timestamp);
}
putenv("TZ=$timezone");
$startTimeLocal=date("Y-m-d H:i:s", $timestamp);
$timezone=$CDRTool['provider']['timezone'];
putenv("TZ=$timezone");
return $startTimeLocal;
}
function validDay($month,$day,$year) {
if (!$month || !$year) {
return $day;
}
while (1) {
if (!checkdate($month,$day,$year) && $day) {
$day--;
next;
} else {
break;
}
}
return $day;
}
// include CDRTool modules defined in global.inc
if (is_array($CDRToolModules)) {
foreach ($CDRToolModules as $module) {
$module_filename="cdr_".$module.".php";
include($module_filename);
}
}
function unLockNormalization ($dbid,$lockname) {
$query=sprintf("SELECT RELEASE_LOCK('%s')",$lockname);
$log=sprintf("Unlock %s",$lockname);
syslog(LOG_NOTICE, $log);
if (!$dbid->query($query)) {
$log="Error in unLockNormalization()";
syslog(LOG_NOTICE, $log);
}
}
class SIPonline {
function SIPonline ($datasource='',$database='db',$table='location') {
global $CDRTool;
$expandAll = $_REQUEST['expandAll'];
$domain = $_REQUEST['domain'];
$this->expandAll = $expandAll;
$this->domain = $domain;
$this->datasource = $datasource;
if (strlen($CDRTool['filter']['domain'])) {
$this->allowedDomains=explode(" ",$CDRTool['filter']['domain']);
$allowed_domains_sql="";
$j=0;
foreach ($this->allowedDomains as $_domain) {
if ($j>0) $allowed_domains_sql.=",";
$allowed_domains_sql.="'".addslashes($_domain)."'";
$j++;
}
}
$this->locationDB = new $database;
$this->locationTable = $table;
$this->Registered=array();
$this->countUA=array();
$where = " where (1=1) " ;
if ($allowed_domains_sql) {
$where.= sprintf("and domain in (%s)",$allowed_domains_sql) ;
}
$query=sprintf("select count(*) as c, domain
from %s %s
group by domain
order by domain ASC",$this->locationTable,$where);
$this->locationDB->query($query);
$this->domains=$this->locationDB->num_rows();
while ($this->locationDB->next_record()) {
$this->Registered[$this->locationDB->f('domain')]=$this->locationDB->f('c');
$this->total=$this->total+$this->locationDB->f('c');
}
$query=sprintf("select count(*) as c, user_agent
from %s %s",$this->locationTable,$where);
if ($this->domain) {
$query.=sprintf(" and domain = '%s' ",$this->domain);
}
$query.="
group by user_agent
order by c DESC";
$this->locationDB->query($query);
while ($this->locationDB->next_record()) {
$this->countUA[$this->locationDB->f('user_agent')]=$this->locationDB->f('c');
}
}
function showHeader() {
print "<table border=0 cellspacing=1 class=border>";
print "<tr bgcolor=lightgrey>
";
if ($this->domain) {
print "
<th></th>
<th width=120 align=right>User@</th>
<th align=left>Domain</th>
<th></th>
<th>SIP UA contact</th>
<th>NAT address</th>
<th>User Agent</th>
<th>Expires</th>
<th>Remain</th>
";
} else {
print "
<th></td>
<th width=120 align=right>Users@</th>
<th align=left>Domain</th>
";
}
print "
</tr>
";
}
function showFooter() {
print "
<tr bgcolor=lightgrey>
<th></td>
<th align=right>$this->total users@</td>
<th align=left>$this->domains domains</td>
</tr>
</table>
";
}
function showAll() {
global $found;
$this->showHeader();
foreach (array_keys($this->Registered) as $ld) {
$onlines=$this->Registered[$ld];
$rr = floor($found/2);
$mod = $found-$rr*2;
if ($mod ==0) {
$bgcolor="lightgrey";
} else {
$bgcolor="white";
}
if ($this->expandAll || ($this->domain && $this->domain==$ld)) {
$this->show($ld);
} else {
$found++;
$url = sprintf("%s?datasource=%s&domain=%s",
$_SERVER['PHP_SELF'],
urlencode($this->datasource),
urlencode($ld)
);
print "
<tr bgcolor=white>
<td valign=top align=right>$found</td>
<td valign=top align=right>$onlines users@</td>
<td valign=top><a href=$url>$ld</a></td>
";
if ($this->domain) {
print "
<td></td>
<td></td>
<td></td>
<td></td>
";
}
print "
</tr>
";
}
}
$this->showfooter();
/*
print "<p>";
$this->showUAdistribution();
*/
}
function show() {
global $found;
$query="SELECT *, SEC_TO_TIME(UNIX_TIMESTAMP(expires)-UNIX_TIMESTAMP(NOW())) AS remain
FROM location
";
if ($this->domain) $query.=" where domain = '$this->domain'";
$query.= " ORDER BY domain ASC, username ASC ";
$this->locationDB->query($query);
while ($this->locationDB->next_record()) {
$rr = floor($found/2);
$mod = $found-$rr*2;
if ($mod ==0) {
$bgcolor="lightgrey";
} else {
$bgcolor="white";
}
$found++;
$username = $this->locationDB->f('username');
$domain = $this->locationDB->f('domain');
$contact = $this->locationDB->f('contact');
$received = $this->locationDB->f('received');
$user_agent = $this->locationDB->f('user_agent');
$expires = $this->locationDB->f('expires');
$remain = $this->locationDB->f('remain');
$contact_print=substr($contact,4);
$c_els=explode(";", $contact);
$r_els=explode(";", $received);
$transport="UDP";
if ($c_els[1] && preg_match("/transport=(tcp|tls)/i",$c_els[1],$m)) {
$transport=strtoupper($m[1]);
}
$sip_account=$username."@".$domain;
print "
<tr bgcolor=$bgcolor>
<td valign=top align=right class=border>$found</td>
<td valign=top align=right class=border>$username@</td>
<td valign=top bgcolor=lightyellow class=border>$domain</td>
<td valign=top class=border>$transport</td>
<td valign=top align=right class=border>$c_els[0]</td>
<td valign=top align=right class=border>$r_els[0]</td>
<td valign=top class=border>$user_agent</td>
<td valign=top class=border>$expires</td>
<td valign=top align=right class=border>$remain</td>
</tr>
";
$seen[$username]++;
$seen[$domain]++;
}
}
function showUAdistribution () {
print "<table border=0 cellspacing=1 class=border>";
print "<tr bgcolor=lightgrey> ";
print "<td></td>";
print "<th>User agent</th>";
print "<th>Users</th>";
print "</tr> ";
while (list($k,$v) = each($this->countUA)) {
$users=$users+$v;
$count++;
print "<tr> ";
print "<td>$count</td>";
print "<td>$k</td>";
print "<td>$v</td>";
print "</tr>";
}
print "<tr bgcolor=lightgrey> ";
print "<td></td>";
print "<td><b>$this->domain</b></td>";
print "<td><b>$users</b></td>";
print "</tr> ";
print "</table>";
}
}
class PrepaidHistory {
function PrepaidHistory() {
$this->db = new DB_cdrtool;
}
function purge($days=7) {
$beforeDate=Date("Y-m-d", time()-$days*3600*24);
$query=sprintf("delete from prepaid_history where date < '%s' and action like 'Debit balance%s'",$beforeDate,'%');
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)\n",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE,$log);
} else {
$log=sprintf ("Purged %d records from prepaid history before %s\n",$this->db->affected_rows(),$beforeDate);
print $log;
syslog(LOG_NOTICE,$log);
}
}
}
?>
diff --git a/library/cdr_opensips.php b/library/cdr_opensips.php
index 6ce1c73..42966e5 100644
--- a/library/cdr_opensips.php
+++ b/library/cdr_opensips.php
@@ -1,4521 +1,4526 @@
<?
class CDRS_opensips extends CDRS {
var $table = "radacct";
var $CDR_class = "CDR_opensips";
var $subscriber_table = "subscriber";
var $ENUMtld = '';
var $maxCDRsNormalizeWeb = 500;
var $sipTrace = 'sip_trace';
var $mediaTrace = 'media_trace';
var $missed_calls_group = 'missed-calls';
var $CDRFields=array('id' => 'RadAcctId',
'callId' => 'AcctSessionId',
'duration' => 'AcctSessionTime',
'startTime' => 'AcctStartTime',
'stopTime' => 'AcctStopTime',
'inputTraffic' => 'AcctInputOctets',
'outputTraffic' => 'AcctOutputOctets',
'aNumber' => 'CallingStationId',
'username' => 'UserName',
'domain' => 'Realm',
'cNumber' => 'CalledStationId',
'timestamp' => 'timestamp',
'SipMethod' => 'SipMethod',
'disconnect' => 'SipResponseCode',
'SipFromTag' => 'SipFromTag',
'SipToTag' => 'SipToTag',
'RemoteAddress' => 'SipTranslatedRequestURI',
'SipCodec' => 'SipCodecs',
'SipUserAgents' => 'SipUserAgents',
'applicationType' => 'SipApplicationType',
'BillingPartyId' => 'UserName',
'SipRPID' => 'SipRPID',
'SipProxyServer' => 'NASIPAddress',
'gateway' => 'SourceIP',
'SourceIP' => 'SourceIP',
'SourcePort' => 'SourcePort',
'CanonicalURI' => 'CanonicalURI',
'normalized' => 'Normalized',
'rate' => 'Rate',
'price' => 'Price',
'DestinationId' => 'DestinationId',
'ResellerId' => 'BillingId',
'MediaTimeout' => 'MediaInfo',
'RTPStatistics' => 'RTPStatistics',
'ENUMtld' => 'ENUMtld',
'UserAgent' => 'UserAgent',
'FromHeader' => 'FromHeader'
);
var $CDRNormalizationFields=array('id' => 'RadAcctId',
'callId' => 'AcctSessionId',
'username' => 'UserName',
'domain' => 'Realm',
'gateway' => 'SourceIP',
'duration' => 'AcctSessionTime',
'startTime' => 'AcctStartTime',
'stopTime' => 'AcctStopTime',
'inputTraffic' => 'AcctInputOctets',
'outputTraffic' => 'AcctOutputOctets',
'aNumber' => 'CallingStationId',
'cNumber' => 'CalledStationId',
'timestamp' => 'timestamp',
'RemoteAddress' => 'SipTranslatedRequestURI',
'CanonicalURI' => 'CanonicalURI',
'SipMethod' => 'SipMethod',
'applicationType' => 'SipApplicationType',
'BillingPartyId' => 'UserName',
'ResellerId' => 'BillingId',
'price' => 'Price',
'DestinationId' => 'DestinationId',
'ENUMtld' => 'ENUMtld'
);
var $GROUPBY=array('UserName' => 'SIP Billing Party',
'CallingStationId' => 'SIP Caller Party',
'SipRPID' => 'SIP Remote Party Id',
'CanonicalURI' => 'SIP Canonical URI',
'DestinationId' => 'SIP Destination Id',
'NASIPAddress' => 'SIP Proxy',
'SourceIP' => 'Source IP',
'Realm' => 'SIP Billing domain',
'UserAgent' => 'User Agent',
'SipCodecs' => 'Codec type',
'SipApplicationType' => 'Application',
'SipResponseCode' => 'SIP status code',
'BillingId' => 'Tech prefix',
' ' => '-------------',
'hour' => 'Hour of day',
'DAYOFWEEK' => 'Day of Week',
'DAYOFMONTH' => 'Day of Month',
'DAYOFYEAR' => 'Day of Year',
'BYMONTH' => 'Month',
'BYYEAR' => 'Year'
);
var $FormElements=array(
"begin_hour","begin_min","begin_month","begin_day","begin_year","begin_datetime",
"end_hour","end_min","end_month","end_day","end_year","end_datetime",
"call_id","sip_proxy",
"a_number","a_number_comp","UserName","UserName_comp","BillingId",
"c_number","c_number_comp","DestinationId","ExcludeDestinations",
"NASPortId","Realm","Realms",
"SipMethod","SipCodec","SipRPID","UserAgent",
"application","SipStatus","SipStatusClass","SipProxyServer","gateway",
"duration","action","MONTHYEAR",
"order_by","order_type","group_by",
"cdr_source","trace",
"unnormalize","MediaTimeout","cdr_table","maxrowsperpage"
);
var $createTableFile="/setup/radius/OpenSIPS/radacct.mysql";
function LoadDisconnectCodes() {
$query="select * from sip_status order by code";
$this->disconnectCodesElements[]=array("label"=>"Any Status","value"=>"");
$this->disconnectCodesElements[]=array("label"=>"Undefined (0)","value"=>"0");
$this->disconnectCodesClassElements[]=array("label"=>"Any Status Class","value"=>"");
if ($this->cdrtool->query($query)) {
while($this->cdrtool->next_record()) {
$key = $this->cdrtool->f('code');
$value = $this->cdrtool->f('description');
$value_print = $this->cdrtool->f('description')." (".$this->cdrtool->f('code').")";
if (preg_match("/^[^2-6]/",$key)) {
continue;
}
$this->disconnectCodesElements[]=array("label"=>$value_print,"value"=>$key);
$this->disconnectCodesDescription[$key]=$value;
$class=substr($key,0,1);
$class_text=substr($key,0,1)."XX (".$this->cdrtool->f('code_type').")";
if (!$seen[$class]) {
$this->disconnectCodesClassElements[]=array("label"=>$class_text,"value"=>substr($key,0,1));
$this->disconnectCodesClassDescription[substr($key,0,1)]=$class_text;
$seen[$class]++;
}
$i++;
}
}
}
function showTableHeader($begin_datetime,$end_datetime) {
if (preg_match("/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/",$begin_datetime) && preg_match("/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/",$end_datetime)) {
print "<p>From $begin_datetime to $end_datetime";
}
print "
<table border=1 cellspacing=2 width=100% align=center>
<tr>
<td>
<table border=0 cellspacing=2 width=100%>
<tr bgcolor=lightgrey>
<td>Id</td>
<td><b>Start Time</b></td>
<td><b>SIP Caller</b></td>
<td><b>Location</b></td>
<td><b>Sip Proxy</b></td>
<td><b>SIP Destination</b></td>
<td><b>Dur</b></td>
<td><b>Price</b></td>
<td align=right><b>KBIn</b></td>
<td align=right><b>KBOut</b></td>
<td align=right><b>Status</b></td>
<td align=right><b>Codecs</b></td>
</tr>
";
}
function showExportHeader() {
print "id,StartTime,StopTime,BillingParty,BillingDomain,PSTNCallerId,CallerParty,CalledParty,DestinationId,DestinationName,RemoteAddress,CanonicalURI,Duration,Price,SIPProxy,Caller KBIn,Called KBIn,CallingUserAgent,CalledUserAgent,StatusCode,StatusName,Codec,Application\n";
}
function showTableHeaderSubscriber($begin_datetime,$end_datetime) {
if (!$this->export) {
if (preg_match("/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/",$begin_datetime) && preg_match("/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/",$end_datetime)) {
print "<p>
From $begin_datetime to $end_datetime
";
}
print "
<table border=1 cellspacing=2 width=100% align=center>
<tr>
<td>
<table border=0 cellspacing=2 width=100%>
<tr bgcolor=lightgrey>
<td>Id</td>
<td><b>Start Time</b>
<td><b>SIP Caller</b></td>
<td><b>Location</b></td>
<td><b>Sip Proxy</b></td>
<td><b>SIP Destination</b></td>
<td><b>Duration</b></td>
<td><b>Price</b></td>
<td align=right><b>KBIn</b></td>
<td align=right><b>KBOut</b></td>
</tr>
";
} else {
print "id,StartTime,StopTime,SIPBillingParty,SIPBillingDomain,RemotePartyId,CallerParty,CalledParty,DestinationId,DestinationName,RemoteAddress,CanonicalURI,Duration,Price,SIPProxy,Caller KBIn,Called KBIn,CallingUserAgent,CalledUserAgent,StatusCode,StatusName,Codec,Application\n";
}
}
function showTableHeaderStatistics($begin_datetime,$end_datetime) {
$group_byPrint=$this->GROUPBY[$this->group_byOrig];
if (!$this->export) {
if (preg_match("/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/",$begin_datetime) && preg_match("/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/",$end_datetime)) {
print "<p>
From $begin_datetime to $end_datetime
";
}
print "
<table border=1 cellspacing=2 width=100% align=center>
<tr>
<td>
<table border=0 cellspacing=2 width=100%>
<tr bgcolor=lightgrey>
<td></td>
<td> <b>Calls</b></td>
<td align=right><b>Seconds</b></td>
<td align=right><b>Minutes</b></td>
<td align=right><b>Hours</b></td>
<td align=right><b>Price</b></td>
<td align=right><b>TrafficIn(MB)</b></td>
<td align=right><b>TrafficOut(MB)</b></td>
<td align=center colspan=2><b>Success</b></td>
<td align=center colspan=2><b>Failure</b></td>
<td> <b>$group_byPrint</b></td>
<td> <b>Description</b></td>
<td> <b>Action</b></td>
</tr>
";
} else {
print "id,Calls,Seconds,Minutes,Hours,Price,TrafficIn(MB),TrafficOut(MB),Success(%),Success(calls),Failure(%),Failure(calls),$group_byPrint,Description\n";
}
}
function initForm() {
// form els added below must have global vars
foreach ($this->FormElements as $_el) {
global ${$_el};
${$_el} = trim($_REQUEST[$_el]);
}
$action = "search";
if ($this->CDRTool['filter']['gateway']) {
$gateway=$this->CDRTool["filter"]["gateway"];
}
if ($this->CDRTool['filter']['aNumber']) {
$UserName=$this->CDRTool['filter']['aNumber'];
}
if ($this->CDRTool['filter']['domain']) {
$Realm = $this->CDRTool['filter']['domain'];
}
if (!$maxrowsperpage) $maxrowsperpage=15;
$this->f = new form;
if (isset($this->CDRTool['dataSourcesAllowed'])) {
while (list($k,$v)=each($this->CDRTool['dataSourcesAllowed'])) {
if ($this->DATASOURCES[$v]['invisible']) continue;
$cdr_source_els[]=array("label"=>$this->DATASOURCES[$v]['name'],"value"=>$v);
}
}
if (!$cdr_source) $cdr_source=$cdr_source_els[0]['value'];
$this->f->add_element(array("name"=>"cdr_source",
"type"=>"select",
"options"=>$cdr_source_els,
"size"=>"1",
"extrahtml"=>"onChange=\"document.datasource.submit.disabled = true; location.href = 'callsearch.phtml?cdr_source=' + this.options[this.selectedIndex].value\"",
"value"=>"$cdr_source"
)
);
$cdr_table_els=array();
foreach ($this->tables as $_table) {
if (preg_match("/^.*(\d{6})$/",$_table,$m)) {
$cdr_table_els[]=array("label"=>$m[1],"value"=>$_table);
} else {
$cdr_table_els[]=array("label"=>$_table,"value"=>$_table);
}
}
$this->f->add_element(array( "name"=>"cdr_table",
"type"=>"select",
"options"=>$cdr_table_els,
"size"=>"1",
"value"=>$cdr_table
));
if ($begin_datetime) {
preg_match("/^(\d\d\d\d)-(\d+)-(\d+)\s+(\d\d):(\d\d)/", "$begin_datetime", $parts);
$begin_year =date(Y,$begin_datetime);
$begin_month=date(m,$begin_datetime);
$begin_day =date(d,$begin_datetime);
$begin_hour =date(H,$begin_datetime);
$begin_min =date(i,$begin_datetime);
} else {
$begin_day = $_REQUEST["begin_day"];
$begin_month = $_REQUEST["begin_month"];
$begin_year = $_REQUEST["begin_year"];
$begin_hour = $_REQUEST["begin_hour"];
$begin_min = $_REQUEST["begin_min"];
}
if ($end_datetime) {
preg_match("/^(\d\d\d\d)-(\d+)-(\d+)\s+(\d\d):(\d\d)/", "$end_datetime", $parts);
$end_year =date(Y,$end_datetime);
$end_month =date(m,$end_datetime);
$end_day =date(d,$end_datetime);
$end_hour =date(H,$end_datetime);
$end_min =date(i,$end_datetime);
} else {
$end_day = $_REQUEST["end_day"];
$end_month = $_REQUEST["end_month"];
$end_year = $_REQUEST["end_year"];
$end_hour = $_REQUEST["end_hour"];
$end_min = $_REQUEST["end_min"];
}
// corect last day of the month to be valid day
$begin_day = validDay($begin_month,$begin_day,$begin_year);
$end_day = validDay($end_month,$end_day,$end_year);
$default_year = Date("Y");
$default_month = Date("m");
$default_day = Date("d");
$default_hour = Date(H,time());
if ($default_hour > 1) $default_hour=$default_hour-1;
$default_hour = preg_replace("/^(\d)$/","0$1",$default_hour);
$default_min = Date("i");
if ($default_min > 10) {
$default_min=$default_min-10;
$default_min=preg_replace("/^(\d)$/","0$1",$default_min);
}
if (!$begin_hour) $begin_hour = $default_hour;
if (!$begin_min) $begin_min = $default_min;
if (!$begin_day) $begin_day = $default_day;
if (!$begin_month) $begin_month = $default_month;
if (!$begin_year) $begin_year = $default_year;
if (!$end_hour) $end_hour = 23;
if (!$end_min) $end_min = 55;
if (!$end_day) $end_day = $default_day;
if (!$end_month) $end_month = $default_month;
if (!$end_year) $end_year = $default_year;
$m=0;
while ($m<24) {
if ($m<10) { $v="0".$m; } else { $v=$m; }
$hours_els[]=array("label"=>$v,"value"=>$v);
$m++;
}
$this->f->add_element(array(
"name"=>"begin_hour",
"type"=>"select",
"options"=>$hours_els,
"size"=>"1"
));
$this->f->add_element(array( "name"=>"end_hour",
"type"=>"select",
"options"=>$hours_els,
"size"=>"1",
"value"=>"23"
));
$m=0;
while ($m<60) {
if ($m<10) { $v="0".$m; } else { $v=$m; }
$min_els[]=array("label"=>$v,"value"=>$v);
$m++;
}
$this->f->add_element(array( "name"=>"begin_min",
"type"=>"select",
"options"=>$min_els,
"size"=>"1"
));
$this->f->add_element(array(
"name"=>"end_min",
"type"=>"select",
"options"=>$min_els,
"size"=>"1"
));
$m=1;
while ($m<32) {
if ($m<10) { $v="0".$m; } else { $v=$m; }
$days_els[]=array("label"=>$v,"value"=>$v);
$m++;
}
$this->f->add_element(array( "name"=>"begin_day",
"type"=>"select",
"options"=>$days_els,
"size"=>"1"
));
$this->f->add_element(array( "name"=>"end_day",
"type"=>"select",
"options"=>$days_els,
"size"=>"1"
));
$m=1;
while ($m<13) {
if ($m<10) { $v="0".$m; } else { $v=$m; }
$month_els[]=array("label"=>$v,"value"=>$v);
$m++;
}
$this->f->add_element(array( "name"=>"begin_month",
"type"=>"select",
"options"=>$month_els,
"size"=>"1"
));
$this->f->add_element(array( "name"=>"end_month",
"type"=>"select",
"options"=>$month_els,
"size"=>"1"
));
$thisYear=date("Y",time());
$y=$thisYear;
while ($y>$thisYear-6) {
$year_els[]=array("label"=>$y,"value"=>$y);
$y--;
}
$this->f->add_element(array( "name"=>"begin_year",
"type"=>"select",
"options"=>$year_els,
"size"=>"1"
));
$this->f->add_element(array( "name"=>"end_year",
"type"=>"select",
"options"=>$year_els,
"size"=>"1"
));
$this->f->add_element(array( "name"=>"call_id",
"type"=>"text",
"size"=>"50",
"maxlength"=>"100"
));
$this->f->add_element(array( "name"=>"UserName",
"type"=>"text",
"size"=>"25",
"maxlength"=>"255"
));
$this->f->add_element(array( "name"=>"a_number",
"type"=>"text",
"size"=>"25",
"maxlength"=>"255"
));
$this->f->add_element(array( "name"=>"BillingId",
"type"=>"text",
"size"=>"25",
"maxlength"=>"255"
));
$this->f->add_element(array( "name"=>"c_number",
"type"=>"text",
"size"=>"25",
"maxlength"=>"255"
));
$this->f->add_element(array( "name"=>"SipStatus",
"type"=>"select",
"options"=>$this->disconnectCodesElements,
"size"=>"1",
"value"=>$SipStatus,
));
$this->f->add_element(array( "name"=>"SipStatusClass",
"type"=>"select",
"options"=>$this->disconnectCodesClassElements,
"size"=>"1"
));
if (!$this->CDRTool['filter']['aNumber']) {
$durations_els = array(
array("label"=>"All calls","value"=>""),
array("label"=>"0 seconds","value"=>"zero"),
array("label"=>"non 0 seconds","value"=>"nonzero"),
array("label"=>"non 0 seconds with 0 price","value"=>"zeroprice"),
array("label"=>"less than 5 seconds","value"=>"< 5"),
array("label"=>"more than 5 seconds","value"=>"> 5"),
array("label"=>"less than 60 seconds","value"=>"< 60"),
array("label"=>"greater than 1 hour","value"=>"> 3600"),
array("label"=>"one hour","value"=>"onehour"),
array("label"=>"greater than 5 hours","value"=>"> 18000"),
array("label"=>"Un-normalized calls","value"=>"unnormalized"),
array("label"=>"Un-normalized calls > 0s","value"=>"unnormalized_duration"),
array("label"=>"One way media","value"=>"onewaymedia")
);
} else {
$durations_els = array(
array("label"=>"All calls","value"=>""),
array("label"=>"0 seconds call","value"=>"zero"),
array("label"=>"Succesfull calls","value"=>"nonzero"),
array("label"=>"less than 60 seconds","value"=>"< 60"),
array("label"=>"greater than 1 hour","value"=>"> 3600")
);
$this->GROUPBY=array(
'UserName' => 'SIP Billing Party',
'CallingStationId' => 'SIP Caller Party',
'DestinationId' => 'SIP Destination Id',
'SipApplicationType' => 'Application',
' ' => '-------------',
'hour' => 'Hour of day',
'DAYOFWEEK' => 'Day of Week',
'DAYOFMONTH' => 'Day of Month',
'DAYOFYEAR' => 'Day of Year',
'BYMONTH' => 'Month',
'BYYEAR' => 'Year'
);
}
$this->f->add_element(array( "name"=>"duration",
"type"=>"select",
"options"=>$durations_els,
"value"=>"All",
"size"=>"1"
));
$comp_ops_els = array(
array("label"=>"Begins with","value"=>"begin"),
array("label"=>"Contains","value"=>"contain"),
array("label"=>"Is empty","value"=>"empty"),
array("label"=>"Equal","value"=>"equal")
);
$this->f->add_element(array( "name"=>"a_number_comp",
"type"=>"select",
"options"=>$comp_ops_els,
"value"=>"begin",
"size"=>"1"
));
$this->f->add_element(array( "name"=>"c_number_comp",
"type"=>"select",
"options"=>$comp_ops_els,
"value"=>"begin",
"size"=>"1"
));
$this->f->add_element(array( "name"=>"UserName_comp",
"type"=>"select",
"options"=>$comp_ops_els,
"value"=>"begin",
"size"=>"1"
));
$this->f->add_element(array( "name"=>"Realm",
"type"=>"text",
"size"=>"25",
"maxlength"=>"25"
));
$this->f->add_element(array( "name"=>"MediaTimeout",
"type"=>"checkbox",
"value"=>"1"
));
$this->f->add_element(array("type"=>"submit",
"name"=>"submit",
"value"=>"Search"
));
$max_els=array(
array("label"=>"5","value"=>"5"),
array("label"=>"10","value"=>"10"),
array("label"=>"15","value"=>"15"),
array("label"=>"25","value"=>"25"),
array("label"=>"50","value"=>"50"),
array("label"=>"100","value"=>"100"),
array("label"=>"500","value"=>"500")
);
$this->f->add_element(array( "name"=>"maxrowsperpage",
"type"=>"select",
"options"=>$max_els,
"size"=>"1",
"value"=>"25"
));
$order_type_els=array(
array("label"=>"Descending","value"=>"DESC"),
array("label"=>"Ascending","value"=>"ASC")
);
$this->f->add_element(array( "name"=>"order_type",
"type"=>"select",
"options"=>$order_type_els,
"size"=>"1"
));
$this->f->add_element(array("type"=>"hidden",
"name"=>"action",
"value"=>$action,
));
$order_by_els=array(array("label"=>"Id","value"=>"RadAcctId"),
array("label"=>"Date","value"=>"AcctStopTime"),
array("label"=>"Billing Party","value"=>"UserName"),
array("label"=>"Remote Party Id","value"=>"SipRPID"),
array("label"=>"Caller Party","value"=>"CallingStationId"),
array("label"=>"Destination","value"=>"CalledStationId"),
array("label"=>"Duration","value"=>"AcctSessionTime"),
array("label"=>"Input traffic","value"=>"AcctInputOctets"),
array("label"=>"Output traffic","value"=>"AcctOutputInputOctets"),
array("label"=>"Price","value"=>"Price"),
array("label"=>"Failures(%)","value"=>"zeroP"),
array("label"=>"Success(%)","value"=>"nonzeroP"),
array("label"=>"Group by","value"=>"group_by")
);
$group_by_els[]=array("label"=>"","value"=>"");
while (list($k,$v)=each($this->GROUPBY)) {
$group_by_els[]=array("label"=>$v,"value"=>$k);
}
$this->f->add_element(array("name"=>"order_by",
"type"=>"select",
"options"=>$order_by_els,
"value"=>$order_by,
"size"=>"1"
));
$this->f->add_element(array("name"=>"group_by",
"type"=>"select",
"options"=>$group_by_els,
"value"=>$group_by,
"size"=>"1"
));
$application_els=array(
array("label"=>"Any Application", "value"=>""),
array("label"=>"Audio", "value"=>"audio"),
array("label"=>"Video", "value"=>"video"),
array("label"=>"SMS" , "value"=>"message"),
array("label"=>"IM Chat" , "value"=>"chat"),
array("label"=>"File Transfer","value"=>"file-transfer")
);
$this->f->add_element(array("name"=>"application",
"type"=>"select",
"options"=>$application_els,
"value"=>$application,
"size"=>"1"
));
$this->f->add_element(array("name"=>"UserAgent",
"type"=>"text",
"size"=>"25",
"maxlength"=>"50",
"value"=>$UserAgent
));
$this->f->add_element(array("name"=>"SipCodec",
"type"=>"text",
"size"=>"10",
"maxlength"=>"50",
"value"=>$SipCodec
));
$this->f->add_element(array("name"=>"SipProxyServer",
"type"=>"text",
"size"=>"25",
"maxlength"=>"255",
"value"=>$SipProxyServer
));
$this->f->add_element(array("name"=>"gateway",
"type"=>"text",
"size"=>"25",
"maxlength"=>"255",
"value"=>$gateway
));
$this->f->add_element(array("name"=>"sip_proxy",
"type"=>"text",
"size"=>"25",
"maxlength"=>"255",
"value"=>$sip_proxy
));
$this->f->add_element(array("name"=>"DestinationId",
"type"=>"text",
"size"=>"10"
));
$this->f->add_element(array( "name"=>"ExcludeDestinations",
"type"=>"text",
"size"=>"20",
"maxlength"=>"255"
));
$this->f->load_defaults();
}
function searchForm() {
global $perm;
$this->initForm();
$this->f->start("","POST","","","datasource");
print "<table cellpadding=5 CELLSPACING=0 border=6 width=100% align=center>";
$this->showDataSources ($this->f);
$this->showDateTimeElements ($this->f);
// freeze some form els
if ($this->CDRTool['filter']['aNumber']) {
$ff[]="a_number";
$ff[]="a_number_comp";
$ff[]="UserName";
$ff[]="UserName_comp";
}
if ($this->CDRTool['filter']['domain']) {
$Realm=$this->CDRTool['filter']['domain'];
$ff[]="Realm";
}
if ($this->CDRTool['filter']['gateway']) {
$gateway=$this->CDRTool['filter']['gateway'];
$ff[]="gateway";
}
if (count($ff)) {
$this->f->freeze($ff);
}
print "
<tr>
<td align=left>
<b>SIP Call Id / Source IP</b>
</td>
<td valign=top>
";
$this->f->show_element("call_id","");
print " / ";
$this->f->show_element("gateway","");
print "
Sip Proxy ";
$this->f->show_element("sip_proxy","");
print "
</td>
</tr>
<tr>
</tr>
";
print "
<tr>
<td align=left>
<b>User Agent / Media Codecs</b>
</td>
<td valign=top>
";
$this->f->show_element("UserAgent","");
print " Codec: ";
$this->f->show_element("SipCodec","");
print "
</td>
</tr>
<tr>
</tr>
";
print "
<tr>
<td align=left>
<b>SIP Billing Party (Username)</b>
</td>
<td valign=top>
";
$this->f->show_element("UserName_comp","");
$this->f->show_element("UserName","");
print "@";
$this->f->show_element("Realm","");
print " Tech prefix: ";
$this->f->show_element("BillingId","");
print "
</td>
</tr>
<tr>
</tr>
";
print "
<tr>
<td align=left>
<b>
SIP Caller Party (From URI)
</b>
</td>
<td valign=top>
";
$this->f->show_element("a_number_comp","");
$this->f->show_element("a_number");
print "
</td>
</tr>
<tr>
</tr>
";
print "
<tr>
<td align=left>
<b>SIP Destination (Canonical URI)
</b>
</td>
<td valign=top> ";
$this->f->show_element("c_number_comp","");
$this->f->show_element("c_number","");
print " Exclude: ";
$this->f->show_element("ExcludeDestinations_comp");
$this->f->show_element("ExcludeDestinations","");
print "
</td>
</tr>
<tr>
</tr>
";
print "
<tr>
<td align=left>
<b>Duration / Application / Status</b>
</td>
<td valign=top> ";
$this->f->show_element("duration","");
$this->f->show_element("application","");
$this->f->show_element("SipStatus","");
$this->f->show_element("SipStatusClass","");
print " Media timeout ";
$this->f->show_element("MediaTimeout","");
print "
</td>
</tr>
";
print "
<tr>
<td align=left>
<b>Order by / Group by</b>
</td>
<td valign=top>
";
$this->f->show_element("order_by","");
$this->f->show_element("order_type","");
if ($perm->have_perm("statistics")) {
print " Group by ";
$this->f->show_element("group_by","");
}
print "<nobr> Max results per page ";
$this->f->show_element("maxrowsperpage","");
print "</nobr>&nbsp";
if (!$perm->have_perm('readonly')) {
print ";&nbsp;&nbsp; <nobr>ReNormalize";
print "<input type=checkbox name=ReNormalize value=1>
</nobr>";
}
print "
</td>
</tr>
";
print "
</table>
<p>
<center>
";
$this->f->show_element("submit","");
$this->f->finish();
print "
</center>
";
}
function searchFormSubscriber() {
global $perm;
$this->initForm();
$this->f->start("","POST","","","datasource");
print "
<table cellpadding=5 CELLSPACING=0 border=6 width=100% align=center>
";
$this->showDataSources ($this->f);
$this->showDateTimeElements ($this->f);
// freeze some form els
if ($this->CDRTool['filter']['aNumber']) {
$ff[]="UserName";
}
if ($this->CDRTool['filter']['domain']) {
$ff[]="Realm";
}
if ($this->CDRTool["filter"]["gateway"]) {
$ff[]="gateway";
}
if (count($ff)) {
$this->f->freeze($ff);
}
print "
<tr>
<td align=left>
<b>
SIP Caller Party
</b>
</td>
<td valign=top>
";
$this->f->show_element("a_number","");
print "
</td>
</tr>
<tr>
</tr>
";
print "
<tr>
<td align=left>
<b>
SIP Billing Party
</b>
</td>
<td valign=top>
";
$this->f->show_element("UserName","");
print "
</td>
</tr>
<tr>
</tr>
";
print "
<tr>
<td align=left>
<b>
SIP Destination
</b>
</td>
<td valign=top> ";
$this->f->show_element("c_number_comp","");
$this->f->show_element("c_number","");
//$this->f->show_element("DestinationId","");
print "
</td>
</tr>
<tr>
</tr>
";
print "
<tr>
<td align=left>
<b>SIP Session duration</b>
</td>
<td valign=top> ";
$this->f->show_element("duration","");
print " Application ";
$this->f->show_element("application","");
print "
</td>
</tr>
";
print "
<tr>
<td align=left>
<b>Order by</b>
</td>
<td valign=top>
";
$this->f->show_element("order_by","");
$this->f->show_element("order_type","");
if ($perm->have_perm("statistics")) {
print " Group by ";
$this->f->show_element("group_by","");
}
print " Max results per page ";
$this->f->show_element("maxrowsperpage","");
print "
</td>
</tr>
";
print "
</table>
<p>
<center>
";
$this->f->show_element("submit","");
$this->f->finish();
print "
</center>
";
}
function show() {
global $perm;
if (!is_object($this->CDRdb)) {
$log=sprintf("Error: CDR database is not initalized");
print $log;
return false;
}
foreach ($this->FormElements as $_el) {
${$_el} = trim($_REQUEST[$_el]);
}
// overwrite some elements based on user rights
if ($this->CDRTool['filter']['gateway']) {
$gateway =$this->CDRTool['filter']['gateway'];
}
if (!$this->export) {
if (!$begin_datetime) {
$begin_datetime="$begin_year-$begin_month-$begin_day $begin_hour:$begin_min";
$begin_datetime_timestamp=mktime($begin_hour, $begin_min, 0, $begin_month,$begin_day,$begin_year);
} else {
$begin_datetime_timestamp=$begin_datetime;
$begin_datetime=Date("Y-m-d H:i",$begin_datetime);
}
$begin_datetime_url=urlencode($begin_datetime_timestamp);
if (!$end_datetime) {
$end_datetime_timestamp=mktime($end_hour, $end_min, 0, $end_month,$end_day,$end_year);
$end_datetime="$end_year-$end_month-$end_day $end_hour:$end_min";
} else {
$end_datetime_timestamp=$end_datetime;
$end_datetime=Date("Y-m-d H:i",$end_datetime);
}
$end_datetime_url=urlencode($end_datetime_timestamp);
} else {
$begin_datetime=Date("Y-m-d H:i",$begin_datetime);
$end_datetime=Date("Y-m-d H:i",$end_datetime);
}
if (!$order_by || (!$group_by && $order_by == "group_by")) {
$order_by=$this->idField;
}
if (!$cdr_table) $cdr_table=$this->table;
$this->url="?cdr_source=$this->cdr_source&cdr_table=$cdr_table";
if ($this->CDRTool['filter']['domain']) {
$this->url = $this->url."&Realms=".urlencode($this->CDRTool['filter']['domain']);
$Realms = explode(" ",$this->CDRTool['filter']['domain']);
} else if ($Realms) {
$this->url = $this->url."&Realms=".urlencode($Realms);
$Realms = explode(" ",$Realms);
}
if ($this->CDRTool['filter']['aNumber']) {
$this->url = $this->url."&UserName=".urlencode($this->CDRTool['filter']['aNumber']);
}
if ($order_by) {
$this->url.="&order_by=$order_by&order_type=$order_type";
}
$this->url.="&begin_datetime=$begin_datetime_url";
$this->url.="&end_datetime=$end_datetime_url";
if (!$call_id && $begin_datetime && $end_datetime) {
$where .= " ($this->startTimeField >= '$begin_datetime' and $this->startTimeField < '$end_datetime') ";
} else {
$where .= " ($this->startTimeField >= '1970-01-01' ) ";
}
if ($MONTHYEAR) {
$where .= " and $this->startTimeField like '$MONTHYEAR%' ";
$MONTHYEAR_url=urlencode($MONTHYEAR);
$this->url.="&MONTHYEAR=$MONTHYEAR_url";
}
if ($this->CDRTool['filter']['aNumber']) {
// force user to see only CDRS with his a_numbers
$where .= sprintf("
and ( %s = '%s' or %s = '%s') ",
$this->usernameField,
addslashes($this->CDRTool['filter']['aNumber']),
$this->CanonicalURIField,
addslashes($this->CDRTool['filter']['aNumber'])
);
$UserName_comp='equal';
$UserName=$this->CDRTool['filter']['aNumber'];
}
if ($UserName) {
if (!$UserName_comp) $UserName_comp='begin';
if ($UserName_comp=="begin") {
$where .= " and $this->usernameField like '".addslashes($UserName)."%'";
} elseif ($UserName_comp=="contain") {
$where .= " and $this->usernameField like '%".addslashes($UserName)."%'";
} elseif ($UserName_comp=="equal") {
$where .= " and $this->usernameField = '".addslashes($UserName)."'";
} else {
$where .= " and $this->usernameField = '' ";
}
$UserName_encoded=urlencode($UserName);
$this->url.="&UserName=$UserName_encoded&UserName_comp=$UserName_comp";
}
$a_number=trim($a_number);
if ($a_number_comp == "empty") {
$where .= " and $this->aNumberField = ''";
$this->url.="&a_number_comp=$a_number_comp";
} else if (strlen($a_number)) {
$a_number=urldecode($a_number);
if (!$a_number_comp) $a_number_comp="equal";
$a_number_encoded=urlencode($a_number);
$this->url.="&a_number=$a_number_encoded";
if ($a_number_comp=="begin") {
$where .= " and $this->aNumberField like '".addslashes($a_number)."%'";
$s=1;
} elseif ($a_number_comp=="contain") {
$where .= " and $this->aNumberField like '%".addslashes($a_number)."%'";
$s=1;
} elseif ($a_number_comp=="equal") {
$where .= " and $this->aNumberField = '".addslashes($a_number)."'";
$s=1;
}
$this->url.="&a_number_comp=$a_number_comp";
}
$Realm=trim($Realm);
if ($this->CDRTool['filter']['domain']) {
$where .= "
and (" ;
$rr=0;
foreach ($Realms as $realm) {
if ($rr) $where .= " or ";
$where .= " $this->domainField like '".addslashes($realm)."%' ";
$rr++;
}
$where .= " ) ";
} else if ($Realm) {
$Realm=urldecode($Realm);
$where .= " and $this->domainField like '".addslashes($Realm)."%' ";
$Realm_encoded=urlencode($Realm);
$this->url.="&Realm=$Realm_encoded";
} else if ($Realms) {
$where .= "
and (" ;
$rr=0;
foreach ($Realms as $realm) {
if ($rr) {
$where .= " or ";
}
$where .= " $this->domainField like '".addslashes($realm)."%' ";
$rr++;
}
$where .= " ) ";
}
$BillingId=trim($BillingId);
if (preg_match("/^\d+$/",$BillingId) && $this->BillingIdField) {
$where .= " and $this->BillingIdField = '".addslashes($BillingId)."'";
$BillingId_encoded=urlencode($BillingId);
$this->url.="&BillingId=$BillingId_encoded";
}
if ($application) {
$where .= " and $this->applicationTypeField like '%".addslashes($application)."%'";
$application_encoded=urlencode($application);
$this->url.="&application=$application_encoded";
}
if ($DestinationId) {
if ($DestinationId=="empty") {
$DestinationIdSQL="";
} else {
$DestinationIdSQL=$DestinationId;
}
$where .= " and $this->DestinationIdField = '".addslashes($DestinationIdSQL)."'";
$DestinationId_encoded=urlencode($DestinationId);
$this->url.="&DestinationId=$DestinationId_encoded";
}
if (strlen(trim($ExcludeDestinations))) {
$ExcludeDestArray=explode(" ",trim($ExcludeDestinations));
foreach ($ExcludeDestArray as $exclDst) {
if (preg_match("/^0+(\d+)$/",$exclDst,$m)) {
$exclDest_id=$m[1];
} else {
$exclDest_id=$exclDst;
}
$where .= " and ".
$this->CanonicalURIField.
" not like '".
addslashes(trim($exclDst)).
"'";
}
$ExcludeDestinations_encoded=urlencode($ExcludeDestinations);
$this->url.="&ExcludeDestinations=$ExcludeDestinations_encoded";
}
$call_id=trim($call_id);
if ($call_id) {
$call_id=urldecode($call_id);
$where .= " and $this->callIdField = '".addslashes($call_id)."'";
$call_id_encoded=urlencode($call_id);
$this->url.="&call_id=$call_id_encoded";
}
if ($sip_proxy) {
$sip_proxy=urldecode($sip_proxy);
$where .= " and $this->SipProxyServerField = '".addslashes($sip_proxy)."'";
$sip_proxy_encoded=urlencode($sip_proxy);
$this->url.="&sip_proxy=$sip_proxy_encoded";
}
if ($SipCodec) {
$SipCodec_enc=urlencode($SipCodec);
$this->url.="&SipCodec=$SipCodec_enc";
if ($SipCodec != "empty") {
$where .= " and $this->SipCodecField = '".addslashes($SipCodec)."'";
} else {
$where .= " and $this->SipCodecField = ''";
}
}
if ($SipRPID) {
$SipRPID_enc=urlencode($SipRPID);
$this->url.="&SipRPID=$SipRPID_enc";
if ($SipRPID != "empty") {
$where .= " and $this->SipRPIDField = '".addslashes($SipRPID)."'";
} else {
$where .= " and $this->SipRPIDField = ''";
}
}
if ($UserAgent) {
$where .= " and $this->UserAgentField like '%".addslashes($UserAgent)."%'";
$UserAgent_enc=urlencode($UserAgent);
$this->url.="&UserAgent=$UserAgent_enc";
}
if (strlen($SipStatus)) {
$where .= " and $this->disconnectField ='".addslashes($SipStatus)."'";
$this->url.="&SipStatus=$SipStatus";
}
if ($SipStatusClass) {
$where .= " and $this->disconnectField like '$SipStatusClass%'";
$this->url.="&SipStatusClass=$SipStatusClass";
}
if ($this->CDRTool[filter]["gateway"]) {
$gatewayFilter=$this->CDRTool[filter]["gateway"];
$where .= " and ($this->gatewayField = '".addslashes($gatewayFilter)."')";
} else if ($gateway) {
$gateway=urldecode($gateway);
$gateway_encoded=urlencode($gateway);
$where .= " and $this->gatewayField = '".addslashes($gateway)."'";
$this->url.="&gateway=$gateway_encoded";
}
$c_number=trim($c_number);
if (strlen($c_number)) {
$c_number=urldecode($c_number);
if (!$c_number_comp || $c_number_comp=="begin") {
$where .= " and $this->CanonicalURIField like '".addslashes($c_number)."%'";
} elseif ($c_number_comp=="equal") {
$where .= " and $this->CanonicalURIField = '".addslashes($c_number)."'";
} elseif ($c_number_comp=="contain") {
$where .= " and $this->CanonicalURIField like '%".addslashes($c_number)."%'";
}
$c_number_encoded=urlencode($c_number);
$this->url.="&c_number=$c_number_encoded&c_number_comp=$c_number_comp";
}
if ($duration) {
if (preg_match("/\d+/",$duration) ) {
$where .= " and ($this->durationField > 0 and $this->durationField $duration) ";
} elseif (preg_match("/onehour/",$duration) ) {
$where .= " and ($this->durationField < 3610 and $this->durationField > 3530) ";
} elseif ($duration == "zero") {
$where .= " and $this->durationField = 0";
} elseif ($duration == "zeroprice" && $this->priceField) {
$where .= " and $this->durationField > 0 and $this->priceField ='' ";
} elseif ($duration == "nonzero") {
$where .= " and $this->durationField > 0";
} elseif ($duration == "onewaymedia") {
$where .= " and (($this->inputTrafficField > 0 && $this->outputTrafficField = 0) || ($this->inputTrafficField = 0 && $this->outputTrafficField > 0)) " ;
}
$duration_enc=urlencode($duration);
$this->url.="&duration=$duration_enc";
}
if ($MediaTimeout) {
$this->url.="&MediaTimeout=1";
$where .= " and $this->MediaTimeoutField > ''";
}
$this->url.="&maxrowsperpage=$this->maxrowsperpage";
$url_calls = $this->scriptFile.$this->url."&action=search";
if ($group_by) {
$this->url.="&group_by=$group_by";
}
$this->url_edit = $this->scriptFile.$this->url."&action=edit";
$this->url_run = $this->scriptFile.$this->url."&action=search";
$this->url_export = $_SERVER["PHP_SELF"].$this->url."&action=search&export=1";
if ($duration == "unnormalized") {
$where .= " and $this->normalizedField = '0' ";
}
if ($duration == "unnormalized_duration") {
$where .= " and $this->normalizedField = '0' and $this->durationField > 0 ";
}
if ($group_by) {
$this->group_byOrig=$group_by;
if ($group_by=="hour") {
$group_by="HOUR(AcctStartTime)";
} else if (preg_match("/^DAY/",$group_by)) {
$group_by="$group_by(AcctStartTime)";
} else if (preg_match("/BYMONTH/",$group_by)) {
$group_by="DATE_FORMAT(AcctStartTime,'%Y-%m')";
} else if (preg_match("/BYYEAR/",$group_by)) {
$group_by="DATE_FORMAT(AcctStartTime,'%Y')";
} else if ($group_by=="UserAgentType") {
$group_by="SUBSTRING_INDEX($this->SipUserAgentsField, ' ', '1')";
}
$this->group_by=$group_by;
if ($group_by==$this->callIdField) {
$having=" having count($group_by) > 1 ";
}
$query= "select sum($this->durationField) as duration,
SEC_TO_TIME(sum($this->durationField)) as duration_print,
count($group_by) as calls,
$group_by
from $cdr_table
where $where
group by $group_by
$having
";
} else {
$query = "select count(*) as records
from $cdr_table where ".$where;
}
dprint($query);
if ($this->CDRdb->query($query)) {
$this->CDRdb->next_record();
if ($group_by) {
$rows = $this->CDRdb->num_rows();
} else {
$rows = $this->CDRdb->f('records');
}
} else {
printf ("%s",$this->CDRdb->Error);
$rows = 0;
}
$this->rows=$rows;
if ($this->CDRTool['filter']['aNumber']) {
$this->showResultsMenuSubscriber();
} else {
$this->showResultsMenu();
}
if (!$this->next) {
$i=0;
$this->next=0;
} else {
$i=$this->next;
}
$j=0;
$z=0;
if (!$this->export) print "<p>";
if ($rows>0) {
if ($call_id && $unnormalize) {
$query=sprintf("update %s
set %s = '0'
where %s = '%s'",
addslashes($cdr_table),
addslashes($this->normalizedField),
addslashes($this->callIdField),
addslashes($call_id)
);
$this->CDRdb->query($query);
}
if ($UnNormalizedCalls=$this->getUnNormalized($where,$cdr_table)) {
if ($UnNormalizedCalls < $this->maxCDRsNormalizeWeb) {
$this->NormalizeCDRS($where,$cdr_table);
if (!$this->export && $this->status['normalized'] ) {
printf ("%d CDRs normalized. ",$this->status['normalized']);
if ($this->status['cached_keys']['saved_keys']) {
printf ("Quota usage updated for %d accounts. ",$this->status['cached_keys']['saved_keys']);
}
}
}
}
if ($rows > $this->maxrowsperpage) {
$maxrows=$this->maxrowsperpage+$this->next;
if ($maxrows > $rows) {
$maxrows=$rows;
$prev_rows=$maxrows;
}
} else {
$maxrows=$rows;
}
if ($duration == "unnormalized") {
// if display un normalized calls we must substract
// the amount of calls normalized above
$maxrows=$maxrows-$this->status['normalized'];
}
if ($group_by) {
if ($order_by=="group_by") {
$order_by1=$group_by;
} else {
if ($order_by == $this->inputTrafficField ||
$order_by == $this->outputTrafficField ||
$order_by == $this->durationField ||
$order_by == $this->priceField ||
$order_by == "zeroP" ||
$order_by == "nonzeroP" ) {
$order_by1=$order_by;
} else {
$order_by1="calls";
}
}
$this->SipMethodField=$this->CDRFields['SipMethod'];
$query= "
select sum($this->durationField) as $this->durationField,
SEC_TO_TIME(sum($this->durationField)) as hours,
count($group_by) as calls,
$this->SipMethodField,
2*sum($this->inputTrafficField)/1024/1024 as $this->inputTrafficField,
2*sum($this->outputTrafficField)/1024/1024 as $this->outputTrafficField,
SUM($this->durationField = '0') as zero,
SUM($this->durationField > '0') as nonzero,
";
if ($order_by=="zeroP" || $order_by=="nonzeroP") {
$query.="
SUM($this->durationField = '0')/count($group_by)*100 as zeroP,
SUM($this->durationField > '0')/count($group_by)*100 as nonzeroP,
";
}
$query.="
sum($this->inputTrafficField)*8*2/1024/sum($this->durationField) as netrate_in,
sum($this->outputTrafficField)*8*2/1024/sum($this->durationField) as netrate_out
";
if ($this->priceField) {
$query.= ", sum($this->priceField) as $this->priceField ";
}
$query.= "
, $group_by as mygroup
from $cdr_table
where $where
group by $group_by
$having
order by $order_by1 $order_type
limit $i,$this->maxrowsperpage
";
$this->CDRdb->query($query);
$this->showTableHeaderStatistics($begin_datetime,$end_datetime);
while ($i<$maxrows) {
$found=$i+1;
$this->CDRdb->next_record();
$calls = $this->CDRdb->f('calls');
$seconds = $this->CDRdb->f($this->durationField);
$seconds_print = number_format($this->CDRdb->f($this->durationField),0);
$minutes = number_format($this->CDRdb->f($this->durationField)/60,0,"","");
$minutes_print = number_format($this->CDRdb->f($this->durationField)/60,0);
$hours = $this->CDRdb->f('hours');
$AcctInputOctets = number_format($this->CDRdb->f($this->inputTrafficField),2,".","");
$AcctOutputOctets = number_format($this->CDRdb->f($this->outputTrafficField),2,".","");
$NetRateIn = $this->CDRdb->f('netrate_in');
$NetRateOut = $this->CDRdb->f('netrate_out');
$SipMethod = $this->CDRdb->f($this->callTypeField);
$AcctTerminateCause = $this->CDRdb->f($this->disconnectField);
$mygroup = $this->CDRdb->f('mygroup');
$zero = $this->CDRdb->f('zero');
$nonzero = $this->CDRdb->f('nonzero');
$success = number_format($nonzero/$calls*100,2,".","");
$failure = number_format($zero/$calls*100,2,".","");
$NetworkRateIn = number_format($NetRateIn,2);
$NetworkRateOut = number_format($NetRateOut,2);
$NetworkRate = max($NetworkRateIn,$NetworkRateOut);
if ($this->priceField) {
$price = $this->CDRdb->f($this->priceField);
}
$rr=floor($found/2);
$mod=$found-$rr*2;
if ($mod ==0) {
$inout_color="lightgrey";
} else {
$inout_color="white";
}
$traceValue="";
if ($this->group_byOrig==$this->DestinationIdField) {
if ($this->CDRTool['filter']['domain'] && $this->destinations[$this->CDRTool['filter']['domain']]) {
list($_dst_id,$_dst_name)=$this->getPSTNDestinationId($mygroup,'',$this->CDRTool['filter']['domain']);
$description=$_dst_name;
} else {
$description=$this->destinations["default"][$mygroup];
//list($_dst_id,$_dst_name)=$this->getPSTNDestinationId($mygroup);
//$description=$_dst_name;
}
$mygroup_print=$mygroup;
if ($mygroup) {
$traceValue=$mygroup;
} else {
$traceValue="empty";
}
} else if ($this->group_byOrig==$this->aNumberField) {
# Normalize Called Station Id
$N=$this->NormalizeNumber($mygroup);
$mygroup_print=$N['username']."@".$N[domain];
$description="";
$traceField="a_number";
$traceValue=urlencode($mygroup);
} else if ($this->group_byOrig==$this->cNumberField) {
$traceField="c_number";
$traceValue=urlencode($mygroup);
} else if ($this->group_byOrig==$this->SipProxyServerField) {
$traceField="sip_proxy";
$traceValue=urlencode($mygroup);
$mygroup_print = $mygroup;
} else if ($this->group_byOrig==$this->SipCodecField) {
$traceField="SipCodec";
$mygroup_print = $mygroup;
} else if (preg_match("/UserAgent/",$this->group_byOrig)) {
$traceField="UserAgent";
$mygroup_print = $mygroup;
} else if (preg_match("/^BY/",$this->group_byOrig)) {
$traceField="MONTHYEAR";
$mygroup_print = $mygroup;
} else if ($this->group_byOrig==$this->callIdField) {
$traceField="call_id";
} else if ($this->group_byOrig=="DAYOFWEEK") {
if ($mygroup == "1") {
$description="Sunday";
} else if ($mygroup == "2") {
$description="Monday";
} else if ($mygroup == "3") {
$description="Tuesday";
} else if ($mygroup == "4") {
$description="Wednesday";
} else if ($mygroup == "5") {
$description="Thursday";
} else if ($mygroup == "6") {
$description="Friday";
} else if ($mygroup == "7") {
$description="Saturday";
}
$mygroup_print=$mygroup;
} else if ($this->group_byOrig=="DAYOFMONTH") {
$description =$this->CDRdb->f('day');
$mygroup_print = $mygroup;
} else if ($this->group_byOrig=="DAYOFYEAR") {
$description =$this->CDRdb->f('day');
$mygroup_print = $mygroup;
} else if ($this->group_byOrig=="SourceIP") {
$traceField="gateway";
$mygroup_print = $mygroup;
} else if ($this->group_byOrig=="SipResponseCode") {
$description =$this->disconnectCodesDescription[$mygroup];
$mygroup_print = $mygroup;
$traceField="SipStatus";
} else if ($this->group_byOrig=="SipApplicationType") {
$mygroup_print = $mygroup;
$traceField="application";
} else {
$description = "";
$mygroup_print = $mygroup;
}
$mygroup_print=quoted_printable_decode($mygroup_print);
if (!$traceField) {
$traceField = $group_by;
}
if (!$traceValue) {
$traceValue = $mygroup;
}
if (!$traceValue) {
$traceValue="empty";
}
$traceValue_enc=urlencode($traceValue);
if (!$this->export) {
print "
<tr bgcolor=$inout_color>
<td><b>$found</b></td>
<td align=right>$calls</td>
<td align=right>$seconds_print</td>
<td align=right>$minutes_print</td>
<td align=right>$hours</td>
";
if ($perm->have_perm("showPrice")) {
$pricePrint=number_format($price,4,".","");
} else {
$pricePrint='x.xxx';
}
print "
<td align=right>$pricePrint</td>
<td align=right>$AcctInputOctets</td>
<td align=right>$AcctOutputOctets</td>
<td align=right>$success%</td>
<td align=right>($nonzero calls)</td>
<td align=right>$failure%</td>
<td align=right>($zero calls)</td>
<td>$mygroup_print</td>
<td>$description</td>
<td>";
printf("<a href=%s&%s=%s&%s_comp=begin target=_new>Display calls</a></td>",$url_calls,$traceField,$traceValue_enc,$traceField);
print "
</tr>
";
} else {
print "$found,";
print "$calls,";
print "$seconds,";
print "$minutes,";
print "$hours,";
if ($perm->have_perm("showPrice")) {
$pricePrint=$price;
} else {
$pricePrint='x.xxx';
}
print "$pricePrint,";
print "$AcctInputOctets,";
print "$AcctOutputOctets,";
print "$success,";
print "$nonzero,";
print "$failure,";
print "$zero,";
print "$mygroup_print,";
print "$description";
print "\n";
}
$i++;
}
if (!$this->export) {
print "
</table>
</td>
</tr>
</table>
";
}
} else {
if (!$this->export) {
printf ("For more information about each call click on its Id column. ");
}
if ($order_by=="zeroP" || $order_by=="nonzeroP") {
$order_by="timestamp";
}
$query="select *, UNIX_TIMESTAMP($this->startTimeField) as timestamp
from $cdr_table where ".
$where.
" order by $order_by $order_type ".
" limit $i,$this->maxrowsperpage";
$this->CDRdb->query($query);
if ($this->CDRTool['filter']['aNumber']) {
$this->showTableHeaderSubscriber($begin_datetime,$end_datetime);
} else {
if (!$this->export) {
$this->showTableHeader($begin_datetime,$end_datetime);
} else {
$this->showExportHeader();
}
}
while ($i<$maxrows) {
global $found;
$found=$i+1;
$this->CDRdb->next_record();
$Structure=$this->_readCDRFieldsFromDB();
$CDR = new $this->CDR_class($this, $Structure);
if ($this->CDRTool['filter']['aNumber']) {
$CDR->showSubscriber();
} else {
if (!$this->export) {
$CDR->show();
} else {
$CDR->export();
}
}
$i++;
}
if (!$this->export) {
print "
</table>
</td>
</tr>
</table>
";
}
}
$this->showPagination($this->next,$maxrows);
}
}
function LoadDomains() {
if (!$this->db_subscribers) {
$log=printf("Error: Cannot load domains because db_subscribers is not defined in datasource %s",$this->cdr_source);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
if (!is_object($this->AccountsDB)) {
$log=printf("Error: AccountsDB is not a valid database object");
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
if (strlen($this->DATASOURCES[$this->cdr_source]['enableThor'])) {
$this->domain_table = "sip_domains";
} else {
$this->domain_table = "domain";
}
$query=sprintf("select * from %s",$this->domain_table);
if ($this->CDRTool['filter']['aNumber']) {
$els=explode("@",$this->CDRTool['filter']['aNumber']);
$query.=" where domain = '$els[1]' ";
} else if ($this->CDRTool['filter']['domain']) {
$fdomain=$this->CDRTool['filter']['domain'];
$query.=" where domain = '$fdomain' ";
}
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database %s error: %s (%d) %s\n",$this->db_subscribers,$this->AccountsDB->Error,$this->AccountsDB->Errno,$query);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
while($this->AccountsDB->next_record()) {
if ($this->AccountsDB->f('domain')) {
$this->localDomains[$this->AccountsDB->f('domain')]=array('name' => $this->AccountsDB->f('domain'),
'reseller' => intval($this->AccountsDB->f('reseller_id'))
);
}
}
return count($this->localDomains);
}
function LoadTrustedPeers() {
if (!$this->db_subscribers) {
$log=printf("Error: Cannot load trusted peers because db_subscribers is not defined in datasource %s",$this->cdr_source);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
if (!is_object($this->AccountsDB)) {
$log=printf("Error: AccountsDB is not a valid database object");
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
if (strlen($this->DATASOURCES[$this->cdr_source]['enableThor'])) {
$this->trusted_table = "sip_trusted";
} else {
$this->trusted_table = "trusted";
}
$query=sprintf("select * from %s",$this->trusted_table);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database %s error: %s (%d) %s\n",$this->db_subscribers,$this->AccountsDB->Error,$this->AccountsDB->Errno,$query);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
while($this->AccountsDB->next_record()) {
if ($this->AccountsDB->f('src_ip')) {
$this->trustedPeers[$this->AccountsDB->f('src_ip')]=array('ip' => $this->AccountsDB->f('src_ip'),
'reseller' => intval($this->AccountsDB->f('reseller_id'))
);
}
}
return count($this->trustedPeers);
}
function getQuota($account) {
if (!$this->quotaEnabled) return true;
if (!$account) return;
if (!is_object($this->AccountsDB)) {
$log=printf("Error: AccountsDB is not a valid database object");
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
list($username,$domain) = explode("@",$account);
if ($this->enableThor) {
$query=sprintf("select * from sip_accounts where username = '%s' and domain = '%s'",$username,$domain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query 1 %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
$this->AccountsDB->next_record();
$_profile=json_decode(trim($this->AccountsDB->f('profile')));
return $_profile->quota;
} else {
return 0;
}
} else {
$query=sprintf("select quota from subscriber where username = '%s' and domain = '%s'",$username,$domain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
$this->AccountsDB->next_record();
return $this->AccountsDB->f('quota');
} else {
return 0;
}
}
}
function getBlockedByQuotaStatus($account) {
if (!$this->quotaEnabled) return true;
if (!$account) return 0;
if (!is_object($this->AccountsDB)) {
$log=printf("Error: AccountsDB is not a valid database object");
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
list($username,$domain) = explode("@",$account);
if ($this->enableThor) {
$query=sprintf("select * from sip_accounts where username = '%s' and domain = '%s'",$username,$domain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query2 %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
$this->AccountsDB->next_record();
$_profile=json_decode(trim($this->AccountsDB->f('profile')));
if (in_array('quota',$_profile->groups)) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
} else {
$query=sprintf("select CONCAT(username,'@',domain) as account from grp where grp = 'quota' and username = '%s' and domain = '%s'",$username,$domain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
return 1;
} else {
return 0;
}
}
return 0;
}
function notifyLastSessions($count='200',$account='') {
// send emails with last missed and received sessions to subscribers in group $this->missed_calls_group
$lockName=sprintf("%s:notifySessions",$this->cdr_source);
if (!$this->getNormalizeLock($lockName)) {
return true;
}
if (strlen($account)) {
list($username,$domain)=explode('@',$account);
if (!strlen($username) || !strlen($domain)) return false;
} else {
$query=sprintf("select * from memcache where `key` = '%s'",'notifySessionsLastRun');
$this->cdrtool->query($query);
if ($this->cdrtool->num_rows()) {
$this->cdrtool->next_record();
$lastRun=$this->cdrtool->f('value');
if (Date('Y-m-d') == $lastRun) {
$log=sprintf("Notify sessions script already run for date %s\n",$lastRun);
print $log;
syslog(LOG_NOTICE,$log);
return true;
}
}
}
$this->notifySubscribers=array();
require_once('Mail.php');
require_once('Mail/mime.php');
if ($this->enableThor) {
$query=sprintf("select * from sip_accounts");
if (strlen($account)) {
$query.= sprintf (" where username = '%s' and domain = '%s' ",$username,$domain);
}
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
while ($this->AccountsDB->next_record()) {
$_profile=json_decode(trim($this->AccountsDB->f('profile')));
if (in_array($this->missed_calls_group,$_profile->groups)) {
$this->notifySubscribers[$this->AccountsDB->f('username').'@'.$this->AccountsDB->f('domain')]=array('email'=>$this->AccountsDB->f('email'));
}
}
} else {
return 0;
}
} else {
$query=sprintf("select CONCAT(username,'@',domain) as account from grp where grp = '%s'",$this->missed_calls_group);
if (strlen($account)) {
$query.= sprintf (" and username = '%s' and domain = '%s' ",$username,$domain);
}
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
while ($this->AccountsDB->next_record()) {
$this->notifySubscribers[$this->AccountsDB->f('account')]=array('email'=>$this->AccountsDB->f('email'));
}
} else {
return 0;
}
}
if (!count($this->notifySubscribers)) return 0;
$j=0;
foreach (array_keys($this->notifySubscribers) as $_subscriber) {
$j++;
$_last_sessions=array();
unset($textBody);
unset($htmlBody);
$query = sprintf("SELECT * FROM %s where (%s = '%s' or %s = '%s') and %s > DATE_ADD(NOW(), INTERVAL -1 day) order by %s desc limit 200",
$this->table,
$this->usernameField,
$_subscriber,
$this->CanonicalURIField,
$_subscriber,
$this->startTimeField,
$this->startTimeField);
if (!$this->CDRdb->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->CDRdb->Error,$this->CDRdb->Errno);
syslog(LOG_NOTICE,$log);
print $log;
return 0;
}
if (Date('d')== 1) {
while ($this->CDRdb->next_record()) {
$_last_sessions[]=array('duration' => $this->CDRdb->f($this->durationField),
'from' => $this->CDRdb->f($this->aNumberField),
'to' => $this->CDRdb->f($this->cNumberField),
'username' => $this->CDRdb->f($this->usernameField),
'canonical' => $this->CDRdb->f($this->CanonicalURIField),
'date' => $this->CDRdb->f($this->startTimeField)
);
}
if (preg_match("/^(\w+)(\d{4})(\d{2})$/",$this->table,$m)) {
$previousTable=$m[1].date('Ym', mktime(0, 0, 0, $m[3]-1, "01", $m[2]));
$query = sprintf("SELECT * FROM %s where %s = '%s' and %s > DATE_ADD(NOW(), INTERVAL -1 day) order by %s desc limit 200",
$previousTable,
$this->CanonicalURIField,
$_subscriber,
$this->startTimeField,
$this->startTimeField);
if (!$this->CDRdb->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->CDRdb->Error,$this->CDRdb->Errno);
syslog(LOG_NOTICE,$log);
print $log;
return 0;
}
while ($this->CDRdb->next_record()) {
$_last_sessions[]=array('duration' => $this->CDRdb->f($this->durationField),
'from' => $this->CDRdb->f($this->aNumberField),
'to' => $this->CDRdb->f($this->cNumberField),
'username' => $this->CDRdb->f($this->usernameField),
'canonical' => $this->CDRdb->f($this->CanonicalURIField),
'date' => $this->CDRdb->f($this->startTimeField)
);
}
}
} else {
while ($this->CDRdb->next_record()) {
$_last_sessions[]=array('duration' => $this->CDRdb->f($this->durationField),
'from' => $this->CDRdb->f($this->aNumberField),
'to' => $this->CDRdb->f($this->cNumberField),
'username' => $this->CDRdb->f($this->usernameField),
'canonical' => $this->CDRdb->f($this->CanonicalURIField),
'date' => $this->CDRdb->f($this->startTimeField)
);
}
}
if (!count($_last_sessions)) continue;
$sessions=array('missed' => array(),
'received' => array(),
'diverted' => array()
);
$have_sessions=0;
foreach ($_last_sessions as $_s) {
if ($_s['duration'] == 0 && $_s['canonical'] == $_subscriber) {
$sessions['missed'][]=$_s;
$have_sessions++;
continue;
}
if ($_s['duration'] > 0 && $_s['canonical'] == $_subscriber) {
$sessions['received'][]=$_s;
$have_sessions++;
continue;
}
if ($_s['from'] != $_subscriber && $_s['canonical'] != $_subscriber) {
$sessions['diverted'][]=$_s;
$have_sessions++;
continue;
}
}
if (!$have_sessions) continue;;
if (count($sessions['missed'])) {
// missed sessions
$textBody .= sprintf ("Missed sessions\n\n
Id,Date,From,Duration\n
");
$htmlBody .= sprintf ("<h2>Missed sessions</h2>
<p>
<table border=1>
<tr>
<th>
</th>
<th>Date and time
</th>
<th>Caller address
</th>
</tr>
");
$i=0;
foreach ($sessions['missed'] as $_session) {
$i++;
if ($i >= $count) break;
$htmlBody.=sprintf ("<tr><td>%s</td><td>%s</td><td><a href=sip:%s>sip:%s</a></td></tr>",
$i,
$_session['date'],
$_session['from'],
$_session['from']
);
$txtBody.=sprintf ("%s,%s,%s,%s,%s\n",
$i,
$_session['date'],
$_session['from'],
$_session['to']
);
}
$htmlBody.="</table>";
}
if (count($sessions['diverted'])) {
// diverted sessions
$textBody .= sprintf ("Diverted sessions\n\n
Id,Date,From,Diverted to\n
");
$htmlBody .= sprintf ("<h2>Diverted sessions</h2>
<p>
<table border=1>
<tr>
<th>
</th>
<th>Date and time
</th>
<th>Caller address
</th>
<th>Diverted to
</th>
</tr>
");
$i=0;
foreach ($sessions['diverted'] as $_session) {
$i++;
if ($i >= $count) break;
$htmlBody.=sprintf ("<tr><td>%s</td><td>%s</td><td><a href=sip:%s>sip:%s</a></td><td>%s</td></tr>",
$i,
$_session['date'],
$_session['from'],
$_session['from'],
$_session['canonical']
);
$txtBody.=sprintf ("%s,%s,%s,%s\n",
$i,
$_session['date'],
$_session['from'],
$_session['canonical']
);
}
$htmlBody.="</table>";
}
if (count($sessions['received'])) {
// received sessions
$textBody .= sprintf ("Received sessions\n\n
Id,Date,From,Duration\n");
$htmlBody .= sprintf ("<h2>Received sessions</h2>
<p>
<table border=1>
<tr>
<th>
</th>
<th>Date and time
</th>
<th>Caller address
</th>
<th>Duration
</th>
</tr>
");
$i=1;
foreach ($sessions['received'] as $_session) {
if ($i >= $count) break;
$htmlBody.=sprintf ("<tr><td>%s</td><td>%s</td><td><a href=sip:%s>sip:%s</a></td><td>%s</td></tr>",
$i,
$_session['date'],
$_session['from'],
$_session['from'],
$_session['duration']
);
$txtBody.=sprintf ("%s,%s,%s,%s\n",
$i,
$_session['date'],
$_session['from'],
$_session['duration']
);
$i++;
}
$htmlBody.="</table>";
}
$htmlBody.="<p>This is an automatically generated message, do not reply.";
$txtBody.="\nThis is an automatically generated message, do not reply.\n";
$crlf = "\n";
$hdrs = array(
'From'=> $this->CDRTool['provider']['fromEmail'],
'Subject' => sprintf("%s: incoming SIP sessions on %s",$_subscriber,date('Y-m-d'))
);
$mime = new Mail_mime($crlf);
$mime->setTXTBody($textBody);
$mime->setHTMLBody($htmlBody);
$body = $mime->get();
$hdrs = $mime->headers($hdrs);
$mail =& Mail::factory('mail');
$mail->send($this->notifySubscribers[$_subscriber]['email'], $hdrs, $body);
$log=sprintf("Notify %s at %s with last %d sessions\n",
$_subscriber,
$this->notifySubscribers[$_subscriber]['email'],
count($_last_sessions));
print $log;
syslog(LOG_NOTICE,$log);
}
$query=sprintf("update memcache set `value` = '%s' where `key` = '%s'",Date('Y-m-d'),'notifySessionsLastRun');
if (!$this->cdrtool->query($query)) {
$log=sprintf("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
if (!$this->cdrtool->affected_rows()) {
$query=sprintf("insert into memcache (`value`,`key`) values ('%s','%s')",Date('Y-m-d'),'notifySessionsLastRun');
if (!$this->cdrtool->query($query)) {
if ($this->cdrtool->Errno != 1062) {
$log=sprintf("Database error for query %s: %s (%s)",$query,$this->cdrtool->Error,$this->cdrtool->Errno);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
}
}
}
}
class CDR_opensips extends CDR {
var $show_in_icon=0;
var $show_out_icon=0;
var $QoSParameters=array(
"PS"=>"Audio packets sent",
"OS"=>"Audio octets sent",
"SP"=>"Comfort noise packets sent",
"SO"=>"Silence octets sent",
"PR"=>"Audio packets received",
"OR"=>"Audio octets received",
"CR"=>"Comfort noise packets received",
"SR"=>"Comfort noise octets received",
"PL"=>"Receive packets lost",
"BL"=>"Receive maximum burst packets lost",
"EN"=>"Encoder1, encoder 2",
"DE"=>"Decoder1, decoder 2",
"JI"=>"Jitter in ms"
);
function CDR_opensips(&$parent, $CDRfields) {
$this->CDRS = &$parent;
$this->cdr_source = $this->CDRS->cdr_source;
foreach (array_keys($this->CDRS->CDRFields) as $field) {
$this->$field = $CDRfields[$this->CDRS->CDRFields[$field]];
}
if ($this->CanonicalURI) {
$this->CanonicalURI = quoted_printable_decode($this->CanonicalURI);
}
if ($this->RemoteAddress) {
$this->RemoteAddress = quoted_printable_decode($this->RemoteAddress);
}
if ($this->BillingPartyId) {
$this->BillingPartyId = quoted_printable_decode($this->BillingPartyId);
}
if ($this->aNumber) {
$this->aNumber = quoted_printable_decode($this->aNumber);
}
if ($this->cNumber) {
$this->cNumber = quoted_printable_decode($this->cNumber);
}
if (!$this->applicationType && $this->SipMethod) {
$_method=strtolower($this->SipMethod);
- if ($_method=="message") {
- $this->applicationType="message";
+ if ($_method == 'message') {
+ $this->applicationType = 'message';
+ $this->stopTimeNormalized=$this->startTime;
} else {
- $this->applicationType="audio";
+ $this->applicationType = 'audio';
}
}
+ if ($this->applicationType == 'message') {
+ $this->stopTimeNormalized=$this->startTime;
+ }
+
$this->applicationType=strtolower($this->applicationType);
$this->applicationType_print=quoted_printable_decode($this->applicationType);
$this->FromHeaderPrint = quoted_printable_decode($this->FromHeader);
if (strstr($this->FromHeaderPrint,';')) {
$_els=explode(";",$this->FromHeaderPrint);
$this->FromHeaderPrint=$_els[0];
}
$this->FromHeaderPrint = htmlentities($this->FromHeaderPrint);
$this->UserAgentPrint = quoted_printable_decode($this->UserAgent);
if (strstr($this->applicationType,'audio')) {
$this->applicationType='audio';
}
if (!in_array($this->applicationType,$this->supportedApplicationTypes)) {
$this->applicationType = $this->defaultApplicationType;
}
//$this->applicationTypeNormalized=$this->applicationType;
if ($this->aNumber) {
$NormalizedNumber = $this->CDRS->NormalizeNumber($this->aNumber,"source");
$this->aNumberPrint = $NormalizedNumber['NumberPrint'];
$this->aNumberNormalized = $NormalizedNumber['Normalized'];
$this->aNumberDomain = $NormalizedNumber['domain'];
}
if (!$this->BillingPartyId || $this->BillingPartyId == 'n/a') {
$this->BillingPartyId=$this->aNumberPrint;
}
// calculate reseller either from trusted ip or domain
$_els=explode("@",$this->BillingPartyId);
if (count($_els)==2) {
if (!$this->domain) $this->domain=$_els[1];
$this->ResellerId=$this->CDRS->localDomains[$_els[1]]['reseller'];
} else if (count($_els)==1) {
$this->ResellerId=$this->CDRS->trustedPeers[$_els[0]]['reseller'];
}
$this->BillingPartyId=strtolower($this->BillingPartyId);
$this->BillingPartyIdPrint=$this->BillingPartyId;
$this->domainNormalized = $this->domain;
if (is_array($this->CDRS->DATASOURCES[$this->cdr_source]['domainTranslation_SourceIP']) &&
in_array($this->SourceIP,array_keys($this->CDRS->DATASOURCES[$this->cdr_source]['domainTranslation_SourceIP'])) &&
strlen($this->CDRS->DATASOURCES[$this->cdr_source]['domainTranslation_SourceIP'][$this->SourceIP])) {
$this->domainNormalized=$this->CDRS->DATASOURCES[$this->cdr_source]['domainTranslation_SourceIP'][$this->SourceIP];
} else if (is_array($this->CDRS->DATASOURCES[$this->cdr_source]['domainTranslation']) &&
in_array($this->domain,array_keys($this->CDRS->DATASOURCES[$this->cdr_source]['domainTranslation'])) &&
strlen($this->CDRS->DATASOURCES[$this->cdr_source]['domainTranslation'][$this->domain])) {
$this->domainNormalized=$this->CDRS->DATASOURCES[$this->cdr_source]['domainTranslation'][$this->domain];
}
$this->domainNormalized=strtolower($this->domainNormalized);
$this->RemoteAddressPrint=quoted_printable_decode($this->RemoteAddress);
$this->SipRPIDPrint=quoted_printable_decode($this->SipRPID);
$_timestamp_stop=$this->timestamp+$this->duration;
$this->dayofweek = date("w",$this->timestamp);
$this->hourofday = date("G",$this->timestamp);
$this->dayofyear = date("Y-m-d",$this->timestamp);
// Called Station ID or cNumber should not be used for rating purposes because
// it is chosen by the subscriber but the Proxy rewrites it into a different
// final destination (the Canonical URI)
// Canonical URI is the final logical SIP destination after all
// lookups like aliases, usrloc , call forwarding, ENUM
// mappings or PSTN gateways but before the DNS lookup
// Canonical URI must be saved in the SIP Proxy and added as an extra
// Radius attribute in the Radius START packet
if (!$this->CanonicalURI) {
if ($this->RemoteAddress) {
$this->CanonicalURI=$this->RemoteAddress;
} else if ($this->cNumber) {
$this->CanonicalURI=$this->cNumber;
}
}
if ($this->CanonicalURI) {
$this->CanonicalURIPrint = $this->CanonicalURI;
$NormalizedNumber = $this->CDRS->NormalizeNumber($this->CanonicalURI,"destination",$this->BillingPartyId,$this->domain,$this->gateway,'',$this->ENUMtld,$this->ResellerId);
$this->CanonicalURINormalized = $NormalizedNumber['Normalized'];
$this->CanonicalURIUsername = $NormalizedNumber['username'];
$this->CanonicalURIDomain = $NormalizedNumber['domain'];
$this->CanonicalURIPrint = $NormalizedNumber['NumberPrint'];
$this->CanonicalURIDelimiter = $NormalizedNumber['delimiter'];
// Destination Id is used for rating purposes
$this->DestinationId = $NormalizedNumber['DestinationId'];
$this->destinationName = $NormalizedNumber['destinationName'];
}
if ($this->cNumber) {
$NormalizedNumber = $this->CDRS->NormalizeNumber($this->cNumber,"destination",$this->BillingPartyId,$this->domain,$this->gateway,'',$this->ENUMtld,$this->ResellerId);
$this->cNumberNormalized = $NormalizedNumber['Normalized'];
$this->cNumberUsername = $NormalizedNumber['username'];
$this->cNumberDomain = $NormalizedNumber['domain'];
$this->cNumberPrint = $NormalizedNumber['username'].$NormalizedNumber['delimiter'].$NormalizedNumber['domain'];
$this->cNumberDelimiter = $NormalizedNumber['delimiter'];
}
if ($this->RemoteAddress) {
// Next hop is the real destination after all lookups including DNS
$NormalizedNumber = $this->CDRS->NormalizeNumber($this->RemoteAddress,"destination",$this->BillingPartyId,$this->domain,$this->gateway,'',$this->ENUMtld,$this->ResellerId);
$this->RemoteAddressPrint = $NormalizedNumber['NumberPrint'];
$this->RemoteAddressNormalized = $NormalizedNumber['Normalized'];
$this->RemoteAddressDestinationId = $NormalizedNumber['DestinationId'];
$this->RemoteAddressDestinationName = $NormalizedNumber['destinationName'];
$this->RemoteAddressUsername = $NormalizedNumber['username'];
$this->RemoteAddressDelimiter = $NormalizedNumber['delimiter'];
$this->remoteGateway = $NormalizedNumber['domain'];
$this->remoteUsername = $NormalizedNumber['username'];
}
if ($this->applicationType=="presence") {
$this->destinationPrint = $this->cNumberUsername.$this->cNumberDelimiter.$this->cNumberDomain;
$this->DestinationForRating = $this->cNumberNormalized;
} else {
if (!$this->DestinationId) {
if ($this->CanonicalURIDomain) {
$this->destinationPrint = $this->CanonicalURIUsername.$this->CanonicalURIDelimiter.$this->CanonicalURIDomain;
} else {
$this->destinationPrint = $this->cNumberUsername.$this->cNumberDelimiter.$this->cNumberDomain;
}
if (strstr($this->CanonicalURINormalized,'@')) {
$this->DestinationForRating = $this->CanonicalURINormalized;
} else {
$this->DestinationForRating = $this->RemoteAddressNormalized;
}
} else {
$this->DestinationForRating = $this->CanonicalURINormalized;
$this->destinationPrint = $this->CanonicalURIPrint;
}
}
if ($this->inputTraffic) {
$this->inputTrafficPrint = number_format($this->inputTraffic/1024,2);
}
if ($this->outputTraffic) {
$this->outputTrafficPrint = number_format($this->outputTraffic/1024,2);
}
if (!$CDRfields['skip_fix_prepaid_duration']) {
if (!$this->normalized && $this->callId) {
// fix the duration of prepaid sessions if the prepaid duration is different than radius calculated duration
$query=sprintf("select duration from prepaid_history
where session = '%s'
and destination = '%s'
order by id desc limit 1",
$this->callId,
$this->destinationPrint // must be synced with maxsession time
);
if ($this->CDRS->cdrtool->query($query)) {
if ($this->CDRS->cdrtool->num_rows()) {
$this->CDRS->cdrtool->next_record();
$this->durationNormalized = $this->CDRS->cdrtool->f('duration');
$this->durationPrint = sec2hms($this->durationNormalized);
} else {
$this->durationPrint = sec2hms($this->duration);
}
} else {
$log=sprintf("Database error for query %s: %s (%s)",$query,$this->CDRS->cdrtool->Error,$this->CDRS->cdrtool->Errno);
syslog(LOG_NOTICE,$log);
}
} else {
$this->durationPrint = sec2hms($this->duration);
}
} else {
$this->durationPrint = sec2hms($this->duration);
}
if ($this->disconnect) {
$this->disconnectPrint = $this->NormalizeDisconnect($this->disconnect);
}
$this->NetworkRateDictionary=array(
'callId' => $this->callId,
'Timestamp' => $this->timestamp,
'Duration' => $this->duration,
'inputTraffic' => $this->inputTraffic,
'outputTraffic' => $this->outputTraffic,
'DestinationId' => $this->DestinationId,
'From' => $this->BillingPartyId,
'To' => $this->DestinationForRating,
'Domain' => $this->domain,
'Gateway' => $this->gateway,
'Application' => $this->applicationType,
'ENUMtld' => $this->ENUMtld,
'ResellerId' => $this->ResellerId
);
$this->traceIn();
$this->traceOut();
$this->obfuscateCallerId();
//$this->isCalleeLocal();
$this->isCallerLocal();
if ($this->CDRS->rating) {
global $perm;
if (is_object($perm) && $perm->have_perm("showPrice")) {
$this->pricePrint=$this->price;
} else {
$this->pricePrint='x.xxx';
}
}
}
function buildCDRdetail() {
global $perm;
global $found;
if (!is_object($perm)) return;
$this->geo_location=$this->lookupGeoLocation($this->SourceIP);
$this->cdr_details="
<table border=0 bgcolor=#CCDDFF class=extrainfo id=row$found cellpadding=0 cellspacing=0>
<tr>
<td valign=top>
<table border=0 cellpadding=0 cellspacing=0>
<tr>
<td colspan=3><b>SIP Signalling</b></td>
</tr>
";
$this->cdr_details.= sprintf("
<tr>
<td width=10></td>
<td colspan=2><a href=%s&call_id=%s><font color=orange>Click here to show only this call id</font></a></td>
</tr>
",
$this->CDRS->url_run,
urlencode($this->callId)
);
if ($this->CDRS->sipTrace) {
$trace_datasource = $this->CDRS->sipTrace;
$callid_enc = urlencode(quoted_printable_decode($this->callId));
$fromtag_enc = urlencode(quoted_printable_decode($this->SipFromTag));
$totag_enc = urlencode(quoted_printable_decode($this->SipToTag));
$this->traceLink="<a href=\"javascript:void(null);\" onClick=\"return window.open('sip_trace.phtml?cdr_source=$trace_datasource&callid=$callid_enc&fromtag=$fromtag_enc&totag=$totag_enc&proxyIP=$this->SipProxyServer', 'Trace',
'toolbar=0,status=0,menubar=0,scrollbars=1,resizable=1,width=1000,height=600')\"><font color=red>Click here to see the SIP trace for this call</font></a> &nbsp;";
$this->cdr_details.= "
<tr>
<td width=10></td>
<td>Call id: </td>
<td>$this->callId</td>
</tr>
";
}
$this->cdr_details.= sprintf("
<tr>
<td width=10></td>
<td colspan=2>%s</td>
</tr>
", $this->traceLink);
$this->cdr_details.= "
<tr>
<td width=10></td>
<td>From/to tags: </td>
<td>$this->SipFromTag/$this->SipToTag</td>
</tr>
<tr>
<td></td>
<td>Start Time: </td>
<td>$this->startTime $providerTimezone</td>
</tr>
<tr>
<td></td>
<td>Stop Time: </td>
<td>$this->stopTime</td>
</tr>
";
$this->cdr_details.= sprintf("
<tr>
<td></td>
<td>Country:</td>
<td>%s</i>
</td>
</tr>
",$this->geo_location);
$this->cdr_details.= "
<tr>
<td></td>
<td>Method:</td>
<td>$this->SipMethod from <i>$this->SourceIP:$this->SourcePort</i>
</td>
</tr>
<tr>
<td></td>
<td>From:</td>
<td>$this->aNumberPrint</td>
</tr>
<tr>
<td></td>
<td>From Header:</td>
<td>$this->FromHeaderPrint</td>
</tr>
<tr>
<td></td>
<td>User Agent:</td>
<td>$this->UserAgentPrint</td>
</tr>
<tr>
<td></td>
<td>Domain:</td>
<td>$this->domain</td>
</tr>
<tr>
<td></td>
<td>To (dialed URI):</td>
<td>$this->cNumberPrint</td>
</tr>
";
if ($this->CanonicalURI) {
$this->cdr_details.= sprintf("
<tr>
<td></td>
<td>Canonical URI: </td>
<td>%s</td>
</tr>
",htmlentities($this->CanonicalURI));
}
$this->cdr_details.= sprintf("
<tr>
<td></td>
<td>Next Hop URI:</td>
<td>%s</td>
</tr>
",htmlentities($this->RemoteAddress));
if ($this->DestinationId) {
$this->cdr_details.= "
<tr>
<td></td>
<td>Destination: </td>
<td>$this->destinationName ($this->DestinationId)</td>
</tr>
";
}
if ($this->ENUMtld && $this->ENUMtld != 'none' && $this->ENUMtld != 'N/A') {
$this->cdr_details.= "
<tr>
<td></td>
<td>ENUM TLD: </td>
<td>$this->ENUMtld</td>
</tr>
";
}
if ($this->SipRPID && $this->SipRPID!='n/a') {
$this->cdr_details .= "
<tr>
<td></td>
<td>Caller ID: </td>
<td>$this->SipRPIDPrint</td>
</tr>
";
}
$this->cdr_details.= "
<tr>
<td></td>
<td>Billing Party:</td>
<td><font color=brown>$this->BillingPartyIdPrint</font></td>
</tr>
<tr>
<td></td>
<td>Reseller:</td>
<td><font color=brown>$this->ResellerId</font></td>
</tr>
</table>
</td>
<td width=30>
<td valign=top>
";
$this->cdr_details.= "
<table border=0 cellpadding=0 cellspacing=0>
<tr>
<td colspan=3><b>Media Streams</b></td>
</tr>
";
if ($this->SipCodec && $this->CDRS->mediaTrace) {
$media_trace_datasource = $this->CDRS->mediaTrace;
$this->mediaTraceLink="<a href=\"javascript:void(null);\" onClick=\"return window.open('media_trace.phtml?cdr_source=$media_trace_datasource&callid=$callid_enc&fromtag=$fromtag_enc&totag=$totag_enc&proxyIP=$this->SipProxyServer', 'Trace',
'toolbar=0,status=0,menubar=0,scrollbars=1,resizable=1,width=800,height=730')\">Click here to see the media information for this call</a> &nbsp;";
$this->cdr_details.= sprintf("
<tr>
<td width=10></td>
<td colspan=2>%s</td>
</tr>
", $this->mediaTraceLink);
}
$this->cdr_details.= "
<tr>
<td></td>
<td>Applications: </td>
<td>$this->applicationType_print</td>
</tr>
";
if ($this->SipCodec) {
$this->SipCodec = quoted_printable_decode($this->SipCodec);
$this->cdr_details.= "
<tr>
<td></td>
<td>Codecs: </td>
<td>$this->SipCodec</td>
</tr>
<tr>
<td></td>
<td>Caller RTP: </td>
<td>$this->inputTrafficPrint KB</td>
</tr>
<tr>
<td></td>
<td>Called RTP: </td>
<td>$this->outputTrafficPrint KB</td>
</tr>
";
if ($this->MediaTimeout) {
$this->cdr_details.= "
<tr>
<td></td>
<td>Media Info:</td>
<td><font color=red>$this->MediaTimeout</font></td>
</tr>
";
}
if ($this->SipUserAgents) {
$this->SipUserAgents = quoted_printable_decode($this->SipUserAgents);
$callerAgents=explode("+",$this->SipUserAgents);
$callerUA=htmlentities($callerAgents[0]);
$calledUA=htmlentities($callerAgents[1]);
$this->cdr_details.= "
<tr>
<td></td>
<td>Caller SIP UA: </td>
<td>$callerUA</td>
</tr>
<tr>
<td></td>
<td>Called SIP UA: </td>
<td>$calledUA</td>
</tr>
";
}
if (is_array($this->QoS)) {
foreach (array_keys($this->QoS) as $_key) {
if ($this->QoSParameters[$_key]) {
$_desc=$this->QoSParameters[$_key];
} else {
$_desc=$_key;
}
$this->cdr_details.=
sprintf ("<tr><td></td><td>%s</td><td>%s</td></tr>\n",
$_desc,$this->QoS[$_key]);
}
}
}
$this->cdr_details.= "
</table>";
$this->cdr_details.= "
</td>
<td width=30></td>
<td valign=top>
";
if ($perm->have_perm("showPrice") && $this->normalized) {
$this->cdr_details.= "
<table border=0 cellpadding=0 cellspacing=0>
<tr>
<td colspan=3><b>Rating</b></td>
</tr>
";
if ($this->price > 0 || $this->rate) {
$this->ratePrint=nl2br($this->rate);
$this->cdr_details.= "
<tr>
<td></td>
<td colspan=2>$this->ratePrint</td>
</tr>
";
} else {
$this->cdr_details.= "
<tr>
<td></td>
<td colspan=2>Free call</td>
</tr>
";
}
$this->cdr_details.= "
</table>
";
}
$this->cdr_details.= "
</td>
</tr>
</table>";
}
function traceIn () {
$datasource=$this->CDRS->traceInURL[$this->SourceIP];
global $DATASOURCES;
if (!$datasource || !$DATASOURCES[$datasource]) {
return;
}
$tplus = $this->timestamp+$this->duration+300;
$tmin = $this->timestamp-300;
$c_number = $this->remoteUsername;
$cdr_table = Date('Ym',time($this->timestamp));
$this->traceIn=
"<a href=callsearch.phtml".
"?cdr_source=$datasource".
"&cdr_table=$cdr_table".
"&trace=1".
"&action=search".
"&c_number=$c_number".
"&c_number_comp=begins".
"&begin_datetime=$tmin".
"&end_datetime=$tplus".
" target=bottom>".
"In".
"</a>";
}
function traceOut () {
$datasource=$this->CDRS->traceOutURL[$this->remoteGateway];
global $DATASOURCES;
if (!$datasource || !$DATASOURCES[$datasource]) {
return;
}
$tplus = $this->timestamp+$this->duration+300;
$tmin = $this->timestamp-300;
$c_number = preg_replace("/^(0+)/","",$this->remoteUsername);
$cdr_table = Date('Ym',time($this->timestamp));
$this->traceOut=
"<a href=callsearch.phtml".
"?cdr_source=$datasource".
"&cdr_table=$cdr_table".
"&trace=1".
"&action=search".
"&c_number=$c_number".
"&c_number_comp=contain".
"&begin_datetime=$tmin".
"&end_datetime=$tplus".
" target=bottom>".
"Out".
"</a>";
}
function show() {
$this->buildCDRdetail();
global $found;
global $perm;
$rr=floor($found/2);
$mod=$found-$rr*2;
if ($mod ==0) {
$inout_color="lightgrey";
} else {
$inout_color="white";
}
$this->ratePrint=nl2br($this->rate);
if ($this->CDRS->Accounts[$this->BillingPartyId]['timezone']) {
$timezone_print=$this->CDRS->Accounts[$this->BillingPartyId]['timezone'];
} else {
$timezone_print=$this->CDRS->CDRTool['provider']['timezone'];
}
$found_print=$found;
if ($this->normalized) $found_print.='N';
$providerTimezone=$this->CDRS->CDRTool['provider']['timezone'];
print "
<tr bgcolor=$inout_color>
<td valign=top onClick=\"return toggleVisibility('row$found')\"><a href=#>$found_print</a></td>
<td valign=top onClick=\"return toggleVisibility('row$found')\"><nobr>$this->startTime</nobr></td>
<td valign=top onClick=\"return toggleVisibility('row$found')\"><nobr>$this->aNumberPrint</td>
<td valign=top onClick=\"return toggleVisibility('row$found')\"><nobr>$this->geo_location</td>
<td valign=top onClick=\"return toggleVisibility('row$found')\">$this->SipProxyServer</td>
<td valign=top><nobr>$this->destinationPrint</nobr>
";
if ($this->DestinationId) {
if ($this->DestinationId != $this->CanonicalURI) {
print " ($this->destinationName $this->DestinationId)";
} else {
print " ($this->destinationName)";
}
}
print "</td>";
if (!$this->normalized){
if ($this->duration > 0 ) {
print "<td valign=top align=left colspan=4><font color=red>$this->duration(s)</a></td>";
} else {
print "<td valign=top align=left colspan=4><font color=red>in progress</a></td>";
}
} else {
print "
<td valign=top align=right>$this->durationPrint</td>
<td valign=top align=right>$this->pricePrint</td>
<td valign=top align=right>$this->inputTrafficPrint </td>
<td valign=top align=right>$this->outputTrafficPrint</td>
";
}
$SIPclass=substr($this->disconnect,0,1);
if ($SIPclass=="6") {
$status_color="<font color=red>";
} else if ($SIPclass=="5" ) {
$status_color="<font color=red>";
} else if ($SIPclass=="4" ) {
$status_color="<font color=blue>";
} else if ($SIPclass=="3" ) {
$status_color="<font color=green>";
} else if ($SIPclass=="2" ) {
$status_color="<font color=green>";
} else {
$status_color="<font color=black>";
}
print "
<td valign=top align=right>$status_color $this->disconnectPrint</font></td>
<td valign=top align=right>$this->SipCodec</td>
</tr>
<tr>
<td></td>
<td colspan=11>$this->cdr_details</td>
</tr>
";
}
function export() {
global $found;
$disconnectName = $this->CDRS->disconnectCodesDescription[$this->disconnect];
$UserAgents = explode("+",$this->SipUserAgents);
$CallingUserAgent = trim($UserAgents[0]);
$CalledUserAgent = trim($UserAgents[1]);
print "$found";
print ",$this->startTime";
print ",$this->stopTime";
print ",$this->BillingPartyIdPrint";
print ",$this->domain";
print ",$this->SipRPIDPrint";
print ",$this->aNumberPrint";
print ",$this->destinationPrint";
print ",$this->DestinationId";
print ",$this->destinationName";
print ",$this->RemoteAddressPrint";
print ",$this->CanonicalURIPrint";
print ",$this->duration";
print ",$this->price";
print ",$this->SipProxyServer";
print ",$this->inputTraffic";
print ",$this->outputTraffic";
print ",$CallingUserAgent";
print ",$CalledUserAgent";
print ",$this->disconnect";
print ",$disconnectName";
print ",$this->SipCodec";
print ",$this->applicationType";
print "\n";
}
function showSubscriber() {
$this->buildCDRdetail();
global $found;
$rr=floor($found/2);
$mod=$found-$rr*2;
if ($mod ==0) {
$inout_color="lightgrey";
} else {
$inout_color="white";
}
if (!$this->CDRS->export) {
$timezone_print=$this->CDRS->CDRTool['provider']['timezone'];
$found_print=$found;
if ($this->normalized) $found_print.='N';
print "
<tr bgcolor=$inout_color>
<td valign=top onClick=\"return toggleVisibility('row$found')\"><a href=#>$found_print</a></td>
<td valign=top onClick=\"return toggleVisibility('row$found')\"><nobr>$this->startTime $timezone_print</nobr></td>
<td valign=top><nobr>$this->aNumberPrint</nobr></td>
<td valign=top onClick=\"return toggleVisibility('row$found')\"><nobr>$this->geo_location</td>
<td valign=top onClick=\"return toggleVisibility('row$found')\">$this->SipProxyServer</td>
<td valign=top><nobr>$this->destinationPrint $this->destinationName</td>
<td valign=top align=right>$this->durationPrint</td>
";
if ($this->CDRS->rating) {
print "<td valign=top align=right>$this->pricePrint</td>";
}
print "
<td valign=top align=right>$this->inputTrafficPrint </td>
<td valign=top align=right>$this->outputTrafficPrint</td>
</tr>
";
print "
<tr>
<td></td>
<td colspan=11>$this->cdr_details</td>
</tr>
";
} else {
$disconnectName=$this->CDRS->disconnectCodesDescription[$this->disconnect];
$UserAgents=explode("+",$this->SipUserAgents);
$CallingUserAgent=trim($UserAgents[0]);
$CalledUserAgent=trim($UserAgents[1]);
print "$found,$this->startTime,$this->stopTime,$this->BillingPartyId,$this->domain,$this->aNumberPrint,$this->cNumberPrint,$this->DestinationId,$this->destinationName,$this->RemoteAddressPrint,$this->duration,$this->price,$this->SipProxyServer,$this->inputTraffic,$this->outputTraffic,$CallingUserAgent,$CalledUserAgent,$this->disconnect,$disconnectName,$this->SipCodec,$this->applicationType\n";
}
}
function isCallerLocal() {
// used by quota
if (in_array($this->aNumberDomain,array_keys($this->CDRS->localDomains))) {
$this->CallerIsLocal=1;
return true;
}
return false;
}
function isCalleeLocal() {
if ($this->CanonicalURIUsername == $this->RemoteAddressUsername &&
in_array($this->CanonicalURIDomain,array_keys($this->CDRS->localDomains)) &&
!preg_match("/^0/",$this->CanonicalURIUsername)) {
$this->CalleeIsLocal=1;
}
}
function obfuscateCallerId() {
global $obfuscateCallerId;
if ($obfuscateCallerId) {
//Caller party
$caller_els=explode("@",$this->aNumberPrint);
if (is_numeric($caller_els[0]) && strlen($caller_els[0]>3)) {
$_user=substr($caller_els[0],0,strlen($caller_els[0])-3).'xxx';
} else {
$_user='caller';
}
if (count($caller_els)== 2) {
$this->aNumberPrint=$_user.'@'.$caller_els[1];
} else {
$this->aNumberPrint=$_user;
}
//Billing party
$caller_els=explode("@",$this->BillingPartyIdPrint);
if (is_numeric($caller_els[0]) && strlen($caller_els[0]>3)) {
$_user=substr($caller_els[0],0,strlen($caller_els[0])-3).'xxx';
} else {
$_user='party';
}
$this->BillingPartyIdPrint=$_user.'@'.$caller_els[1];
// Destination
$caller_els=explode("@",$this->destinationPrint);
if (is_numeric($caller_els[0]) && strlen($caller_els[0]>3)) {
$_user=substr($caller_els[0],0,strlen($caller_els[0])-3).'xxx';
} else {
$_user='destination';
}
if (count($caller_els)== 2) {
$this->destinationPrint=$_user.'@'.$caller_els[1];
} else {
$this->destinationPrint=$_user;
}
$caller_els=explode("@",$this->cNumberPrint);
if (is_numeric($caller_els[0]) && strlen($caller_els[0]>3)) {
$_user=substr($caller_els[0],0,strlen($caller_els[0])-3).'xxx';
} else {
$_user='dialedNumber';
}
if (count($caller_els)== 2) {
$this->cNumberPrint=$_user.'@'.$caller_els[1];
} else {
$this->cNumberPrint=$_user;
}
$caller_els=explode("@",$this->RemoteAddressPrint);
if (is_numeric($caller_els[0]) && strlen($caller_els[0]>3)) {
$_user=substr($caller_els[0],0,strlen($caller_els[0])-3).'xxx';
} else {
$_user='remoteAddress';
}
if (count($caller_els)== 2) {
$this->RemoteAddressPrint=$_user.'@'.$caller_els[1];
} else {
$this->RemoteAddressPrint=$_user;
}
// Canonical URI
$caller_els=explode("@",$this->CanonicalURIPrint);
if (is_numeric($caller_els[0]) && strlen($caller_els[0]>3)) {
$_user=substr($caller_els[0],0,strlen($caller_els[0])-3).'xxx';
} else {
$_user='canonicalURI';
}
if (count($caller_els)== 2) {
$this->CanonicalURIPrint=$_user.'@'.$caller_els[1];
} else {
$this->CanonicalURIPrint=$_user;
}
if (is_numeric($this->SipRPIDPrint) && strlen($this->SipRPIDPrint) > 3) {
$this->SipRPIDPrint=substr($this->SipRPID,0,strlen($this->SipRPID)-3).'xxx';
} else {
$_user='callerId';
}
// IP address
$this->SourceIP='xxx.xxx.xxx.xxx';
}
}
}
class SIP_trace {
var $enableThor = false;
var $trace_array = array();
var $traced_ip = array();
var $SIPProxies = array();
var $mediaTrace = false;
var $thor_nodes = array();
var $hostnames = array();
function SIP_trace ($cdr_source) {
global $DATASOURCES, $auth;
$this->cdr_source = $cdr_source;
$this->cdrtool = new DB_CDRTool();
if (!is_array($DATASOURCES[$this->cdr_source])) {
$log=sprintf("Error: datasource '%s' is not defined",$this->cdr_source);
print $log;
return 0;
}
if (strlen($DATASOURCES[$this->cdr_source]['enableThor'])) {
$this->enableThor = $DATASOURCES[$this->cdr_source]['enableThor'];
}
if (strlen($DATASOURCES[$this->cdr_source]['mediaTrace'])) {
$this->mediaTrace = $DATASOURCES[$this->cdr_source]['mediaTrace'];
}
if ($this->enableThor) {
require("/etc/cdrtool/ngnpro_engines.inc");
require_once("ngnpro_soap_library.php");
if ($DATASOURCES[$this->cdr_source]['soapEngineId'] && in_array($DATASOURCES[$this->cdr_source]['soapEngineId'],array_keys($soapEngines))) {
$this->soapEngineId=$DATASOURCES[$this->cdr_source]['soapEngineId'];
$this->SOAPlogin = array(
"username" => $soapEngines[$this->soapEngineId]['username'],
"password" => $soapEngines[$this->soapEngineId]['password'],
"admin" => true
);
$this->SOAPurl=$soapEngines[$this->soapEngineId]['url'];
$this->SoapAuth = array('auth', $this->SOAPlogin , 'urn:AGProjects:NGNPro', 0, '');
// Instantiate the SOAP client
$this->soapclient = new WebService_NGNPro_SipPort($this->SOAPurl);
$this->soapclient->setOpt('curl', CURLOPT_TIMEOUT, 5);
$this->soapclient->setOpt('curl', CURLOPT_SSL_VERIFYPEER, 0);
$this->soapclient->setOpt('curl', CURLOPT_SSL_VERIFYHOST, 0);
if (is_array($soapEngines[$this->soapEngineId]['hostnames'])) {
$this->hostnames=$soapEngines[$this->soapEngineId]['hostnames'];
}
} else {
printf ("<p><font color=red>Error: soapEngineID not defined in datasource %s</font>",$this->cdr_source);
return false;
}
} else {
$this->table = $DATASOURCES[$this->cdr_source]['table'];
$db_class = $DATASOURCES[$this->cdr_source]['db_class'];
$this->purgeRecordsAfter = $DATASOURCES[$this->cdr_source]['purgeRecordsAfter'];
if (class_exists($db_class)) {
$this->db = new $db_class;
} else {
printf("<p><font color=red>Error: database class '%s' is not defined</font>",$db_class);
return false;
}
}
if (is_object($auth)) $this->isAuthorized=1;
if (is_array($DATASOURCES[$this->cdr_source]['SIPProxies'])) {
$this->SIPProxies=$DATASOURCES[$this->cdr_source]['SIPProxies'];
}
}
function isProxy($ip,$sip_proxy='') {
if (!$ip) return false;
if (!$this->enableThor) {
if (!is_array($this->SIPProxies)) {
return false;
}
if (in_array($ip,array_keys($this->SIPProxies))) {
return true;
}
} else if ($sip_proxy) {
if ($this->thor_nodes[$ip]) {
return true;
} else {
if (isThorNode($ip,$sip_proxy)) {
$this->thor_nodes[$ip]=1;
return true;
} else {
return false;
}
}
}
return false;
}
function getTrace ($proxyIP,$callid,$fromtag,$totag) {
if ($this->enableThor) {
// get trace using soap request
if (!$proxyIP || !$callid || !$fromtag) return false;
if (!is_object($this->soapclient)) {
print "Error: soap client is not defined.";
return false;
}
$this->seen_ip=array();
$filter=array('nodeIp' => $proxyIP,
'callId' => $callid,
'fromTag' => $fromtag,
'toTag' => $totag
);
$this->soapclient->addHeader($this->SoapAuth);
$result = $this->soapclient->getSipTrace($filter);
if (PEAR::isError($result)) {
$error_msg = $result->getMessage();
$error_fault = $result->getFault();
$error_code = $result->getCode();
printf("<font color=red>Error from %s: %s: %s</font>",$this->SOAPurl,$error_fault->faultstring,$error_fault->faultcode);
return false;
}
$columns=0;
$traces=json_decode($result);
$trace_array=array();
foreach ($traces as $_trace) {
if (preg_match("/^(udp|tcp|tls):(.*):(.*)$/",$_trace->to_ip,$m)) {
$toip = $m[2];
$transport = $m[1];
$toport = $m[3];
} else if (preg_match("/^(.*):(.*)$/",$_trace->to_ip,$m)) {
$toip = $m[1];
$transport = 'udp';
$toport = $m[2];
} else {
$toip = $_trace->to_ip;
$transport = 'udp';
$toport = '5060';
}
if (preg_match("/^(udp|tcp|tls):(.*):(.*)$/",$_trace->from_ip,$m)) {
$fromip = $m[2];
$fromport = $m[3];
} else if (preg_match("/^(.*):(.*)$/",$_trace->from_ip,$m)) {
$fromip = $m[1];
$fromport = $m[2];
} else {
$fromip = $_trace->from_ip;
}
if (!$this->seen_ip[$toip] && $this->isProxy($toip)) {
$this->seen_ip[$toip]++;
}
if (!$this->seen_ip[$fromip] && $this->isProxy($fromip)) {
$this->seen_ip[$fromip]++;
}
if (!$this->column[$fromip]) {
$this->column[$fromip] = $columns+1;
$this->column_port[$fromip]=$fromport;
$columns++;
}
if (!$this->column[$toip]) {
$this->column[$toip] = $columns+1;
$this->column_port[$toip]=$toport;
$columns++;
}
preg_match("/^(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)$/",$_trace->time_stamp,$m);
$timestamp = mktime($m[4],$m[5],$m[6],$m[2],$m[3],$m[1]);
$idx=$proxyIP.'_'.$_trace->id;
$trace_array[$idx]=
array (
'id' => $idx,
'direction' => $_trace->direction,
'fromip' => $fromip,
'toip' => $toip,
'fromport' => $fromport,
'toport' => $toport,
'method' => $_trace->method,
'transport' => $transport,
'date' => $_trace->time_stamp,
'status' => $_trace->status,
'timestamp' => $timestamp,
'msg' => $_trace->message,
'md5' => md5($_trace->message)
);
}
$this->trace_array=$trace_array;
$this->rows = count($this->trace_array);
} else {
// get trace from SQL
if (!is_object($this->db)) {
print "<p><font color=red>Error: no database connection defined</font>";
return false;
}
$query=sprintf("select *,UNIX_TIMESTAMP(time_stamp) as timestamp
from %s where callid = '%s' order by id asc",
$this->table,
$callid);
if (!$this->db->query($query)) {
printf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
return false;
}
$this->rows = $this->db->num_rows();
$columns = 0;
while ($this->db->next_record()) {
if (preg_match("/^(udp|tcp|tls):(.*):(.*)$/",$this->db->f('toip'),$m)) {
$toip = $m[2];
$transport = $m[1];
$toport = $m[3];
} else if (preg_match("/^(.*):(.*)$/",$this->db->f('toip'),$m)) {
$toip = $m[1];
$transport = 'udp';
$toport = $m[2];
} else {
$toip = $this->db->f('toip');
$toport = '5060';
}
if (preg_match("/^(udp|tcp|tls):(.*):(.*)$/",$this->db->f('fromip'),$m)) {
$fromip = $m[2];
$fromport = $m[3];
} else if (preg_match("/^(.*):(.*)$/",$this->db->f('fromip'),$m)) {
$fromip = $m[1];
$fromport = $m[2];
} else {
$fromip = $this->db->f('fromip');
$transport = 'udp';
$fromport = '5060';
}
if (!$this->seen_ip[$toip] && $this->isProxy($toip)) {
$this->seen_ip[$toip]++;
}
if (!$this->seen_ip[$fromip] && $this->isProxy($fromip)) {
$this->seen_ip[$fromip]++;
}
if (!$this->column[$fromip]) {
$this->column[$fromip]=$columns+1;
$this->column_port[$fromip]=$fromport;
$columns++;
}
if (!$this->column[$toip]) {
$this->column[$toip]=$columns+1;
$this->column_port[$toip]=$toport;
$columns++;
}
$this->trace_array[$this->db->f('id')]=
array (
'id' => $this->db->f('id'),
'direction' => $this->db->f('direction'),
'fromip' => $fromip,
'toip' => $toip,
'method' => $this->db->f('method'),
'fromport' => $fromport,
'toport' => $toport,
'transport' => $transport,
'date' => $this->db->f('date'),
'status' => $this->db->f('status'),
'timestamp' => $this->db->f('timestamp'),
'msg' => $this->db->f('msg'),
'md5' => md5($this->db->f('msg'))
);
}
}
}
function show($proxyIP,$callid,$fromtag,$totag) {
$action = $_REQUEST['action'];
$toggleVisibility = $_REQUEST['toggleVisibility'];
if ($action=='toggleVisibility') {
$this->togglePublicVisibility($callid,$fromtag,$toggleVisibility);
}
if ($_SERVER['HTTPS'] == "on") {
$protocolURL = "https://";
} else {
$protocolURL = "http://";
}
$this->getTrace($proxyIP,$callid,$fromtag,$totag);
if (!count($this->trace_array)) {
print "<p>
SIP trace for session id $callid is not available.";
return;
}
print "
<h1>CDRTool SIP trace</h1>
<h2>SIP session $callid $authorize</h2>
<table border=0 width=100%>
<tr>
<form method=post name=visibility>
";
if ($this->isAuthorized) {
$key="callid-".trim($callid).trim($fromtag);
$query=sprintf("select * from memcache where `key` = '%s'",addslashes($key));
$this->cdrtool->query($query);
$basicURL = $protocolURL.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
if ($this->cdrtool->num_rows()) {
$selected_toggleVisibility['public']="selected";
$fullURL=$basicURL."&public=1";
$color2="lightblue";
} else {
$selected_toggleVisibility['private']="selected";
$fullURL=$basicURL;
}
print "
<td align=left bgcolor=$color2>
This SIP trace is visible
<select name=toggleVisibility onChange=\"document.visibility.submit.disabled = true; location.href = '$basicURL&action=toggleVisibility&toggleVisibility=' + this.options[this.selectedIndex].value\">
<option $selected_toggleVisibility[public] value=1>without authorization
<option $selected_toggleVisibility[private] value=0>only to authorized users
</select>
<input type=hidden name=action value=toggleVisibility>
";
print "URLs for this trace: <a href=$fullURL>HTML</a> | <a href=$fullURL&format=text>TEXT</a></td>";
} else {
print "<td align=left>";
}
if ($this->mediaTrace) {
$this->mediaTraceLink=sprintf("<p><a href=\"javascript:void(null);\" onClick=\"return window.open('media_trace.phtml?cdr_source=%s&callid=%s&fromtag=%s&totag=%s&proxyIP=%s', 'mediatrace',
'toolbar=0,status=0,menubar=0,scrollbars=1,resizable=1,width=800,height=730')\">Click here for the media trace</a>",
$this->mediaTrace,
urlencode($callid),
urlencode($fromtag),
urlencode($totag),
$proxyIP
);
}
print "</td>
</form>
<td align=right>
Click on each trace entry to see the full message.
$this->mediaTraceLink
</td>
</tr>
</table>
";
foreach (array_keys($this->trace_array) as $key) {
if ($this->trace_array[$key]['direction'] == 'in') {
if (is_array($this->SIPProxies)) {
$thisIP=explode(":",$this->trace_array[$key]['fromip']);
if ($this->isProxy($thisIP[0],$proxyIP)) {
$this->trace_array[$key]['isProxy'] = 1;
}
}
if ($this->trace_array[$key]['fromip'] == $this->trace_array[$key]['toip']) {
$this->trace_array[$key]['arrow_align'] = "left";
$arrow_direction="loop";
} else if ($this->column[$this->trace_array[$key]['fromip']] < $this->column[$this->trace_array[$key]['toip']]) {
$arrow_direction="right";
$this->trace_array[$key]['arrow_align'] = "right";
} else {
$arrow_direction="left";
$this->trace_array[$key]['arrow_align'] = "left";
}
$this->trace_array[$key]['msg_possition'] = $this->column[$this->trace_array[$key]['toip']];
$this->trace_array[$key]['arrow_possition'] = $this->column[$this->trace_array[$key]['fromip']];
$this->trace_array[$key]['arrow_direction'] = $arrow_direction;
} else {
if ($this->trace_array[$key]['fromip'] == $this->trace_array[$key]['toip']) {
$this->trace_array[$key]['arrow_align'] = "left";
$arrow_direction="loop";
} else if ($this->column[$this->trace_array[$key]['fromip']] < $this->column[$this->trace_array[$key]['toip']]) {
$this->trace_array[$key]['arrow_align'] = "left";
$arrow_direction="right";
} else {
$this->trace_array[$key]['arrow_align'] = "right";
$arrow_direction="left";
}
$this->trace_array[$key]['msg_possition'] = $this->column[$this->trace_array[$key]['fromip']];
$this->trace_array[$key]['arrow_possition'] = $this->column[$this->trace_array[$key]['toip']];
$this->trace_array[$key]['arrow_direction'] = $arrow_direction;
}
}
//print_r($this->hostnames);
print "
<p>
<a name=#top>
<table cellpadding=2 cellspacing=0 border=0 width=100%>
<tr bgcolor=lightgrey>
<td></td>
<td align=center><b>Id</b></td>
<td align=center><b>Size</b></td>
<td align=center><b>Time</b></td>
";
foreach (array_keys($this->column) as $_key) {
$IPels=explode(":",$_key);
if ($this->hostnames[$IPels[0]]) {
$_hostname=$this->hostnames[$IPels[0]];
} else {
$_hostname=$_key;
}
print "<td align=center class=border>";
if ($proxyIP != $IPels[0] && $this->isProxy($IPels[0],$proxyIP)) {
$trace_link=sprintf("<a href=\"javascript:void(null);\" onClick=\"return window.open('sip_trace.phtml?cdr_source=%s&callid=%s&fromtag=%s&totag=%s&proxyIP=%s', 'Trace',
'toolbar=0,status=1,menubar=1,scrollbars=1,resizable=1,width=1000,height=600')\"><font color=red><b>%s:%s (Trace) </b></font></a>",
urlencode($this->cdr_source),
urlencode($callid),
urlencode($fromtag),
urlencode($totag),
$IPels[0],
$_hostname,
$this->column_port[$_key]
);
printf ("<b><font color=blue>%s</b>",$trace_link);
} else {
printf ("<b>%s</b>",$_hostname);
}
print "</td>";
}
print "</tr>";
$i=0;
foreach (array_keys($this->trace_array) as $key) {
$i++;
$id = $this->trace_array[$key]['id'];
$msg = $this->trace_array[$key]['msg'];
$fromip = $this->trace_array[$key]['fromip'];
$toip = $this->trace_array[$key]['toip'];
$date = substr($this->trace_array[$key]['date'],11);
$status = $this->trace_array[$key]['status'];
$direction = $this->trace_array[$key]['direction'];
$timestamp = $this->trace_array[$key]['timestamp'];
$method = $this->trace_array[$key]['method'];
$isProxy = $this->trace_array[$key]['isProxy'];
$transport = $this->trace_array[$key]['transport'];
$msg_possition = $this->trace_array[$key]['msg_possition'];
$arrow_possition = $this->trace_array[$key]['arrow_possition'];
$arrow_direction = $this->trace_array[$key]['arrow_direction'];
$arrow_align = $this->trace_array[$key]['arrow_align'];
$md5 = $this->trace_array[$key]['md5'];
if ($i==1) $begin_timestamp = $timestamp;
$timeline=$timestamp-$begin_timestamp;
$sip_phone_img=getImageForUserAgent($msg);
if ($seen_msg[$md5]) continue;
$SIPclass=substr($status,0,1);
if ($SIPclass=="6") {
$status_color="<font color=red>";
} else if ($SIPclass=="5" ) {
$status_color="<font color=red>";
} else if ($SIPclass=="4" ) {
$status_color="<font color=blue>";
} else if ($SIPclass=="3" ) {
$status_color="<font color=green>";
} else if ($SIPclass=="2" ) {
$status_color="<font color=green>";
} else if ($SIPclass=="1" ) {
$status_color="<font color=\"#cc6600\">";
} else {
$status_color="<font color=black>";
}
$_lines=explode("\n",$msg);
if (preg_match("/^(.*) SIP/",$_lines[0],$m)) {
$_lines[0]=$m[1];
} else if (preg_match("/^SIP\/2\.0 (.*)/",$_lines[0],$m)) {
$_lines[0]=$m[1];
}
unset($media);
unset($diversions);
$j=0;
$t=0;
foreach ($_lines as $_line) {
if (preg_match("/^(Diversion: ).*;(.*)$/",$_line,$m)) {
$diversions[]=$m[1].$m[2];
}
if (preg_match("/^c=IN \w+ ([\d|\w\.]+)/i",$_line,$m)) {
$media['ip']=$m[1];
}
if (preg_match("/^m=(\w+) (\d+) /i",$_line,$m)) {
$t++;
$media['streams'][$m[2]]=$m[1];
}
if (preg_match("/^a=alt:1/i",$_line,$m)) {
$media['streams'][$t]=$media['streams'][$t]." ICE";
}
if (preg_match("/^Cseq:\s*\d+\s*(.*)$/i",$_line,$m)) {
$status_for_method=$m[1];
}
$j++;
}
$_els=explode(";",$_lines[0]);
$cell_content = "<a name=\"packet$i\">
$status_color $_els[0]
";
if ($status) $cell_content.=" <font color=black>for ".$status_for_method."</font>";
if (is_array($diversions)) {
foreach ($diversions as $_diversion) {
$cell_content.="<br><b>$_diversion</b>";
}
}
if (is_array($media['streams'])) {
foreach (array_keys($media['streams']) as $_key) {
$_stream=$media['streams'][$_key].':'.$media['ip'].':'.$_key;
$cell_content.="<br><b>$_stream</b>";
}
}
$cell_content.="
</font>
</a>
";
print "
<tr class=border onClick=\"return toggleVisibility('row$i')\">
<td>
";
if ($timeline && !$_seen_timeline[$timeline]) {
printf ("%s+%ds </font>",$status_color,$timeline);
$_seen_timeline[$timeline]++;
}
$len=strlen($msg);
print "
</td>
<td class=bbot>$status_color$i/$this->rows&nbsp;</font></td>
<td class=bbot>$len bytes</td>
<td class=bbot><nobr>$status_color$date</nobr></font>
";
$column_current=1;
while ($column_current <= count($this->column)) {
if ($arrow_possition==$column_current) {
if ($direction=='out') {
if ($arrow_direction == 'left') {
$arrow="green_arrow_left.png";
} else if ($arrow_direction == 'right') {
$arrow="green_arrow_right.png";
} else if ($arrow_direction == 'loop'){
$arrow="LoopArrow.png";
}
} else {
if ($arrow_direction == 'left') {
$arrow="blue_arrow_left.png";
} else if ($arrow_direction == 'right') {
$arrow="blue_arrow_right.png";
} else if ($arrow_direction == 'loop'){
$arrow="LoopArrow.png";
}
}
}
if ($arrow_possition==$column_current) {
print "<td class=bbot align=$arrow_align>";
if ($arrow_direction == 'left') {
print "<img src=images/$arrow border=0>";
if (!$isProxy && $direction=='in' && $sip_phone_img && $sip_phone_img!='unknown.png')
print "<img src=images/30/$sip_phone_img border=0>";
} else {
if (!$isProxy && $direction=='in' && $sip_phone_img && $sip_phone_img!='unknown.png')
print "<img src=images/30/$sip_phone_img border=0>";
print "<img src=images/$arrow border=0>";
}
print "<br>";
if ($transport == 'tls') print "<img src=images/lock15.gif border=0> ";
if ($direction == 'in') {
printf ("%s port %d",strtoupper($transport),$this->trace_array[$key]['fromport']);
} else {
printf ("%s port %d",strtoupper($transport),$this->trace_array[$key]['toport']);
}
} else {
print "<td class=bbot>";
}
if ($msg_possition == $column_current) {
print $cell_content;
print "<br>";
if ($direction == 'in') {
printf ("%s port %d",strtoupper($transport),$this->trace_array[$key]['toport']);
} else {
printf ("%s port %d",strtoupper($transport),$this->trace_array[$key]['fromport']);
}
} else {
print "&nbsp;";
}
print "
</td>
";
$column_current++;
if ($arrow_direction=='loop') $seen_msg[$md5]++;
}
print "</tr>";
if (is_array($this->SIPProxies)) {
$IPels=explode(":",$fromip);
$justIP=$IPels[0];
foreach (array_keys($this->SIPProxies) as $localProxy) {
if ($localProxy==$justIP) {
$direction="out";
break;
}
}
}
$trace_span=count($this->column)+2;
print "
<tr>
<td></td>
<td colspan=$trace_span>
";
print "
<table class=extrainfo id=row$i width=100%>
<tr>
<td valign=top align=center class=border>
";
if ($direction == "out") {
print "
<nobr>
<h1>SIP Proxy</h1></nobr>
";
} else {
if ($sip_phone_img) print "<img src=images/$sip_phone_img border=0>";
}
if ($timeline > 0) {
printf ("<p>+%s s<br>(%s)",$timeline,sec2hms($timeline));
}
$msg=nl2br(htmlentities($msg));
print "
</td>
<td valign=top class=border colspan=2 width=100%>$status_color $msg </font></td>
</td>
</tr>
</table>
";
print "
</td>
</tr>
";
}
print "
</table>
";
}
function showText($proxyIP,$callid,$fromtag,$totag) {
$this->getTrace($proxyIP,$callid,$fromtag,$totag);
print "<pre>";
if (!count($this->trace_array)) {
print "SIP trace for session id $callid is not available.";
return false;
}
printf ("SIP trace on proxy %s for session %s\n--\n\n",$proxyIP,$callid);
foreach (array_keys($this->trace_array) as $key) {
$i++;
printf ("Packet %d at %s from %s to %s (%s)\n",
$i,
$this->trace_array[$key]['date'],
$this->trace_array[$key]['fromip'],
$this->trace_array[$key]['toip'],
$this->trace_array[$key]['direction']);
printf ("\n%s\n",htmlspecialchars($this->trace_array[$key]['msg']));
print "---\n";
}
print "</pre>";
}
function togglePublicVisibility($callid,$fromtag,$public='0') {
$key="callid-".trim($callid).trim($fromtag);
if (!$public) {
$query=sprintf("delete from memcache where `key` = '%s'",addslashes($key));
$this->cdrtool->query($query);
} else {
$query=sprintf("delete from memcache where `key` = '%s'",addslashes($key));
$this->cdrtool->query($query);
$query=sprintf("insert into memcache values ('%s','public')",addslashes($key));
$this->cdrtool->query($query);
}
}
function purgeRecords($days='') {
if ($this->enableThor) {
return true;
}
$b=time();
if ($days) {
$this->purgeRecordsAfter=$days;
} else if (!$this->purgeRecordsAfter) {
$this->purgeRecordsAfter=15;
}
$beforeDate=Date("Y-m-d", time()-$this->purgeRecordsAfter*3600*24);
$query=sprintf("select id as min, time_stamp from %s order by id ASC limit 1",
$this->table);
if ($this->db->query($query)) {
if ($this->db->num_rows()) {
$this->db->next_record();
$min=$this->db->f('min');
$begindate=$this->db->f('date');
} else {
$log=sprintf("No records found in %s\n",$this->table);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
} else {
$log=sprintf("Error: %s (%s)\n",$this->db->Error,$query);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
$query=sprintf("select id as max from %s where time_stamp < '%s' order by id DESC limit 1",
$this->table,$beforeDate);
if ($this->db->query($query) && $this->db->num_rows()) {
$this->db->next_record();
$max=$this->db->f('max');
} else {
$log=sprintf("No records found in %s before %s, records start after %s\n",
$this->table,$beforeDate,$begindate);
syslog(LOG_NOTICE,$log);
print $log;
return false;
}
$deleted=0;
$i=$min;
$interval=1000;
$rows2delete=$max-$min;
$found = 0;
print "$rows2delete traces to delete between $min and $max\n";
while ($i<=$max) {
$found=$found+$interval;
if ($i + $interval < $max) {
$top=$i;
} else {
$top=$max;
}
$query=sprintf("delete low_priority from %s
where id >= '%d' and id <='%d'",
$this->table,$min,$top);
if ($this->db->query($query)) {
$deleted=$deleted+$this->db->affected_rows();
} else {
$log=sprintf("Error: %s (%s)",$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE,$log);
return false;
}
if ($found > $progress*$rows2delete/100) {
$progress++;
if ($progress%10==0) {
print "$progress% ";
}
flush();
}
$i=$i+$interval;
}
print "\n";
$e =time();
$d =$e-$b;
$rps=0;
if ($deleted && $d) $rps=$deleted/$d;
$log=sprintf("%s records before %s from %s deleted in %d s @ %.0f rps\n",$deleted,$beforeDate,$this->table,$d,$rps);
syslog(LOG_NOTICE,$log);
print $log;
return true;
}
}
class Media_trace {
var $enableThor = false;
var $table = 'media_sessions';
function Media_trace ($cdr_source) {
global $DATASOURCES;
$this->cdr_source = $cdr_source;
$this->cdrtool = new DB_CDRTool();
if (!is_array($DATASOURCES[$this->cdr_source])) {
$log=sprintf("Error: datasource '%s' is not defined",$this->cdr_source);
print $log;
return 0;
}
if (strlen($DATASOURCES[$this->cdr_source]['enableThor'])) {
$this->enableThor = $DATASOURCES[$this->cdr_source]['enableThor'];
}
if ($this->enableThor) {
require("/etc/cdrtool/ngnpro_engines.inc");
require_once("ngnpro_soap_library.php");
if ($DATASOURCES[$this->cdr_source]['soapEngineId'] && in_array($DATASOURCES[$this->cdr_source]['soapEngineId'],array_keys($soapEngines))) {
$this->soapEngineId=$DATASOURCES[$this->cdr_source]['soapEngineId'];
$this->SOAPlogin = array(
"username" => $soapEngines[$this->soapEngineId]['username'],
"password" => $soapEngines[$this->soapEngineId]['password'],
"admin" => true
);
$this->SOAPurl=$soapEngines[$this->soapEngineId]['url'];
$this->SoapAuth = array('auth', $this->SOAPlogin , 'urn:AGProjects:NGNPro', 0, '');
// Instantiate the SOAP client
$this->soapclient = new WebService_NGNPro_SipPort($this->SOAPurl);
$this->soapclient->setOpt('curl', CURLOPT_TIMEOUT, 5);
$this->soapclient->setOpt('curl', CURLOPT_SSL_VERIFYPEER, 0);
$this->soapclient->setOpt('curl', CURLOPT_SSL_VERIFYHOST, 0);
} else {
print "Error: soapEngineID not defined in datasource $this->cdr_source";
return false;
}
} else {
if ($DATASOURCES[$this->cdr_source]['table']) {
$this->table = $DATASOURCES[$this->cdr_source]['table'];
}
$db_class = $DATASOURCES[$this->cdr_source]['db_class'];
if (class_exists($db_class)) {
$this->db = new $db_class;
} else {
printf("<p><font color=red>Error: database class %s is not defined in datasource %s</font>",$db_class,$this->cdr_source);
return false;
}
}
}
function getTrace ($proxyIP,$callid,$fromtag,$totag) {
if ($this->enableThor) {
// get trace using soap request
if (!$proxyIP || !$callid || !$fromtag) {
print "<p><font color=red>Error: proxyIP or callid or fromtag are not defined</font>";
return false;
}
if (!is_object($this->soapclient)) {
print "<p><font color=red>Error: soap client is not defined</font>";
return false;
}
$filter=array('nodeIp' => $proxyIP,
'callId' => $callid,
'fromTag' => $fromtag,
'toTag' => $totag
);
$this->soapclient->addHeader($this->SoapAuth);
$result = $this->soapclient->getMediaTrace($filter);
if (PEAR::isError($result)) {
$error_msg = $result->getMessage();
$error_fault = $result->getFault();
$error_code = $result->getCode();
if ($error_fault->detail->exception->errorcode != 1060) {
printf("<font color=red>Error from %s: %s: %s</font>",$this->SOAPurl,$error_fault->detail->exception->errorcode,$error_fault->detail->exception->errorstring);
}
return false;
}
$this->info = json_decode($result);
} else {
if (!is_object($this->db)) {
print "<p><font color=red>Error: no database connection defined</font>";
return false;
}
// get trace from SQL
$query=sprintf("select info from %s where call_id = '%s' and from_tag = '%s' and to_tag= '%s'",
$this->table,
addslashes($callid),
addslashes($fromtag),
addslashes($totag)
);
if (!$this->db->query($query)) {
printf ("<p><font color=red>Database error for query %s: %s (%s)</font>",$query,$this->db->Error,$this->db->Errno);
return false;
}
if ($this->db->num_rows()) {
$this->db->next_record();
$this->info = json_decode($this->db->f('info'));
}
}
/*
print "<pre>";
print_r($this->info);
print "</pre>";
*/
}
function show($proxyIP,$callid,$fromtag,$totag) {
if ($_SERVER['HTTPS'] == "on") {
$protocolURL = "https://";
} else {
$protocolURL = "http://";
}
$this->getTrace($proxyIP,$callid,$fromtag,$totag);
if (!is_object($this->info)) {
print "<p>No information available.";
return false;
}
if (!count($this->info->streams)) {
print "<p>
No media streams have been established for this session.";
return;
}
print "
<h1>CDRTool media trace</h1>
<h2>Media session $callid</h2>
";
foreach (array_values($this->info->streams) as $_val) {
$_diff=$_val->end_time-$_val->timeout_wait;
$seen_stamp[$_val->start_time]++;
$seen_stamp[$_val->end_time]++;
$seen_stamp[$_diff]++;
$media_types[]=$_val->media_type;
}
print "<h3>Media information</h3>";
print "<table border=0>";
printf ("<tr><td class=border>Call duration</td><td class=border>%s</td></tr>",$this->info->duration);
list($relay_ip,$relay_port)=explode(":",$this->info->streams[0]->caller_local);
printf ("<tr><td class=border>Media relay</td><td class=border>%s</td></tr>",$relay_ip);
print "</table>";
print "<h3>Media streams</h3>";
print "<table border=0>";
print "<th></th>";
foreach (array_values($media_types) as $_type) {
printf ("<th>%s</th>",ucfirst($_type));
}
print "</tr>";
foreach ($this->info->streams[0] as $_val => $_value) {
printf ("<tr><td class=border>%s</td>",ucfirst(preg_replace("/_/"," ",$_val)));
$j=0;
while ($j < count($media_types)) {
printf ("<td class=border>%s</td>",$this->info->streams[$j]->$_val);
$j++;
}
printf ("</tr>\n");
}
print "</table>";
print "<h3>Stream succession</h3>";
$w=500;
$w1=30;
$stamps=array_keys($seen_stamp);
sort($stamps);
$w2=$w+$w1;
print "<table border=0 width=$w2>";
foreach (array_values($this->info->streams) as $_val) {
$w_col1=intval($_val->start_time*$w/$this->info->duration);
$w_col2=intval(($_val->end_time-$_val->start_time-$_val->timeout_wait)*$w/$this->info->duration);
$w_col3=intval(($this->info->duration-$_val->end_time+$_val->timeout_wait)*$w/$this->info->duration);
print "<tr><td width=$w1 class=border>$_val->media_type</td>";
//$t2=$_val->end_time-$_val->timeout_wait;
$t2=$_val->end_time-$_val->start_time;
if (!$t2) $t2='';
$t3=$this->info->duration;
print "<td>
<table width=100%><tr>";
print "<td width=$w_col1 bgcolor=white></td>";
print "<td width=$w_col2 bgcolor=green align=center><font color=white>$t2</font></td>";
if ($_val->timeout_wait) {
print "<td width=$w_col3 bgcolor=red align=center><font color=white>$t3</font></td>";
} else {
print "<td width=$w_col3 bgcolor=white></td>";
}
print "</table>";
print "</td></tr>";
}
print "</table>";
print "<h4>Legend</h4>";
print "<table border=0>
<tr><td bgcolor=green width=50>&nbsp;</td><td>Session data</td></tr>
<tr><td bgcolor=red width=50>&nbsp;</td><td>Timeout period</td></tr>
</table>
";
}
}
include_once("phone_images.php");
function getImageForUserAgent($msg) {
global $userAgentImages;
$msg_lines=explode("\n",$msg);
foreach($msg_lines as $line) {
$els=explode(":",$line);
if (strtolower($els[0]) == 'user-agent' || strtolower($els[0]) == 'server') {
foreach ($userAgentImages as $agentRegexp => $image) {
if (preg_match("/^(user-agent|server):.*$agentRegexp/i", $line)) {
return $image;
}
}
}
}
return "unknown.png";
}
function isThorNode($ip,$sip_proxy) {
if (!$ip || !$sip_proxy) return false;
$socket = fsockopen($sip_proxy, 9500, $errno, $errstr, 1);
if (!$socket) {
return false;
}
$request=sprintf("is_online %s as sip_proxy",$ip);
if (fputs($socket,"$request\r\n") !== false) {
$ret = trim(fgets($socket,4096));
fclose($socket);
} else {
fclose($socket);
return false;
}
if ($ret == 'Yes') {
return true;
} else {
return false;
}
}
?>
diff --git a/library/rating.php b/library/rating.php
index 8e9db61..8a5b9c6 100644
--- a/library/rating.php
+++ b/library/rating.php
@@ -1,7222 +1,7224 @@
<?
/*
Copyright (c) 2007-2008 AG Projects
http://ag-projects.com
Author Adrian Georgescu
This library contains classes and functions for rating functionality
*/
class Rate {
var $priceDenominator = 10000; // allow sub cents
var $priceDecimalDigits = 4; // web display
var $durationPeriodRated = 60; // how the prices are indicated in the billing_rates, default is per minute
var $trafficSizeRated = 1024; // in KBytes, default 1MByte
var $minimumDuration = 0; // minimum duration considered to apply rates for a call, if call is shorter the price is zero
var $ENUMtld = '';
var $ENUMdiscount = 0; // how much percentage to substract from the final price
var $price = 0;
var $spans = 0; // number of spans we looped through
var $connectCost = 0;
var $rateValuesCache = array(); // used to speed up prepaid apoplication
var $brokenRates = array();
var $increment = 0; // used to consider the duration of the call in increments (e.g. 30 seconds)
var $min_duration = 0; // minimum duration considered for calculating the price
var $max_duration = 0; // maimum duration considered for calculating the price
var $max_price = 0; // maximum price fro the call
function Rate($settings=array(),&$db) {
$this->settings = $settings;
$this->db = &$db;
$this->db->Halt_On_Error="no";
if ($this->settings['priceDenominator']) {
$this->priceDenominator=$this->settings['priceDenominator'];
}
if ($this->settings['priceDecimalDigits']) {
$this->priceDecimalDigits=$this->settings['priceDecimalDigits'];
}
if ($this->settings['durationPeriodRated']) {
$this->durationPeriodRated=$this->settings['durationPeriodRated'];
}
if ($this->settings['trafficSizeRated']) {
$this->trafficSizeRated=$this->settings['trafficSizeRated'];
}
if ($this->settings['minimumDuration']) {
// if call is shorter than this, it has zero cost
$this->minimumDuration=$this->settings['minimumDuration'];
}
}
function calculate(&$dictionary) {
/////////////////////////////////////////////////////
// required fields passed from a CDR structure
//
// Session start time
$this->callId = $dictionary['callId'];
$this->timestamp = $dictionary['timestamp'];
// Session usage, type and destination Id
$this->duration = $dictionary['duration'];
$this->traffic = 2 * ($dictionary['inputTraffic'] + $dictionary['outputTraffic']);
$this->applicationType = $dictionary['applicationType'];
$this->DestinationId = $dictionary['DestinationId'];
$this->ResellerId = $dictionary['ResellerId'];
// Billable entities we try to best match the rating tables
// against
$this->BillingPartyId = $dictionary['BillingPartyId'];
$this->domain = $dictionary['domain'];
$this->gateway = $dictionary['gateway'];
if (!$this->gateway) $this->gateway="0.0.0.0";
$this->RatingTables = &$dictionary['RatingTables'];
/////////////////////////////////////////////////////////
// informational fields from CDR structure
$this->aNumber = $dictionary['aNumber'];
$this->cNumber = $dictionary['cNumber'];
$this->ENUMtld = $dictionary['ENUMtld'];
if ($this->minimumDuration && $this->duration < $this->minimumDuration) {
//syslog(LOG_NOTICE, "Duration less than minimum $this->minimumDuration");
$this->rateInfo .= " Duration < $this->minimumDuration s\n";
return false;
}
if ($this->ENUMtld && $this->ENUMtld != 'n/a' && $this->ENUMtld != 'none' && $this->RatingTables->ENUMtlds[$this->ENUMtld]) {
$this->ENUMdiscount = $this->RatingTables->ENUMtlds[$this->ENUMtld]['discount'];
if (!is_numeric($this->ENUMdiscount ) || $this->ENUMdiscount <0 || $this->ENUMdiscount >100) {
syslog(LOG_NOTICE, "Error: ENUM discount for tld $this->ENUMtld must be between 0 and 100");
}
}
if (!$this->duration) $this->duration = 0;
if (!$this->traffic) $this->traffic = 0;
if (!$this->applicationType) $this->applicationType='audio';
+ $this->rateSyslog .= sprintf("App=%s ",$this->applicationType);
+
$durationRate = 0;
$foundRates=array();
if (!$this->DestinationId) {
return false;
}
if (!$this->lookupDestination()) {
return false;
}
if (!$this->lookupProfiles()) {
return false;
}
$this->startTimeBilling = getLocalTime($this->billingTimezone,$this->timestamp);
list($dateText,$timeText) = explode(" ",trim($this->startTimeBilling));
$Bdate = explode("-",$dateText);
$Btime = explode(":",$timeText);
$this->timestampBilling = mktime($Btime[0], $Btime[1], $Btime[2], $Bdate[1], $Bdate[2], $Bdate[0]);
$this->startTimeBilling = Date("Y-m-d H:i:s",$this->timestampBilling);
$this->trafficKB=number_format($this->traffic/1024,0,"","");
// check min_duration and increment per destination
if ($this->increment >= 1) {
// increase the billed duration to the next increment
$this->duration = $this->increment * ceil($this->duration / $this->increment);
}
if ($this->max_duration && $this->duration > $this->max_duration) {
// limit the maximum duration for rating
$this->duration=$this->max_duration;
}
$this->rateSyslog="";
if ($this->duration) {
if ($this->increment >= 1) {
$this->rateInfo .=
" Increment: $this->increment s\n";
$this->rateSyslog .= sprintf("Increment=%s ",$this->increment);
}
if ($this->min_duration) {
$this->rateInfo .=
" Min duration: $this->min_duration s\n";
$this->rateSyslog .= sprintf("MinDuration=%s ",$this->min_duration);
}
if ($this->max_duration) {
$this->rateInfo .=
" Max duration: $this->max_duration s\n";
$this->rateSyslog .= sprintf("MaxDuration=%s ",$this->max_duration);
}
if ($this->max_price) {
$this->rateInfo .=
" Max price: $this->max_price\n";
$this->rateSyslog .= sprintf("MaxPrice=%s ",$this->max_price);
}
unset($IntervalsForPricing);
$this->rateInfo .=
" Duration: $this->duration s\n".
" App: $this->applicationType\n".
" Destination: $this->DestinationId\n".
" Customer: $this->CustomerProfile\n";
if ($this->ENUMtld && $this->ENUMtld != 'none' && $this->ENUMtld != 'n/a') {
$this->rateInfo .=
" ENUM tld: $this->ENUMtld\n".
" ENUM discount: $this->ENUMdiscount%\n";
}
$i=0;
$durationRatedTotal=0;
// get recursively a set of arrays with rates
// until we billed the whole duration
while ($durationRatedTotal < $this->duration) {
if ($i == "0") {
$dayofweek = date("w",$this->timestampBilling);
$hourofday = date("G",$this->timestampBilling);
$dayofyear = date("Y-m-d",$this->timestampBilling);
} else {
$dayofweek = date("w",$this->timestampBilling+$durationRatedTotal);
$hourofday = $foundRate['nextHourOfDay'];
$dayofyear = date("Y-m-d",$this->timestampBilling+$durationRatedTotal);
}
$foundRate = $this->lookupRate($dayofyear,$dayofweek,$hourofday,$durationRatedTotal);
$durationRatedTotal = $durationRatedTotal + $foundRate['duration'];
if (!$foundRate['rate']) {
return false;
}
$foundRates[] = $foundRate;
$i++;
if ($i > 10) {
// possible loop because of wrong coding make sure we can end this somehow
$body="Rating of call $this->callId (DestId=$this->DestinationId) has more than 10 spans. It could be a serious bug.\n";
mail($this->toEmail, "CDRTool rating problem", $body , $this->extraHeaders);
syslog(LOG_NOTICE, "Error: Rating of call $this->callId (DestId=$this->DestinationId) has more than 10 spans.");
break;
}
}
}
$j=0;
$span=0;
foreach ($foundRates as $thisRate) {
$spanPrice=0;
$span++;
if ($j > 0) {
$payConnect=0;
$durationForRating=$thisRate['duration'];
} else {
$payConnect=1;
if ($this->min_duration && $this->duration < $this->min_duration) {
$durationForRating=$this->min_duration;
} else {
$durationForRating=$thisRate['duration'];
}
}
$connectCost = $thisRate['values']['connectCost'];
$durationRate = $thisRate['values']['durationRate'];
$connectCostIn = $thisRate['values']['connectCostIn'];
$durationRateIn = $thisRate['values']['durationRateIn'];
if ($span=="1") {
$connectCostSpan=$connectCost;
$this->connectCost=number_format($connectCost/$this->priceDenominator,$this->priceDecimalDigits);
$connectCostSpanIn=$connectCostIn;
$this->connectCostIn=number_format($connectCostIn/$this->priceDenominator,$this->priceDecimalDigits);
} else {
$connectCostSpan=0;
$connectCostSpanIn=0;
}
$connectCostPrint = number_format($connectCostSpan/$this->priceDenominator,$this->priceDecimalDigits);
$durationRatePrint = number_format($durationRate/$this->priceDenominator,$this->priceDecimalDigits);
$connectCostPrintIn = number_format($connectCostSpanIn/$this->priceDenominator,$this->priceDecimalDigits);
$durationRatePrintIn = number_format($durationRateIn/$this->priceDenominator,$this->priceDecimalDigits);
if (!$connectCostSpan) $connectCostSpan=0;
if (!$durationRate) $durationRate=0;
if (!$connectCostSpanIn) $connectCostSpanIn=0;
if (!$durationRateIn) $durationRateIn=0;
if (!$this->inputTraffic) $this->inputTraffic=0;
if (!$this->outputTraffic) $this->outputTraffic=0;
if ($span>1) $this->rateInfo .= "--\n";
/*
durationRate*durationForRating/durationPeriodRated/priceDenominator+
trafficRate/priceDenominator/trafficSizeRated*(inputTraffic+outputTraffic)/8");
$durationRate*$durationForRating/$this->durationPeriodRated/$this->priceDenominator+
$trafficRate/$this->priceDenominator/$this->trafficSizeRated*($this->inputTraffic+$this->outputTraffic)/8");
*/
$spanPrice = $durationRate*$durationForRating/$this->durationPeriodRated/$this->priceDenominator;
$this->price = $this->price+$spanPrice;
$spanPricePrint = number_format($spanPrice,$this->priceDecimalDigits);
$spanPriceIn = $durationRateIn*$durationForRating/$this->durationPeriodRated/$this->priceDenominator;
$this->priceIn = $this->priceIn+$spanPriceIn;
$spanPricePrintIn = number_format($spanPriceIn,$this->priceDecimalDigits);
if ($span=="1" && $thisRate['profile']) {
if ($connectCostIn) {
$this->rateInfo .=
" Connect in: $connectCostPrintIn\n";
}
$this->rateInfo .=
" Connect: $connectCostPrint\n".
" StartTime: $this->startTimeBilling\n".
"--\n";
$this->rateSyslog .= "ConnectFee=$connectCostPrint ";
$this->price = $this->price+$connectCostSpan/$this->priceDenominator*$payConnect;
$this->priceIn = $this->priceIn+$connectCostSpanIn/$this->priceDenominator*$payConnect;
}
$this->rateInfo .=
" Span: $span\n".
" Duration: $durationForRating s\n";
$this->rateSyslog .= sprintf("CallId=%s Span=%s Duration=%s DestId=%s %s",$this->callId,$span,$durationForRating,$this->DestinationId,$thisRate['customer']);
if ($thisRate['profile']) {
$this->rateInfo .=
" ProfileId: $thisRate[profile] / $thisRate[day]\n".
" RateId: $thisRate[rate] / $thisRate[interval]h\n".
" Rate: $durationRatePrint / $this->durationPeriodRated s\n".
" Price: $spanPricePrint\n";
if ($spanPriceIn) {
$this->rateInfo .=
" Price in: $spanPricePrintIn\n";
}
$this->rateSyslog .= sprintf(" Profile=%s Period=%s Rate=%s Interval=%s Cost=%s/%s",$thisRate['profile'],$thisRate['day'],$thisRate['rate'],$thisRate['interval'],$durationRatePrint,$this->durationPeriodRated);
} else {
$this->rateInfo .=
" ProfileId: none\n".
" RateId: none\n";
$this->rateSyslog .= " Profile=none, Rate=none";
}
$this->rateSyslog .= " Price=".sprintf("%.4f",$spanPrice);
$this->rateSyslog .= " PriceIn=".sprintf("%.4f",$spanPriceIn);
syslog(LOG_NOTICE, $this->rateSyslog);
$j++;
}
if ($this->priceIn) {
$this->rateInfo .= "--\n".
" Price out: ".sprintf("%.4f",$this->price)."\n".
" Price in: ".sprintf("%.4f",$this->priceIn)."\n".
" Margin: ".sprintf("%.4f",$this->price-$this->priceIn)."\n";
}
$this->rateInfo=trim($this->rateInfo);
if ($this->max_price && $this->price > $this->max_price) {
$this->price=$this->max_price;
}
if ($this->ENUMdiscount) {
$this->priceBeforeDiscount=sprintf("%.4f",$this->price);
$this->price = $this->price - $this->price*$this->ENUMdiscount/100;
$this->price=sprintf("%.4f",$this->price);
$this->rateInfo .=
"\n--\n".
" Total: $this->priceBeforeDiscount\n".
" Total after discount: $this->price\n";
}
$this->price=sprintf("%.4f",$this->price);
if ($this->price > 0) {
$this->pricePrint=number_format($this->price,$this->priceDecimalDigits);
} else if ($thisRate[profile]) {
if ($j) {
if ($this->DestinationId && !strlen($durationRate)) {
$this->brokenRates[$this->DestinationId]++;
}
}
$this->pricePrint="";
}
return true;
}
function lookupDestination() {
// get rating details for the destination
$query=sprintf("select * from destinations
where dest_id = '%s'
and (reseller_id = %d or reseller_id = 0) order by reseller_id desc limit 1",
addslashes($this->DestinationId),
addslashes($this->ResellerId)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) {
$this->db->next_record();
$this->increment = $this->db->Record['increment'];
$this->min_duration = $this->db->Record['min_duration'];
$this->max_duration = $this->db->Record['max_duration'];
$this->max_price = $this->db->Record['max_price'];
}
return true;
}
function lookupProfiles() {
unset($this->allProfiles);
/*
lookup the profile_name in billing_customers in the following order:
subscriber, domain, gateway (based on $dayofweek):
- profile1 matches days [1-5] (Work-day)
- profile2 matches days [6-0] (Week-end)
- week starts with 0 Sunday and ends with 6 Saturday
Alternatively look for profile1_alt and profile2_alt
If no rates are found for destination in the profileX,
than lookup rates in profileX_alt
*/
$query=sprintf("select * from billing_customers
where subscriber = '%s'
or domain = '%s'
or gateway = '%s'
or (subscriber = '' and domain = '' and gateway = '')
order by subscriber desc, domain desc, gateway desc limit 1 ",
addslashes($this->BillingPartyId),
addslashes($this->domain),
addslashes($this->gateway)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) {
$this->db->next_record();
if ($this->db->Record['subscriber']) {
$this->CustomerProfile = sprintf("subscriber=%s",$this->db->Record['subscriber']);
} else if ($this->db->Record['domain']) {
$this->CustomerProfile = sprintf("domain=%s",$this->db->Record['domain']);
} else if ($this->db->Record['gateway']) {
$this->CustomerProfile = sprintf("gateway=%s",$this->db->Record['gateway']);
} else {
$this->CustomerProfile = "default";
}
if (!$this->db->Record['profile_name1']) {
$log=sprintf("Error: customer %s (id=%d) has no weekday profile assigned in profiles table",$this->CustomerProfile,$this->db->Record['id']);
syslog(LOG_NOTICE, $log);
return false;
}
if (!$this->db->Record['profile_name2']) {
$log=sprintf("Error: customer %s (id=%d) has no weekend profile assigned in profiles table",$this->CustomerProfile,$this->db->Record['id']);
syslog(LOG_NOTICE, $log);
return false;
}
if (!$this->db->Record['timezone']) {
$log = sprintf ("Error: missing timezone for customer %s",$this->CustomerProfile);
syslog(LOG_NOTICE, $log);
return false;
}
$this->billingTimezone = $this->db->Record['timezone'];
$this->allProfiles = array (
"profile1" => $this->db->Record['profile_name1'],
"profile2" => $this->db->Record['profile_name2'],
"profile1_alt" => $this->db->Record['profile_name1_alt'],
"profile2_alt" => $this->db->Record['profile_name2_alt'],
"timezone" => $this->db->Record['timezone']
);
return true;
} else {
$log=sprintf("Error: no customer found in billing_customers table for billing party=%s, domain=%s, gateway=%s",$this->BillingPartyId,$this->domain,$this->gateway);
syslog(LOG_NOTICE, $log);
return false;
}
}
function lookupRate($dayofyear,$dayofweek,$hourofday,$durationRatedAlready) {
/*
// Required information from CDR structure
$this->BillingPartyId # calling subscriber
$this->domain # multiple callers may belong to same domain
$this->gateway # multiple callers may belong to the same gateway
$this->cNumber # E164 destination prefixed with 00 (e.g. 0041 CH)
$this->DestinationId # longest matched DestinationId
// pertinent to the curent rating SPAN (a span = same profile like evening hours)
$hourofday # which hour of teh day started for peak/ofpeak rates
$dayofweek # which day of the week for matching profiles
$dayofyear # which day of the year for matching holidays
$durationRatedAlready= the full duration for which a profile is defined (e.g. 0800-1800)
// the call is called recursively until the $durationRatedAlready = $CDR->duration
// when a call spans multiple profiles. If we span multiple profiles we must call
// the function again to lookup the corect rates
Rating logic
------------
1. using the profile_name found, lookup the rate_name based
on $hourofday in billing_profiles
- the day may be split in maximum 4 periods
- each day starts with hour 0 and ends with hour 24
- rate_name1 defines the first interval after hour 0
- rate_name2 defines the first interval after rate_name1
- rate_name3 defines the first interval after rate_name2
- rate_name4 defines the first interval after rate_name3
When the hour matches an interval use the rate_nameX found
to lookup the rate in billing_rates
- if no record is found use the rate called 'default'
2. lookup in billing_rates the record having same name found above
and billing_rates.destination = $this->DestinationId
- return an array with all the values to
$this->calculate() function that called us
*/
// get work-day or weekend profile
if ($this->RatingTables->holidays[$dayofyear]) {
$this->profileName = $this->allProfiles['profile2'];
$this->profileNameAlt = $this->allProfiles['profile2_alt'];
$this->PeriodOfProfile = "weekend";
} else {
if ($dayofweek >=1 && $dayofweek <=5 ) {
$this->profileName = $this->allProfiles['profile1'];
$this->profileNameAlt = $this->allProfiles['profile1_alt'];
$this->PeriodOfProfile = "weekday";
} else {
$this->profileName = $this->allProfiles['profile2'];
$this->profileNameAlt = $this->allProfiles['profile2_alt'];
$this->PeriodOfProfile = "weekend";
}
}
// get rate for the time of the day
$timestampNextProfile = $this->timestampBilling + $durationRatedAlready;
$profileValues = $this->RatingTables->profiles[$this->profileName];
if (is_array($profileValues)) {
$this->profileNameLog = $this->profileName;
if ($hourofday < $profileValues['hour1'] ) {
$this->rateName = $profileValues['rate_name1'];
$this->timeInterval = "0-".$profileValues['hour1'];
$foundProfile = $profileValues['hour1'];
$this->nextProfile = $profileValues['hour1'];
} else if ($hourofday < $profileValues['hour2']) {
$this->rateName = $profileValues['rate_name2'];
$this->timeInterval = $profileValues['hour1']."-".$profileValues['hour2'];
$foundProfile = $profileValues['hour2'];
$this->nextProfile = $profileValues['hour2'];
} else if ($hourofday < $profileValues['hour3']) {
$this->rateName = $profileValues['rate_name3'];
$this->timeInterval = $profileValues['hour2']."-".$profileValues['hour3'];
$foundProfile = $profileValues['hour3'];
$this->nextProfile = $profileValues['hour3'];
} else if ($hourofday < $profileValues['hour4']) {
$this->rateName = $profileValues['rate_name4'];
$this->timeInterval = $profileValues['hour3']."-".$profileValues['hour4'];
$foundProfile = $profileValues['hour4'];
$this->nextProfile = 0;
}
if ($this->rateName) {
$found_history=false;
//get historical rating if exists
if (is_array($this->RatingTables->ratesHistory[$this->rateName][$this->DestinationId][$this->applicationType])) {
$h=0;
foreach (($this->RatingTables->ratesHistory[$this->rateName][$this->DestinationId][$this->applicationType]) as $_idx) {
$h++;
if ($_idx['startDate'] <= $this->timestamp) {
if ($_idx['endDate'] > $this->timestamp) {
// found historical rate
$found_history=true;
$this->rateValues=$_idx;
break;
} else {
$_log=sprintf("Interval missmatch %s < %s",$_idx['endDate'],$this->timestamp);
continue;
}
} else {
$_log=sprintf("Interval missmatch %s > %s",$_idx['startDate'],$this->timestamp);
continue;
}
}
}
if (!$found_history) {
$this->rateValues=$this->lookupRateValues($this->rateName,$this->DestinationId,$this->applicationType);
}
}
}
$profileValuesAlt = $this->RatingTables->profiles[$this->profileNameAlt];
if (!$this->rateValues && is_array($profileValuesAlt)) {
$this->profileNameLog = $this->profileNameAlt;
if ($hourofday < $profileValuesAlt['hour1'] ) {
$this->rateName = $profileValuesAlt['rate_name1'];
$this->timeInterval = "0-".$profileValuesAlt['hour1'];
$foundProfile = $profileValuesAlt['hour1'];
$this->nextProfile = $profileValuesAlt['hour1'];
} else if ($hourofday < $profileValuesAlt['hour2']) {
$this->rateName = $profileValuesAlt['rate_name2'];
$this->timeInterval = $profileValuesAlt['hour1']."-".$profileValuesAlt['hour2'];
$foundProfile = $profileValuesAlt['hour2'];
$this->nextProfile = $profileValuesAlt['hour2'];
} else if ($hourofday < $profileValuesAlt['hour3']) {
$this->rateName = $profileValuesAlt['rate_name3'];
$this->timeInterval = $profileValuesAlt['hour2']."-".$profileValuesAlt['hour3'];
$foundProfile = $profileValuesAlt['hour3'];
$this->nextProfile = $profileValuesAlt['hour3'];
} else if ($hourofday < $profileValuesAlt['hour4']) {
$this->rateName = $profileValuesAlt['rate_name4'];
$this->timeInterval = $profileValuesAlt['hour3']."-".$profileValuesAlt['hour4'];
$foundProfile = $profileValuesAlt['hour4'];
$this->nextProfile = 0;
}
if ($this->rateName) {
$found_history=false;
//get historical rating if exists
if (is_array($this->RatingTables->ratesHistory[$this->rateName][$this->DestinationId][$this->applicationType])) {
$h=0;
foreach (($this->RatingTables->ratesHistory[$this->rateName][$this->DestinationId][$this->applicationType]) as $_idx) {
$h++;
if ($_idx['startDate'] <= $this->timestamp) {
if ($_idx['endDate'] > $this->timestamp) {
// found historical rate
$found_history=true;
$this->rateValues=$_idx;
break;
} else {
$_log=sprintf("Interval missmatch %s < %s",$_idx['endDate'],$this->timestamp);
continue;
}
} else {
$_log=sprintf("Interval missmatch %s > %s",$_idx['startDate'],$this->timestamp);
continue;
}
}
}
if (!$found_history) {
$this->rateValues=$this->lookupRateValues($this->rateName,$this->DestinationId,$this->applicationType);
}
}
}
if (!$this->rateValues) {
$this->rateNotFound=true;
$log=sprintf("Error: Cannot find rates for callid=%s, billing party=%s, customer %s, gateway=%s, destination=%s, profile=%s, app=%s",
$this->callId,$this->BillingPartyId,$this->CustomerProfile,$this->gateway,$this->DestinationId,$this->profileName,$this->applicationType);
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->nextProfile == "24") $this->nextProfile = 0;
$DST = Date("I",$timestampNextProfile);
if (!$this->nextProfile) {
// check it we change daylight saving time tomorrow
// yes this cann happen and we must apply a different rate
$timestampNextProfile =$timestampNextProfile+24*3600;
$DSTNext = Date("I",$timestampNextProfile);
if ($DST != $DSTNext) {
if ($DSTNext==0) {
$timestampNextProfile=$timestampNextProfile+3600;
} else if ($DSTNext==1) {
$timestampNextProfile=$timestampNextProfile-3600;
}
}
}
// see if we have minimum duration or increment
if ($this->rateValues['increment']) {
// increase the billed duration to the next increment
$this->duration = $this->rateValues['increment'] * ceil($this->duration / $this->rateValues['increment']);
}
$durationToRate=$this->duration-$durationRatedAlready;
$month = Date("m",$timestampNextProfile);
$day = Date("d",$timestampNextProfile);
$year = Date("Y",$timestampNextProfile);
$nextProfileTimestamp=mktime($this->nextProfile, 0, 0, $month,$day,$year);
$npdt=Date("Y-m-d H:i", $nextProfileTimestamp);
$timeTillNextProfile=$nextProfileTimestamp-$this->timestampBilling;
if ($durationToRate > $timeTillNextProfile) {
$diff=$durationToRate-$timeTillNextProfile;
$this->durationRated=$timeTillNextProfile;
} else {
$this->durationRated=$durationToRate;
}
$rate=array(
"customer" =>$this->CustomerProfile,
"application" =>$this->applicationType,
"profile" =>$this->profileNameLog,
"day" =>$this->PeriodOfProfile,
"destinationId" =>$this->DestinationId,
"duration" =>$this->durationRated,
"rate" =>$this->rateName,
"values" =>$this->rateValues,
"interval" =>$this->timeInterval,
"nextHourOfDay" =>$this->nextProfile
);
return $rate;
}
function MaxSessionTime(&$dictionary) {
$this->rateValuesCache=array();
// Used for prepaid application
$this->MaxSessionTimeSpans=0;
$durationRate = 0;
/////////////////////////////////////////////////////
// required fields passed from the CDR structure
//
$this->timestamp = time();
$this->callId = $dictionary['callId'];
$this->DestinationId = $dictionary['DestinationId'];
$this->BillingPartyId = $dictionary['BillingPartyId'];
$this->domain = $dictionary['domain'];
$this->duration = $dictionary['duration'];
$this->aNumber = $dictionary['aNumber'];
$this->cNumber = $dictionary['cNumber'];
$this->gateway = $dictionary['gateway'];
$this->RatingTables = &$dictionary['RatingTables'];
$this->applicationType = $dictionary['Application'];
$this->ResellerId = $dictionary['ResellerId'];
$Balance = $dictionary['Balance'];
if (!$this->applicationType) $this->applicationType='audio';
if (!$this->DestinationId) {
$log=sprintf("Error: no DestinationId supplied in MaxSessionTime()");
syslog(LOG_NOTICE, $log);
return false;
}
if (!$this->lookupDestination()) {
return false;
}
if (!$this->lookupProfiles()) {
return false;
}
$this->startTimeBilling = getLocalTime($this->billingTimezone,$this->timestamp);
list($dateText,$timeText) = explode(" ",trim($this->startTimeBilling));
$Bdate = explode("-",$dateText);
$Btime = explode(":",$timeText);
$this->timestampBilling = mktime($Btime[0], $Btime[1], $Btime[2], $Bdate[1], $Bdate[2], $Bdate[0]);
$this->startTimeBilling = Date("Y-m-d H:i:s",$this->timestampBilling);
$i=0;
$durationRatedTotal=0;
while ($Balance > 0 ) {
$span++;
$this->MaxSessionTimeSpans++;
if ($i == "0") {
$dayofweek = date("w",$this->timestampBilling);
$hourofday = date("G",$this->timestampBilling);
$dayofyear = date("Y-m-d",$this->timestampBilling);
} else {
$dayofweek = date("w",$this->timestampBilling+$durationRatedTotal);
$hourofday = $foundRate['nextHourOfDay'];
$dayofyear = date("Y-m-d",$this->timestampBilling+$durationRatedTotal);
}
$foundRate = $this->lookupRate($dayofyear,$dayofweek,$hourofday,$durationRatedTotal);
if ($this->rateNotFound) {
// break here to avoid loops
break;
}
$thisRate=$foundRate;
if ($j > 0) {
$payConnect=0;
$durationForRating = $thisRate['duration'];
} else {
$payConnect=1;
if ($this->min_duration && $this->duration < $this->min_duration) {
$durationForRating=$this->min_duration;
} else {
$durationForRating=$thisRate['duration'];
}
}
$j++;
$connectCost = $thisRate['values']['connectCost'];
$durationRate = $thisRate['values']['durationRate'];
if ($span=="1" && !$dictionary['skipConnectCost']) {
$this->connectCost=number_format($connectCost/$this->priceDenominator,$this->priceDecimalDigits);
$connectCostSpan=$connectCost;
$setupBalanceRequired=$connectCost/$this->priceDenominator;
if ($connectCost && $Balance <= $setupBalanceRequired) {
syslog(LOG_NOTICE,"Balance too small: $Balance <= $setupBalanceRequired");
return false;
}
$Balance = $Balance-$setupBalanceRequired;
} else {
$connectCostSpan=0;
$setupBalanceRequired=0;
}
$connectCostPrint = number_format($connectCostSpan/$this->priceDenominator,$this->priceDecimalDigits);
$durationRatePrint = number_format($durationRate/$this->priceDenominator,$this->priceDecimalDigits);
$spanPrice = $this->price+$setupBalanceRequired*$payConnect+
$durationRate*$durationForRating/$this->durationPeriodRated/$this->priceDenominator;
if ($Balance > $spanPrice) {
$Balance = $Balance-$spanPrice;
$durationRatedTotal = $durationRatedTotal+ $foundRate['duration'];
} else {
$durationAllowedinThisSpan = $Balance /
$durationRate * $this->durationPeriodRated * $this->priceDenominator;
$rateOfThisSpan=$durationRate/$this->priceDenominator;
$durationRatedTotal=$durationRatedTotal + $durationAllowedinThisSpan;
$Balance=$Balance-$spanPrice;
return $durationRatedTotal;
}
if ($durationRatedTotal >= $this->duration) {
return sprintf("%f",$durationRatedTotal);
}
$i++;
if ($i>10) {
return sprintf("%f",$durationRatedTotal);
break;
}
}
return false;
}
function lookupRateValues($rateName,$DestinationId,$applicationType='audio') {
if (is_array($this->rateValuesCache[$rateName][$DestinationId][$applicationType])) {
return $this->rateValuesCache[$rateName][$DestinationId][$applicationType];
}
if ($this->settings['split_rating_table']) {
if ($rateName) {
$table="billing_rates_".$rateName;
} else {
$table="billing_rates_default";
}
$query=sprintf("select * from %s where destination = '%s' and application = '%s'",
$table,
$DestinationId,
$applicationType
);
} else {
$table="billing_rates";
$query=sprintf("select * from %s where name = '%s' and destination = '%s' and application = '%s'",
$table,
$rateName,
$DestinationId,
$applicationType
);
}
// lookup rate from MySQL
if (!$this->db->query($query)) {
if ($this->db->Errno != 1146) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return false;
}
// try the main table
// lookup rate from MySQL
$query=sprintf("select * from billing_rates where name = '%s' and destination = '%s' and application = '%s'",
$rateName,
$DestinationId,
$applicationType
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return false;
}
}
$log=sprintf("Found %d rows",$this->db->num_rows());
if ($this->db->num_rows()) {
$this->db->next_record();
$values=array(
"connectCost" => $this->db->Record['connectCost'],
"durationRate" => $this->db->Record['durationRate'],
"connectCostIn" => $this->db->Record['connectCostIn'],
"durationRateIn" => $this->db->Record['durationRateIn']
);
// cache values
$this->rateValuesCache[$rateName][$DestinationId][$applicationType]=$values;
return $values;
} else {
return false;
}
}
}
class RatingTables {
var $csv_export=array(
"destinations" => "destinations.csv",
"billing_customers" => "customers.csv",
"billing_profiles" => "profiles.csv",
"billing_rates" => "rates.csv",
"billing_rates_history" => "ratesHistory.csv",
"prepaid" => "prepaid.csv",
"billing_enum_tlds" => "enumtld.csv",
"quota_usage" => "quotausage.csv"
);
var $csv_import=array(
"destinations" => "destinations.csv",
"billing_customers" => "customers.csv",
"billing_profiles" => "profiles.csv",
"billing_rates" => "rates.csv",
"billing_rates_history" => "ratesHistory.csv"
);
var $previously_imported_files=0;
var $maxrowsperpage=15;
var $insertDomainOption=array();
var $delimiter=",";
var $filesToImport=array();
var $importFilesPatterns=array('ratesHistory',
'rates',
'profiles',
'destinations',
'customers'
);
var $mustReload = false;
var $web_elements=array('table',
'export',
'web_task',
'subweb_task',
'confirmDelete',
'confirmCopy',
'next',
'id',
'search_text',
'ReloadRatingTables',
'account',
'balance',
'fromRate',
'toRate',
'sessionId'
);
var $requireReload = array('destinations');
var $whereResellerFilter = " (1=1) ";
var $cvs_import_dir = "/var/spool/cdrtool";
var $tables=array(
"destinations"=>array("name"=>"Destinations",
"skip_math"=> true,
"keys"=>array("id"),
"exceptions" =>array(),
"order"=>"dest_id ASC",
"domainFilterColumn"=>"domain",
"fields"=>array(
"gateway"=>array("size"=>15,
"checkType"=>'ip',
"name"=>"Trusted peer"
),
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"domain"=>array("size"=>15,
"name"=>"Domain",
"checkType"=>'domain'
),
"subscriber"=>array("size"=>15,
"checkType"=>'sip_account',
"name"=>"Subscriber"
),
"dest_id"=>array("size"=>15,
"name"=>"Destination Id"
),
"dest_name"=>array("size"=>25,
"name"=>"Description"
),
"increment" =>array("size"=>3,
"checkType"=>'numeric',
"name"=>"Incr"
),
"min_duration" =>array("size"=>3,
"checkType"=>'numeric',
"name"=>"Min Dur"
),
"max_duration" =>array("size"=>5,
"checkType"=>'numeric',
"name"=>"Max Dur"
),
"max_price" =>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Max Price"
)
)
),
"billing_customers"=>array("name"=>"Customers",
"skip_math"=> true,
"keys"=>array("id"),
"domainFilterColumn"=>"domain",
"fields"=>array("gateway"=>array("size"=>15,
"checkType"=>'ip',
"name"=>"Trusted Peer"
),
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"domain"=>array("size"=>15,
"checkType"=>'domain',
"name"=>"Domain"
),
"subscriber"=>array("size"=>25,
"checkType"=>'sip_account',
"name"=>"Subscriber",
),
"profile_name1"=>array("size"=>10,
"name"=>"Profile WD"
),
"profile_name1_alt"=>array("size"=>8,
"name"=>"Fallback"
),
"profile_name2"=>array("size"=>10,
"name"=>"Profile WE"
),
"profile_name2_alt"=>array("size"=>8,
"name"=>"Fallback"
),
"timezone" =>array("size"=>16,
"name"=>"Timezone"
)
)
),
"billing_profiles"=>array("name"=>"Profiles",
"skip_math"=> true,
"keys"=>array("id"),
"exceptions" =>array(),
"size"=>6,
"fields"=>array(
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"name"=>array("size"=>12,
"name"=>"Profile"
),
"rate_name1"=>array("size"=>12,
"name"=>"Rate 1"
),
"hour1"=>array("size"=>3,
"checkType"=>'numeric',
"name"=>"00-H1"
),
"rate_name2"=>array("size"=>12,
"name"=>"Rate 2"
),
"hour2"=>array("size"=>3,
"checkType"=>'numeric',
"name"=>"H1-H2"
),
"rate_name3"=>array("size"=>12,
"name"=>"Rate 3"
),
"hour3"=>array("size"=>3,
"checkType"=>'numeric',
"name"=>"H2-H3"
),
"rate_name4"=>array("size"=>12,
"name"=>"Rate 4"
),
"hour4"=>array("size"=>3,
"checkType"=>'numeric',
"name"=>"H3-24"
),
)
),
"billing_rates"=>array("name"=>"Rates",
"keys"=>array("id"),
"size"=>10,
"exceptions"=>array('maxPrice'),
"order"=>"durationRate desc",
"fields"=>array(
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"name"=>array("size"=>12,
"name"=>"Rate"
),
"destination"=>array("size"=>12,
"name"=>"Destination"
),
"application"=>array("size"=>6,
"name"=>"App"
),
"connectCost"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Conn"
),
"durationRate"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Price"
),
"connectCostIn"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Conn In"
),
"durationRateIn"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Price In"
)
)
),
"billing_rates_history"=>array("name"=>"Rates history",
"keys"=>array("id"),
"size"=>10,
"order"=>"destination ASC, name ASC",
"fields"=>array(
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"name"=>array("size"=>10,
"name"=>"Rate"
),
"destination"=>array("size"=>12,
"name"=>"Destination"
),
"application"=>array("size"=>6,
"name"=>"App"
),
"connectCost"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Conn"
),
"durationRate"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Price"
),
"connectCostIn"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Conn In"
),
"durationRateIn"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Price In"
),
"startDate"=>array("size"=>11,
"name"=>"Start Date"
),
"endDate"=>array("size"=>11,
"name"=>"End Date"
)
)
),
"billing_enum_tlds"=>array("name"=>"ENUM discounts",
"skip_math"=> true,
"keys"=>array("id"),
"exceptions" =>array(),
"size"=>6,
"fields"=>array(
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"enum_tld"=>array("size"=>35,
"mustExist"=>true,
"checkType"=>'domain',
"name"=>"ENUM TLD"
),
"e164_regexp"=>array("size"=>35,
"mustExist"=>true,
"name"=>"E164 Regexp"
),
"discount"=>array("size"=>10,
"mustExist"=>true,
"checkType"=>'numeric',
"name"=>"Discount"
)
)
),
"prepaid"=>array("name"=>"Prepaid accounts",
"keys"=>array("id"),
"size"=>15,
"exceptions" =>array('change_date','active_sessions','domain'),
"order"=>"change_date DESC",
"fields"=>array("account"=>array("size"=>35,
"name"=>"Account",
"checkType"=>'sip_account',
"mustExist"=>true
),
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"balance"=>array("size"=>10,
"name"=>"Balance"
),
"change_date"=>array("size"=>19,
"name"=>"Last Change",
"readonly"=>1
),
"session_counter"=>array("size"=>3,
"name"=>"Active Sessions",
"readonly"=>1
),
"max_sessions"=>array("size"=>3,
"name"=>"Max Sessions"
)
)
),
"prepaid_cards"=>array("name"=>"Prepaid cards",
"keys"=>array("id"),
"size"=>15,
"exceptions" =>array('service'),
"fields"=>array("batch"=>array("size"=>40,
"name"=>"Batch name",
"readonly"=>1
),
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"date_batch"=>array("size"=>11,
"name"=>"Batch Date"
),
"number"=>array("size"=>20,
"checkType"=>'numeric',
"mustExist"=>true,
"name"=>"Card Number"
),
"id"=>array("size"=>20,
"checkType"=>'numeric',
"mustExist"=>true,
"name"=>"Card Id"
),
"value"=>array("size"=>8,
"checkType"=>'numeric',
"mustExist"=>true,
"name"=>"Card Value"
),
"blocked"=>array("size"=>1,
"name"=>"Lock"
),
"date_active"=>array("size"=>18,
"name"=>"Activation Date"
)
)
),
"prepaid_history"=>array("name"=>"Prepaid history",
"order"=>"id DESC",
"skip_math"=> true,
"keys"=>array("id"),
"size"=>15,
"exceptions" =>array('session','destination'),
"fields"=>array("username"=>array("size"=>15,
),
"domain"=>array("size"=>15,
),
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller"
),
"action"=>array("size"=>15
),
"duration"=>array("size"=>5
),
"destination"=>array("size"=>15
),
"session"=>array("size"=>30,
"readonly"=>1
),
"description"=>array("size"=>30
),
"value"=>array("size"=>10
),
"balance"=>array("size"=>10
),
"date"=>array("size"=>18,
))
),
"quota_usage"=>array("name"=>"Quota usage",
"keys"=>array("id"),
"size"=>15,
"readonly"=>1,
"exceptions" =>array("change_date","traffic"),
"domainFilterColumn"=>"domain",
"fields"=>array("datasource"=>array("size"=>15,
"readonly"=>1
),
"reseller_id"=>array("size"=>8,
"checkType"=>'numeric',
"name"=>"Reseller",
"readonly" => true
),
"account"=>array("size"=>30,
"readonly"=>1
),
"domain"=>array("size"=>15,
"readonly"=>1
),
"blocked"=>array("size"=>2,
"readonly"=>1
),
"notified"=>array("size"=>20,
"readonly"=>1
),
"quota"=>array("size"=>5,
"readonly"=>1
),
"cost"=>array("size"=>10,
"readonly"=>1
),
"duration"=>array("size"=>10,
"readonly"=>1
),
"calls"=>array("size"=>10,
"readonly"=>1
),
"traffic"=>array("size"=>20,
"readonly"=>1
)
)
)
);
function RatingTables ($readonly=false) {
global $CDRTool;
global $RatingEngine;
$this->settings = $RatingEngine;
$this->CDRTool = $CDRTool;
$this->table = $_REQUEST['table'];
if (!$this->table || !in_array($this->table,array_keys($this->tables))) $this->table="destinations";
$this->readonly=$readonly;
$this->db = new DB_cdrtool;
$this->db1 = new DB_cdrtool;
$this->db->Halt_On_Error="no";
$this->db1->Halt_On_Error="no";
if ($this->settings['csv_delimiter']) {
$this->delimiter=$this->settings['csv_delimiter'];
}
if (!strlen($this->CDRTool['filter']['reseller'])) {
$this->whereResellerFilter = sprintf ("reseller_id = %d",'99999999');
} else {
if ($this->CDRTool['filter']['reseller'] && $this->tables[$this->table]['fields']['reseller_id']) {
$this->whereResellerFilter = sprintf ("reseller_id = %d",$this->CDRTool['filter']['reseller']);
$this->tables[$this->table]['fields']['reseller_id']['readonly']=true;
}
}
if ($this->settings['split_rating_table']) {
$this->tables['billing_rates']['fields']['name']['readonly']=1;
}
if (strlen($this->settings['socketIP'])) {
if ($this->settings['socketIP'] == '0.0.0.0' || $this->settings['socketIP'] == '0') {
$this->settings['socketIPforClients']='127.0.0.1';
} else {
$this->settings['socketIPforClients']=$this->settings['socketIP'];
}
}
}
function ImportCSVFiles($dir=false) {
$results=0;
if (!$dir) $dir="/var/spool/cdrtool";
$this->scanFilesForImport($dir);
if ($this->previously_imported_files) {
printf("Skipping %d previously imported files\n",$this->previously_imported_files);
}
$results=0;
foreach (array_keys($this->filesToImport) as $file) {
$importFunction="Import".ucfirst($this->filesToImport[$file]['type']);
printf("Reading file %s\n",$this->filesToImport[$file]['path']);
$results = $this->$importFunction($this->filesToImport[$file]['path'],$this->filesToImport[$file]['reseller']);
$this->logImport($dir,$this->filesToImport[$file]['path'],$this->filesToImport[$file]['watermark'],$results,$this->filesToImport[$file]['reseller']);
}
return $results;
}
function ImportRates($file,$reseller=0) {
if (!$file || !is_readable($file) || !$fp = fopen($file, "r")) return false;
$i=0;
$inserted = 0;
$updated = 0;
$deleted = 0;
printf ("Importing rates from %s for reseller %s:\n",$file,$reseller);
while ($buffer = fgets($fp,1024)) {
$buffer=trim($buffer);
$p = explode($this->delimiter, $buffer);
$ops = trim($p[0]);
$name = trim($p[2]);
$destination = trim($p[3]);
$application = trim($p[4]);
$connectCost = trim($p[5]);
$durationRate = trim($p[6]);
$connectCostIn = trim($p[7]);
$durationRateIn = trim($p[8]);
if ($reseller) {
$reseller_id = intval($reseller);
} else {
$reseller_id = intval($p[1]);
}
if (!is_numeric($destination) && !strstr($destination,'@')) {
// skip invalid destinations
$skipped++;
continue;
}
if (strlen($connectCost) && !is_numeric($connectCost)) {
$skipped++;
continue;
}
if (strlen($durationRate) && !is_numeric($durationRate)) {
$skipped++;
continue;
}
if (!$application) $application='audio';
if ($ops=="1") {
$query=sprintf("insert into billing_rates
(
reseller_id,
name,
destination,
application,
connectCost,
durationRate,
connectCostIn,
durationRateIn
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($application),
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
}
if ($this->db->affected_rows()) {
if ($this->settings['split_rating_table']) {
if ($name) {
$_table='billing_rates_'.$name;
} else {
$_table='billing_rates_default';
}
if (!$this->createRatingTable($name)) {
$query=sprintf("insert into %s
(
id,
reseller_id,
name,
destination,
application,
connectCost,
durationRate,
connectCostIn,
durationRateIn
) values (
LAST_INSERT_ID(),
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($_table),
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($application),
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
}
}
$inserted++;
} else {
$failed++;
}
} else if ($ops=="3") {
$query=sprintf("delete from billing_rates
where
reseller_id = '%s'
and name = '%s'
and destination = '%s'
and application = '%s'",
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($application)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows()) {
if ($this->settings['split_rating_table']) {
if ($name) {
$_table='billing_rates_'.$name;
} else {
$_table='billing_rates_default';
}
$query=sprintf("delete from %s
where reseller_id = '%s'
and name = '%s'
and destination = '%s'
and application = '%s'",
addslashes($_table),
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($application)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
}
}
$deleted++;
}
} else if ($ops=="2") {
$query=sprintf("select * from billing_rates
where name = '%s'
and destination = '%s'
and reseller_id = '%s'
and application = '%s'
",
addslashes($name),
addslashes($destination),
addslashes($reseller_id),
addslashes($application)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) {
$query=sprintf("update billing_rates set
connectCost = '%s',
durationRate = '%s',
connectCostIn = '%s',
durationRateIn = '%s'
where name = '%s'
and destination = '%s'
and reseller_id = '%s'
and application = '%s'
",
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn),
addslashes($name),
addslashes($destination),
addslashes($reseller_id),
addslashes($application)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows()) {
if ($this->settings['split_rating_table']) {
if ($name) {
$_table = 'billing_rates_'.$name;
} else {
$_table = 'billing_rates_default';
}
$query=sprintf("update %s set
connectCost = '%s',
durationRate = '%s',
connectCostIn = '%s',
durationRateIn = '%s'
where name = '%s'
and destination = '%s'
and reseller_id = '%s'
and application = '%s'
",
addslashes($_table),
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn),
addslashes($name),
addslashes($destination),
addslashes($reseller_id),
addslashes($application)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
}
}
$updated++;
}
} else {
$query=sprintf("insert into billing_rates
(
reseller_id,
name,
destination,
application,
connectCost,
durationRate,
connectCostIn,
durationRateIn
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($application),
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows()) {
if ($this->settings['split_rating_table']) {
if ($name) {
$_table='billing_rates_'.$name;
} else {
$_table='billing_rates_default';
}
if (!$this->createRatingTable($name)) {
$query=sprintf("insert into %s
(
id,
reseller_id,
name,
destination,
application
connectCost,
durationRate,
connectCostIn,
durationRateIn
) values (
LAST_INSERT_ID(),
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($_table),
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($application),
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
}
}
$inserted++;
} else {
$failed++;
}
}
} else {
$skipped++;
}
$this->showImportProgress($file);
$i++;
}
if ($i) print "Read $i records\n";
if ($skipped) print "Skipped $skipped records\n";
if ($inserted) print "Inserted $inserted records\n";
if ($updated) print "Updated $updated records\n";
if ($deleted) print "Delete $deleted records\n";
$results=$inserted+$updated+$deleted;
return $results;
}
function ImportRatesHistory($file,$reseller=0) {
if (!$file || !is_readable($file) || !$fp = fopen($file, "r")) return false;
$this->mustReload=true;
$i=0;
$inserted = 0;
$updated = 0;
$deleted = 0;
printf ("Importing rates history from %s for reseller %s:\n",$file,$reseller);
while ($buffer = fgets($fp,1024)) {
$buffer=trim($buffer);
$p = explode($this->delimiter, $buffer);
$ops = trim($p[0]);
$name = trim($p[2]);
$destination = trim($p[3]);
$application = trim($p[4]);
$connectCost = trim($p[5]);
$durationRate = trim($p[6]);
$connectCostIn = trim($p[7]);
$durationRateIn = trim($p[8]);
$startDate = trim($p[9]);
$endDate = trim($p[10]);
if ($reseller) {
$reseller_id = intval($reseller);
} else {
$reseller_id = intval($p[1]);
}
if (!is_numeric($destination) && !strstr($destination,'@')) {
// skip invalid destinations
$skipped++;
continue;
}
if (strlen($connectCost) && !is_numeric($connectCost)) {
$skipped++;
continue;
}
if (strlen($durationRate) && !is_numeric($durationRate)) {
$skipped++;
continue;
}
if (preg_match("/^\d{4}\-{\d{2}\-\d{2}$/",$startDate)) {
$skipped++;
continue;
}
if (preg_match("/^\d{4}\-{\d{2}\-\d{2}$/",$endDate)) {
$skipped++;
continue;
}
if ($ops=="1") {
$query=sprintf("insert into billing_rates_history
(
reseller_id,
name,
destination,
application,
connectCost,
durationRate,
connectCostIn,
durationRateIn,
startDate,
endDate
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($application),
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn),
addslashes($startDate),
addslashes($endDate)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$inserted++;
} else {
$failed++;
}
} else if ($ops=="3") {
$query=sprintf("delete from billing_rates_history
where reseller_id = '%s'
and name = '%s'
and destination = '%s'
and startDate = '%s'
and endDate = '%s'",
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($startDate),
addslashes($endDate)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$deleted++;
}
} else if ($ops=="2") {
$query=sprintf("select * from billing_rates_history
where name = '%s'
and destination = '%s'
and reseller_id = '%s'
and startDate = '%s'
and endDate = '%s'
",
addslashes($name),
addslashes($destination),
addslashes($reseller_id),
addslashes($startDate),
addslashes($endDate)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) {
$query=sprintf("update billing_rates_history set
application = '%s',
connectCost = '%s',
durationRate = '%s',
connectCostIn = '%s',
connectCostIn = '%s'
where name = '%s'
and destination = '%s'
and reseller_id = '%s'
and startDate = '%s'
and endDate = '%s'
",
addslashes($application),
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn),
addslashes($name),
addslashes($destination),
addslashes($reseller_id),
addslashes($startDate),
addslashes($endDate)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$updated++;
}
} else {
$query=sprintf("insert into billing_rates_history
(
reseller_id,
name,
destination,
application,
connectCost,
durationRate,
connectCostIn,
durationRateIn,
startDate,
endDate
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($name),
addslashes($destination),
addslashes($application),
addslashes($connectCost),
addslashes($durationRate),
addslashes($connectCostIn),
addslashes($durationRateIn),
addslashes($startDate),
addslashes($endDate)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$inserted++;
} else {
$failed++;
}
}
} else {
$skipped++;
}
$j++;
if ($j=="10000") {
flush();
$j=0;
}
$this->showImportProgress($file);
$i++;
}
if ($i) print "Read $i records\n";
if ($skipped) print "Skipped $skipped records\n";
if ($inserted) print "Inserted $inserted records\n";
if ($updated) print "Updated $updated records\n";
if ($deleted) print "Delete $deleted records\n";
$results=$inserted+$updated+$deleted;
return $results;
}
function ImportCustomers($file,$reseller=0) {
if (!$file || !is_readable($file) || !$fp = fopen($file, "r")) return false;
$this->mustReload=true;
$i=0;
$inserted = 0;
$updated = 0;
$deleted = 0;
printf ("Importing customers from %s for reseller %s:\n",$file,$reseller);
while ($buffer = fgets($fp,1024)) {
$buffer=trim($buffer);
$p = explode($this->delimiter, $buffer);
$ops = trim($p[0]);
$gateway = trim($p[2]);
$domain = trim($p[3]);
$subscriber = trim($p[4]);
$profile_name1 = trim($p[5]);
$profile_name1_alt = trim($p[6]);
$profile_name2 = trim($p[7]);
$profile_name2_alt = trim($p[8]);
$timezone = trim($p[9]);
if ($reseller) {
$reseller_id = intval($reseller);
} else {
$reseller_id = intval($p[1]);
}
if (strlen($reseller_id) && !is_integer($reseller_id)) {
$skipped++;
continue;
}
if ($ops=="1") {
$query=sprintf("insert into billing_customers
(
reseller_id,
gateway,
domain,
subscriber,
profile_name1,
profile_name2,
timezone,
profile_name1_alt,
profile_name2_alt
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($gateway),
addslashes($domain),
addslashes($subscriber),
addslashes($profile_name1),
addslashes($profile_name2),
addslashes($timezone),
addslashes($profile_name1_alt),
addslashes($profile_name2_alt)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$inserted++;
} else {
$failed++;
}
} else if ($ops=="3") {
$query=sprintf("delete from billing_customers
where gateway = '%s'
and reseller_id = '%s'
and domain = '%s'
and subscriber = '%s'
",
addslashes($gateway),
addslashes($reseller_id),
addslashes($domain),
addslashes($subscriber)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$deleted++;
}
} else if ($ops=="2") {
$query=sprintf("select * from billing_customers
where gateway = '%s'
and reseller_id = '%s'
and domain = '%s'
and subscriber = '%s'
",
addslashes($gateway),
addslashes($reseller_id),
addslashes($domain),
addslashes($subscriber)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) {
$query=sprintf("update billing_customers set
profile_name1 = '%s',
profile_name2 = '%s',
profile_name1_alt = '%s',
profile_name2_alt = '%s',
timezone = '%s'
where gateway = '%s'
and domain = '%s'
and reseller_id = '%s'
and subscriber = '%s'\n",
addslashes($profile_name1),
addslashes($profile_name2),
addslashes($profile_name1_alt),
addslashes($profile_name2_alt),
addslashes($timezone),
addslashes($gateway),
addslashes($domain),
addslashes($reseller_id),
addslashes($subscriber)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows()) {
$updated++;
}
} else {
$query=sprintf("insert into billing_customers
(
reseller_id,
gateway,
domain,
subscriber,
profile_name1,
profile_name2,
timezone,
profile_name1_alt,
profile_name2_alt
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($gateway),
addslashes($domain),
addslashes($subscriber),
addslashes($profile_name1),
addslashes($profile_name2),
addslashes($timezone),
addslashes($profile_name1_alt),
addslashes($profile_name2_alt)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows()) {
$inserted++;
}
}
} else {
$skipped++;
}
$this->showImportProgress($file);
$i++;
}
if ($i) print "Read $i records\n";
if ($skipped) print "Skipped $skipped records\n";
if ($inserted) print "Inserted $inserted records\n";
if ($updated) print "Updated $updated records\n";
if ($deleted) print "Delete $deleted records\n";
$results=$inserted+$updated+$deleted;
return $results;
}
function ImportDestinations($file,$reseller=0) {
if (!$file || !is_readable($file) || !$fp = fopen($file, "r")) return false;
$this->mustReload=true;
$i=0;
$inserted = 0;
$updated = 0;
$deleted = 0;
printf ("Importing destinations from %s for reseller %s:\n",$file,$reseller);
while ($buffer = fgets($fp,1024)) {
$buffer=trim($buffer);
$p = explode($this->delimiter, $buffer);
$ops = trim($p[0]);
$gateway = trim($p[2]);
$domain = trim($p[3]);
$subscriber = trim($p[4]);
$dest_id = trim($p[5]);
$dest_name = trim($p[6]);
$increment = intval($p[7]);
$min_duration = intval($p[8]);
$max_duration = intval($p[9]);
$max_price = trim($p[10]);
if ($reseller) {
$reseller_id = intval($reseller);
} else {
$reseller_id = intval($p[1]);
}
if (!is_numeric($dest_id) && !strstr($dest_id,'@')) {
// skip invalid destinations
$skipped++;
continue;
}
if ($ops=="1") {
$query=sprintf("insert into destinations
(
reseller_id,
gateway,
domain,
subscriber,
dest_id,
dest_name,
increment,
min_duration,
max_duration,
max_price
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($gateway),
addslashes($domain),
addslashes($subscriber),
addslashes($dest_id),
addslashes($dest_name),
addslashes($increment),
addslashes($min_duration),
addslashes($max_duration),
addslashes($max_price)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$inserted++;
} else {
$failed++;
}
} elseif ($ops=="3") {
$query=sprintf("delete from destinations
where gateway = '%s'
and reseller_id = '%s'
and domain = '%s'
and subscriber = '%s'
and dest_id = '%s'
",
addslashes($gateway),
addslashes($reseller_id),
addslashes($domain),
addslashes($subscriber),
addslashes($dest_id)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$deleted++;
}
} elseif ($ops=="2") {
$query=sprintf("select * from destinations
where gateway = '%s'
and reseller_id = '%s'
and domain = '%s'
and subscriber = '%s'
and dest_id = '%s'
",
addslashes($gateway),
addslashes($reseller_id),
addslashes($domain),
addslashes($subscriber),
addslashes($dest_id)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) {
$query=sprintf("update destinations set
dest_name = '%s',
increment = '%s',
min_duration = '%s',
max_duration = '%s',
max_price = '%s'
where gateway = '%s'
and reseller_id = '%s'
and domain = '%s'
and subscriber = '%s'
and dest_id = '%s'
",
addslashes($dest_name),
addslashes($increment),
addslashes($min_duration),
addslashes($max_duration),
addslashes($max_price),
addslashes($gateway),
addslashes($reseller_id),
addslashes($domain),
addslashes($subscriber),
addslashes($dest_id)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows()) {
$updated++;
}
} else {
$query=sprintf("insert into destinations
(
reseller_id,
gateway,
domain,
subscriber,
dest_id,
dest_name,
increment,
min_duration,
max_duration,
max_price
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($gateway),
addslashes($domain),
addslashes($subscriber),
addslashes($dest_id),
addslashes($dest_name),
addslashes($increment),
addslashes($min_duration),
addslashes($max_duration),
addslashes($max_price)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$inserted++;
} else {
$failed++;
}
}
} else {
$skipped++;
}
$this->showImportProgress($file);
$i++;
}
if ($i) print "Read $i records\n";
if ($skipped) print "Skipped $skipped records\n";
if ($inserted) print "Inserted $inserted records\n";
if ($updated) print "Updated $updated records\n";
if ($deleted) print "Delete $deleted records\n";
$results=$inserted+$updated+$deleted;
return $results;
}
function ImportProfiles($file,$reseller=0) {
if (!$file || !is_readable($file) || !$fp = fopen($file, "r")) return false;
$this->mustReload=true;
$i=0;
$inserted = 0;
$updated = 0;
$deleted = 0;
print "Importing Profiles:\n";
while ($buffer = fgets($fp,1024)) {
$buffer=trim($buffer);
$p = explode($this->delimiter, $buffer);
$ops = trim($p[0]);
$profile = trim($p[2]);
$rate1 = trim($p[3]);
$hour1 = trim($p[4]);
$rate2 = trim($p[5]);
$hour2 = trim($p[6]);
$rate3 = trim($p[7]);
$hour3 = trim($p[8]);
$rate4 = trim($p[9]);
$hour4 = trim($p[10]);
if ($reseller) {
$reseller_id = intval($reseller);
} else {
$reseller_id = intval($p[1]);
}
if (!$hour1) $hour1=0;
if (!$hour2) $hour2=0;
if (!$hour3) $hour3=0;
if (!$hour4) $hour4=0;
if ($ops=="1") {
$query=sprintf("insert into billing_profiles
(
reseller_id,
name,
rate_name1,
hour1,
rate_name2,
hour2,
rate_name3,
hour3,
rate_name4,
hour4
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($profile),
addslashes($rate1),
addslashes($hour1),
addslashes($rate2),
addslashes($hour2),
addslashes($rate3),
addslashes($hour3),
addslashes($rate4),
addslashes($hour4)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$inserted++;
} else {
$failed++;
}
} else if ($ops=="3") {
$query=sprintf("delete from billing_profiles
where name = '%s'
and reseller_id= '%s'
",
addslashes($profile),
addslashes($reseller_id)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$deleted++;
}
} else if ($ops=="2") {
$query=sprintf("select * from billing_profiles
where name = '%s'
and reseller_id= '%s'
",
addslashes($profile),
addslashes($reseller_id)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) {
$query=sprintf("update billing_profiles set
rate_name1 = '%s',
rate_name2 = '%s',
rate_name3 = '%s',
rate_name4 = '%s',
hour1 = '%s',
hour2 = '%s',
hour3 = '%s',
hour4 = '%s'
where name = '%s'
and reseller_id= '%s'
\n",
addslashes($rate1),
addslashes($rate2),
addslashes($rate3),
addslashes($rate4),
addslashes($hour1),
addslashes($hour2),
addslashes($hour3),
addslashes($hour4),
addslashes($profile),
addslashes($reseller_id)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows()) {
$updated++;
}
} else {
$query=sprintf("insert into billing_profiles
(
reseller_id,
name,
rate_name1,
hour1,
rate_name2,
hour2,
rate_name3,
hour3,
rate_name4,
hour4
) values (
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s',
'%s'
)",
addslashes($reseller_id),
addslashes($profile),
addslashes($rate1),
addslashes($hour1),
addslashes($rate2),
addslashes($hour2),
addslashes($rate3),
addslashes($hour3),
addslashes($rate4),
addslashes($hour4)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows() >0) {
$inserted++;
} else {
$failed++;
}
}
}
$this->showImportProgress($file);
$i++;
}
if ($i) print "Read $i records\n";
if ($inserted) print "Inserted $inserted records\n";
if ($updated) print "Updated $updated records\n";
if ($deleted) print "Delete $deleted records\n";
$results=$inserted+$updated+$deleted;
return $results;
}
function LoadRatingTables () {
$log=sprintf("Memory usage: %0.2fMB, memory limit: %sB",memory_get_usage()/1024/1024,ini_get('memory_limit'));
syslog(LOG_NOTICE, $log);
$loaded['profiles'] = $this->LoadProfilesTable();
$loaded['ratesHistory'] = $this->LoadRatesHistoryTable();
$loaded['holidays'] = $this->LoadHolidaysTable();
$loaded['enumTlds'] = $this->LoadENUMtldsTable();
foreach(array_keys($loaded) as $_load) {
syslog(LOG_NOTICE, "Loaded $loaded[$_load] $_load into memory");
}
$log=sprintf("Memory usage: %0.2fMB, memory limit: %sB",memory_get_usage()/1024/1024,ini_get('memory_limit'));
syslog(LOG_NOTICE, $log);
return $loaded;
}
function LoadENUMtldsTable() {
$query="select * from billing_enum_tlds";
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$i=0;
$rows=$this->db->num_rows();
while($this->db->next_record()) {
if ($this->db->Record['enum_tld']) {
$i++;
$_app=$this->db->Record['application'];
if (!$_app) $_app='audio';
$_ENUMtlds[$this->db->Record['enum_tld']]=
array(
"discount" => $this->db->Record['discount'],
"e164_regexp" => $this->db->Record['e164_regexp']
);
}
}
$this->ENUMtlds = $_ENUMtlds;
$this->ENUMtldsCount = $i;
return $i;
}
function LoadRatesHistoryTable() {
$query="select *,
UNIX_TIMESTAMP(startDate) as startDateTimestamp,
UNIX_TIMESTAMP(endDate) as endDateTimestamp
from billing_rates_history
order by name ASC,destination ASC,startDate DESC";
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$i=0;
$rows=$this->db->num_rows();
while($this->db->next_record()) {
if ($this->db->Record['name'] && $this->db->Record['destination']) {
$i++;
$_app=$this->db->Record['application'];
if (!$_app) $_app='audio';
$_rates[$this->db->Record['name']][$this->db->Record['destination']][$_app][$this->db->Record['id']]=
array(
"connectCost" => $this->db->Record['connectCost'],
"durationRate" => $this->db->Record['durationRate'],
"connectCostIn" => $this->db->Record['connectCostIn'],
"durationRateIn" => $this->db->Record['durationRateIn'],
"increment" => $this->db->Record['increment'],
"min_duration" => $this->db->Record['min_duration'],
"startDate" => $this->db->Record['startDateTimestamp'],
"endDate" => $this->db->Record['endDateTimestamp']
);
}
}
$this->ratesHistory=$_rates;
$this->ratesHistoryCount=$i;
return $i;
}
function LoadProfilesTable() {
$query="select * from billing_profiles order by name";
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$i=0;
while($this->db->next_record()) {
$i++;
if ($this->db->Record['name'] && $this->db->Record['hour1'] > 0 ) {
$_profiles[$this->db->Record['name']]=
array(
"rate_name1" => $this->db->Record['rate_name1'],
"hour1" => $this->db->Record['hour1'],
"rate_name2" => $this->db->Record['rate_name2'],
"hour2" => $this->db->Record['hour2'],
"rate_name3" => $this->db->Record['rate_name3'],
"hour3" => $this->db->Record['hour3'],
"rate_name4" => $this->db->Record['rate_name4'],
"hour4" => $this->db->Record['hour4'],
);
}
}
$this->profiles=$_profiles;
return $i;
}
function LoadHolidaysTable() {
$query="select * from billing_holidays order by day";
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$i=0;
while($this->db->next_record()) {
if ($this->db->Record['day']) {
$i++;
$_holidays[$this->db->Record['day']]++;
}
}
$this->holidays=$_holidays;
return $i;
}
function checkRatingEngineConnection () {
if ($this->settings['socketIPforClients'] && $this->settings['socketPort'] &&
$fp = fsockopen ($this->settings['socketIPforClients'], $this->settings['socketPort'], $errno, $errstr, 2)) {
fclose($fp);
return true;
}
return false;
}
function showCustomers($filter) {
return true;
foreach (array_keys($this->customers) as $key) {
if (strlen($filter)) {
if (preg_match("/$filter/",$key)) {
$customers=$customers.$key."\n";
}
} else {
$customers=$customers.$key."\n";
}
}
return $customers;
}
function showProfiles() {
foreach (array_keys($this->profiles) as $key) {
$profiles=$profiles.$key."\n";
}
return $profiles;
}
function showENUMtlds() {
foreach (array_keys($this->ENUMtlds) as $key) {
$ENUMtlds=$ENUMtlds.$key."\n";
}
return $ENUMtlds;
}
function scanFilesForImport($dir) {
$import_dirs[$this->cvs_import_dir]=array('path'=>$this->cvs_import_dir,
'reseller' => 0
);
if ($handle = opendir($this->cvs_import_dir)) {
while (false !== ($filename = readdir($handle))) {
$reseller=0;
if ($filename == "." || $filename == "..") continue;
$fullPath=$this->cvs_import_dir."/".$filename;
if (is_dir($fullPath) && is_numeric($filename)) {
$reseller=$filename;
$import_dirs[$fullPath]=array('path' => $fullPath,
'reseller'=> $reseller
);
}
}
}
foreach (array_keys($import_dirs) as $_dir) {
if ($handle = opendir($_dir)) {
while (false !== ($filename = readdir($handle))) {
if ($filename != "." && $filename != "..") {
foreach ($this->importFilesPatterns as $_pattern) {
if (strstr($filename,$_pattern) && preg_match("/\.csv$/",$filename)) {
$fullPath=$_dir."/".$filename;
if ($content=file_get_contents($fullPath)) {
$watermark=$filename."-".md5($content);
if ($this->hasFileBeenImported($filename,$watermark)) {
$this->previously_imported_files++;
break;
}
$this->filesToImport[$filename]=array( 'name' => $filename,
'watermark' => $watermark,
'type' => $_pattern,
'path' => $fullPath,
'reseller' => $import_dirs[$_dir]['reseller']
);
}
break;
}
}
}
}
}
}
}
function hasFileBeenImported($filename,$watermark) {
$query=sprintf("select * from log where url = '%s'\n",$watermark);
if ($this->db->query($query)) {
if ($this->db->num_rows()) {
$this->db->next_record();
/*
$log=sprintf ("File %s has already been imported at %s.\n",$filename,$this->db->f('date'));
syslog(LOG_NOTICE, $log);
print $log;
*/
return true;
} else {
return false;
}
} else {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
}
function logImport($dir,$filename,$watermark,$results=0,$reseller=0) {
$query=sprintf("insert into log (date,login,ip,url,results,description,datasource,reseller_id)
values (NOW(),'ImportScript','localhost','%s','%s','Imported %s','%s',%d)",
$watermark,$results,$filename,$dir,$reseller);
$log=sprintf ("Imported file %s, %d records have been affected\n",$filename,$results);
syslog(LOG_NOTICE, $log);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
}
function showImportProgress ($filename='unspecified',$increment=5000) {
$this->importIndex++;
if ($this->importIndex == $increment) {
printf ("Loaded %d records from %s\n",$this->importIndex,$filename);
flush();
$this->importIndex=0;
}
}
function createRatingTable($name) {
if ($name) {
$table='billing_rates_'.$name;
} else {
$table='billing_rates_default';
}
$query=sprintf("create table %s select * from billing_rates where name = '%s'\n",$table,$name);
if ($this->db->query($query)) {
$query=sprintf("alter table %s add index rate_idx (name)",$table);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
}
$query=sprintf("alter table %s add index destination_idx (destination)",$table);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
}
printf ("Created table %s\n",$table);
return true;
} else {
return false;
}
}
function splitRatingTable() {
$query="select count(*) as c from billing_rates";
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$this->db->next_record();
$rows=$this->db->f('c');
$query="select distinct(name) from billing_rates order by name ASC";
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
while ($this->db->next_record()) {
$rate_names[]=$this->db->f('name');
}
foreach ($rate_names as $name) {
if (!$name) $name='default';
$table="billing_rates_".$name;
$query=sprintf("drop table if exists %s",addslashes($table));
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$query=sprintf("create table %s select * from billing_rates where name = '%s'\n",$table,$name);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
} else {
$query=sprintf("alter table %s add index rate_idx (name)",$table);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$query=sprintf("alter table %s add index destination_idx (destination)",$table);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$query=sprintf("select count(*) as c from %s",$table);
$this->db->query($query);
$this->db->next_record();
$records=$this->db->f('c');
$created_records=$created_records+$records;
$progress=100*$created_records/$rows;
printf ("Created table %s with %s records (%.1f %s)\n",$table,$records,$progress,'%');
}
}
return true;
}
function updateTable() {
global $auth;
$loginname=$auth->auth["uname"];
foreach ($this->web_elements as $_el) {
${$_el}= $_REQUEST[$_el];
}
if (!$table) return false;
if ($this->readonly) {
return true;
}
// Init table structure
if (!is_array($this->tables[$table]['exceptions'])) $this->tables[$table]['exceptions']=array();
if (!is_array($this->tables[$table]['keys'])) $this->tables[$table]['keys']=array();
if (!is_array($this->tables[$table]['fields'])) $this->tables[$table]['fields']=array();
$metadata = $this->db->metadata($table="$table");
$cc = count($metadata);
// end init table structure
if ($web_task =="update") {
$affected_rows=0;
if ($subweb_task == "Update") {
if ($this->checkValues($table,$_REQUEST)) {
$update_set='';
$k=0;
while ($k < $cc ) {
$k++;
$Fname=$metadata[$k]['name'];
if (!$Fname) continue;
$value=$_REQUEST[$Fname];
if ($this->tables[$table]['fields'][$Fname]['readonly']) {
continue;
}
if (in_array($Fname,$this->tables[$table]['exceptions'])) {
continue;
}
if (in_array($Fname,$this->tables[$table]['keys'])) {
continue;
}
if ($kkk > 0) {
$comma = ",";
} else {
$comma = "";
}
if (!$this->tables[$table]['skip_math'] && preg_match("/^([\+\-\*\/])(.*)$/",$value,$sign)) {
$update_set .= $comma.$Fname."= ROUND(".$Fname. " ".$sign[1]. "'".$sign[2]."')";
} else {
$update_set .= $comma.$Fname."='".$value."'";
}
$kkk++;
}
$k=0;
while ($k < $cc ) {
if ($metadata[$k]['name'] == 'change_date') {
$update_set .= sprintf("%s %s = NOW() ",$comma,$metadata[$k]['name']);
break;
}
$k++;
}
$log_entity=" id = $id ";
$where = " id = '".$id."' and $this->whereResellerFilter";
if ($table == "billing_rates") {
if ($this->settings['split_rating_table']) {
$rate_table_affected=array();
$query_r="select distinct (name) from billing_rates where". $where;
if ($this->db->query($query_r)) {
while($this->db->next_record()) {
$rate_tables_affected[]='billing_rates_'.$this->db->f('name');
}
} else {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
}
}
} else if ($table=="prepaid") {
register_shutdown_function("unLockTables",$this->db);
if ($this->db->query("lock table prepaid write")) {
$query_q=sprintf("select * from prepaid where account = '%s'",addslashes($account));
if ($this->db->query($query_q) && $this->db->num_rows()) {
$this->db->next_record();
$old_balance=$this->db->f('balance');
}
$this->db->query("unlock tables");
}
}
$query = sprintf("update %s set %s where %s " ,
$table,
$update_set,
$where
);
if ($this->db->query($query)) {
$affected_rows=$this->db->affected_rows();
if ($affected_rows) {
if ($table=="prepaid") {
list($username,$domain)=explode("@",$account);
$value=$balance-$old_balance;
if (floatval($balance) != floatval($old_balance)) {
$query=sprintf("insert into prepaid_history
(username,domain,action,description,value,balance,date,reseller_id)
values
('%s','%s','Set balance','Manual update','%s','%s',NOW(),%d)",
addslashes($username),
addslashes($domain),
addslashes($value),
addslashes($balance),
$this->CDRTool['filter']['reseller']
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
}
}
} else if ($table=='billing_rates') {
if ($this->settings['split_rating_table']) {
foreach ($rate_tables_affected as $extra_rate_table) {
$query_u = sprintf("update %s set %s where %s ",
$extra_rate_table,
$update_set,
$where
);
if (!$this->db->query($query_u)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query_u,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
}
}
}
}
if (in_array($table,$this->requireReload)) {
if (!$this->db->query("update settings setting set var_value= '1' where var_name = 'reloadRating'")){
printf ("<font color=red>Database error: %s (%s)</font>",$this->db->Error,$this->db->Errno);
}
}
}
} else {
printf ("<font color=red>Database error for query '%s': %s (%s)</font>",$query,$this->db->Error,$this->db->Errno);
}
} else {
print "<p>Correct the values and try again.";
}
} elseif ($subweb_task == "Update selection") {
$k=0;
$kkk=0;
$update_set='';
while ($k < $cc ) {
$k++;
$Fname=$metadata[$k]['name'];
$value=$_REQUEST[$Fname];
if (!strlen($value)) continue;
if ($this->tables[$table]['fields'][$Fname]['readonly']) {
continue;
}
if (in_array($Fname,$this->tables[$table]['exceptions'])) {
continue;
}
if (in_array($Fname,$this->tables[$table]['keys'])) {
continue;
}
if ($kkk > 0) {
$comma = ",";
} else {
$comma="";
}
if ($value == "NULL") {
$value="";
}
if (preg_match("/^([\+\-\*\/])(.*)$/",$value,$sign)) {
$update_set .= $comma.$Fname." = ROUND(".$Fname. " ".$sign[1]. "'".$sign[2]."')";
} else {
$update_set .= $comma.$Fname." = '".$value."'";
}
$kkk++;
}
$where = $this->whereResellerFilter;
if ($kkk) {
// reconstruct where clause to apply all changes to selection
// build where clause
// Search build for each field
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$table]['exceptions'])) {
$f_name="search_".$Fname;
$value=$_REQUEST[$f_name];
if (preg_match("/^([<|>]+)(.*)$/",$value,$likes)) {
$like=$likes[1];
$likewhat=$likes[2];
$quotes="";
} else {
$like="like";
$likewhat=$value;
$quotes="'";
}
if (strlen($value)) {
$where .= " and $Fname $like $quotes".$likewhat."$quotes";
$t++;
}
}
$j++;
}
if ($table == 'billing_rates') {
if ($this->settings['split_rating_table']) {
$rate_table_affected=array();
$query_r="select distinct (name) from billing_rates where". $where;
if ($this->db->query($query_r)) {
while($this->db->next_record()) {
$rate_tables_affected[]='billing_rates_'.$this->db->f('name');
}
} else {
printf ("<font color=red>Database error: %s (%s)</font>",$this->db->Error,$this->db->Errno);
}
}
}
$query = sprintf("update %s set %s where %s " ,
$table,
$update_set,
$where
);
if ($this->db->query($query)) {
$affected_rows=$this->db->affected_rows();
if ($affected_rows) {
if ($table == 'billing_rates') {
if ($this->settings['split_rating_table']) {
foreach ($rate_tables_affected as $extra_rate_table) {
$query_u = sprintf("update %s set %s where %s ",
$extra_rate_table,
$update_set,
$where
);
if (!$this->db->query($query_u)) {
printf ("<font color=red>Database error for %s: %s (%s)</font>",$query_u,$this->db->Error,$this->db->Errno);
}
}
}
}
if (in_array($table,$this->requireReload)) {
$this->db->query("update settings setting set var_value= '1' where var_name = 'reloadRating'");
}
}
} else {
printf ("<font color=red>Database error: %s</font>",$this->db->Error);
}
}
} elseif ($subweb_task == "Delete selection") {
if ($confirmDelete) {
// reconstruct where clause to apply all changes to selection
// build where clause
// Search build for each field
$where = $this->whereResellerFilter;
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$table]['exceptions'])) {
$f_name="search_".$Fname;
$value=$_REQUEST[$f_name];
if (preg_match("/^([<|>]+)(.*)$/",$value,$likes)) {
$like=$likes[1];
$likewhat=$likes[2];
$quotes="";
} else {
$like="like";
$likewhat=$value;
$quotes="'";
}
if (strlen($value)) {
$where .= " and $Fname $like $quotes".$likewhat."$quotes";
$t++;
}
}
$j++;
}
if ($table == 'billing_rates') {
if ($this->settings['split_rating_table']) {
$rate_table_affected=array();
$query_r="select distinct (name) from billing_rates where". $where;
if ($this->db->query($query_r)) {
while($this->db->next_record()) {
$rate_tables_affected[]='billing_rates_'.$this->db->f('name');
}
} else {
printf ("<font color=red>Database error: %s (%s)</font>",$this->db->Error,$this->db->Errno);
}
}
}
$query="delete from $table where $where";
if ($this->db->query($query)) {
$affected_rows=$this->db->affected_rows();
if ($affected_rows) {
if ($table == 'billing_rates') {
if ($this->settings['split_rating_table']) {
foreach ($rate_tables_affected as $extra_rate_table) {
$query_u = sprintf("delete from %s where %s ",
$extra_rate_table,
$where
);
if (!$this->db->query($query_u)) {
printf ("<font color=red>Database error for %s: %s (%s)</font>",$query_u,$this->db->Error,$this->db->Errno);
}
}
}
}
if (in_array($table,$this->requireReload)) {
$this->db->query("update settings setting set var_value= '1' where var_name = 'reloadRating'");
}
}
} else {
printf ("<font color=red>Database error: %s</font>",$this->db->Error);
}
unset($confirmDelete);
} else {
print "<p><font color=blue>";
print "Please confirm the deletion by pressing the Delete button again. ";
print "</font>";
print "<input type=hidden name=confirmDelete value=1>";
}
} elseif ($subweb_task == "Copy rate" && strlen($fromRate) && strlen($toRate)) {
$toRate=preg_replace("/%/","",$toRate);
if ($confirmCopy) {
if ($toRate == 'history') {
$values=sprintf("
(reseller_id,name,destination,application,connectCost,durationRate,connectCostIn,durationRateIn,startDate,endDate)
select
billing_rates.reseller_id,
'%s',
billing_rates.destination,
billing_rates.application,
billing_rates.connectCost,
billing_rates.durationRate,
billing_rates.connectCostIn,
billing_rates.durationRateIn,
NOW(),
NOW()
from billing_rates ",
$fromRate);
} else {
$values=sprintf("
(reseller_id,name,destination,application,connectCost,durationRate,connectCostIn,durationRateIn)
select
billing_rates.reseller_id,
'%s',
billing_rates.destination,
billing_rates.application,
billing_rates.connectCost,
billing_rates.durationRate,
billing_rates.connectCostIn,
billing_rates.durationRateIn
from billing_rates ",
$toRate);
}
$where = $this->whereResellerFilter;
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$table]['exceptions'])) {
$f_name="search_".$Fname;
$value=$_REQUEST[$f_name];
if (preg_match("/^([<|>]+)(.*)$/",$value,$likes)) {
$like=$likes[1];
$likewhat=$likes[2];
$quotes="";
} else {
$like="like";
$likewhat=$value;
$quotes="'";
}
if (strlen($value)) {
$where .= " and $Fname $like $quotes".$likewhat."$quotes";
$t++;
}
}
$j++;
}
if ($toRate == 'history') {
$query="insert into billing_rates_history $values where $where";
} else {
$query="insert into billing_rates $values where $where";
}
if ($this->db->query($query)) {
$affected_rows=$this->db->affected_rows();
if ($affected_rows) {
print "$affected_rows rates copied. ";
if ($table == 'billing_rates') {
if ($this->settings['split_rating_table']) {
$query=sprintf("create table billing_rates_%s select * from billing_rates where %s ",
$toRate,
$where
);
if (!$this->db->query($query)) {
printf ("<font color=red>Database error for %s: %s (%s)</font>",$query,$this->db->Error,$this->db->Errno);
}
}
}
if (in_array($table,$this->requireReload)) {
$this->db->query("update settings setting set var_value= '1' where var_name = 'reloadRating'");
}
}
if ($toRate == 'history') {
// Switch to history
$table = 'billing_rates_history';
// Init table structure
$this->tables[$table]['exceptions']= $this->tables[$table]['exceptions'];
$this->tables[$table]['keys'] = $this->tables[$table]['keys'];
$this->tables[$table]['fields'] = $this->tables[$table]['fields'];
$metadata = $this->db->metadata($table="$table");
$cc = count($metadata);
// end init table structure
}
unset($confirmCopy);
} else {
printf ("<font color=red>Database error: %s</font>",$this->db->Error);
}
$log_entity="rate=$toRate";
} else {
print "<p><font color=blue>";
print "Please confirm the copy of rate $fromRate to $toRate. ";
print "</font>";
}
} elseif ($subweb_task == "Insert") {
//print "<h3>Insert</h3>";
if ($this->checkValues($table,$_REQUEST)) {
$query="insert into $table ( ";
$k=1;
$kkk=0;
while ($k < $cc ) {
$Fname=$metadata[$k]['name'];
if (!in_array($Fname,$this->tables[$table]['exceptions']) ) {
if ($kkk > 0) {
$comma = ",";
} else {
$comma="";
}
$query .= $comma.$Fname;
$kkk++;
}
$k++;
}
$query .= ") values ( ";
$k=1;
$kkk=0;
while ($k < $cc ) {
$Fname=$metadata[$k]['name'];
$value=$_REQUEST[$Fname];
if (!in_array($Fname,$this->tables[$table]['exceptions']) ) {
if ($kkk > 0) {
$comma = ",";
} else {
$comma="";
}
if ($Fname == 'reseller_id' && $this->CDRTool['filter']['reseller']) {
$query .= $comma."'".$this->CDRTool['filter']['reseller']."'";
} else {
$query .= $comma."'".$value."'";
}
$kkk++;
}
$k++;
}
$query .= ") ";
$k=1;
while ($k < $cc ) {
$Fname=$metadata[$k]['name'];
$value=$_REQUEST[$Fname];
if (in_array($Fname,$this->tables[$table]['keys']) ) {
if ($value == "") {
$Fname_print_insert=substr($Fname,4);
print "$Fname_print_insert = ???? <br>";
$empty_insert=1;
}
}
$k++;
}
if (!$empty_insert) {
if ($this->db->query($query)) {
$affected_rows=$this->db->affected_rows();
if ($affected_rows) {
$this->db->query("select LAST_INSERT_ID() as lid");
$this->db->next_record();
$log_entity=sprintf("id=%s",$this->db->f('lid'));
if (in_array($table,$this->requireReload)) {
$this->db->query("update settings setting set var_value= '1' where var_name = 'reloadRating'");
}
}
} else {
printf ("<font color=red>Database error for query %s: %s (%s)</font>",$query,$this->db->Error,$this->db->Errno);
}
} else {
print "<font color=red>
Error: The insert statement contains an empty key!
</font>
";
}
} else {
print "<p>Correct the values and try again.";
}
} elseif ($subweb_task == "Delete") {
if ($confirmDelete) {
$query="delete from $table where id = '$id' and $this->whereResellerFilter ";
if ($this->db->query($query)) {
$affected_rows=$this->db->affected_rows();
if ($affected_rows && in_array($table,$this->requireReload)) {
$this->db->query("update settings setting set var_value= '1' where var_name = 'reloadRating'");
}
$log_entity=sprintf("id=%s",$id);
} else {
printf ("<font color=red>Database error: %s</font>",$this->db->Error);
}
unset($confirmDelete);
} else {
$idForDeletion=$id;
print "<p><font color=blue>";
print "Please confirm the deletion by pressing the Delete button again. ";
print "</font>";
print "<input type=hidden name=confirmDelete value=1>";
}
} elseif ($subweb_task == "Delete session" && $sessionId && $table=='prepaid') {
$query=sprintf("select active_sessions from %s where id = %d and %s",$table,$id,$this->whereResellerFilter);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
}
if (!$this->db->num_rows()) return;
$this->db->next_record();
if (strlen($this->db->f('active_sessions'))) {
// remove session
$active_sessions=array();
$old_active_sessions = json_decode($this->db->f('active_sessions'),true);
if (!count($old_active_sessions)) return;
foreach (array_keys($old_active_sessions) as $_key) {
if ($_key==$sessionId) continue;
$active_sessions[$_key]=$old_active_sessions[$_key];
}
} else {
$active_sessions=array();
}
$query=sprintf("update %s
set active_sessions = '%s',
session_counter = %d
where id = %d",
$table,
addslashes(json_encode($active_sessions)),
count($active_sessions),
addslashes($id)
);
if ($this->db->query($query)) {
return 1;
} else {
$log=sprintf ("Database error for %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
print $log;
return 0;
}
}
if ($affected_rows && $table!="prepaid") {
$log_query=sprintf("insert into log
(date,login,ip,datasource,results,description,reseller_id)
values (NOW(),'%s','%s','Rating','%d','%s in table %s %s',%d)",
addslashes($loginname),
addslashes($_SERVER['REMOTE_ADDR']),
addslashes($affected_rows),
addslashes($subweb_task),
addslashes($table),
addslashes($log_entity),
$this->CDRTool['filter']['reseller']
);
$this->db->query($log_query);
}
}
}
function showTable() {
$PHP_SELF=$_SERVER['PHP_SELF'];
foreach ($this->web_elements as $_el) {
${$_el}= $_REQUEST[$_el];
}
if ($this->table == 'prepaid_cards') {
print "<p>
<a href=prepaid_cards.phtml>Prepaid card generator</a>";
}
// Init table structure
if (!is_array($this->tables[$this->table]['exceptions'])) $this->tables[$this->table]['exceptions']=array();
if (!is_array($this->tables[$this->table]['keys'])) $this->tables[$this->table]['keys']=array();
if (!is_array($this->tables[$this->table]['fields'])) $this->tables[$this->table]['fields']=array();
if ($this->table=='prepaid' && strlen($_REQUEST['search_session_counter'])) {
$this->readonly=true;
}
if ($this->readonly) {
$this->tables[$this->table]['readonly']=1;
}
$metadata = $this->db->metadata($this->table);
$cc = count($metadata);
// end init table structure
// delimiter for exporting records
if ($this->settings['csv_delimiter']) {
$delimiter=$this->settings['csv_delimiter'];
} else {
$delimiter=",";
}
$query=sprintf("select count(*) as c from %s where %s",
$this->table,
$this->whereResellerFilter);
$t=0;
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
$f_name="search_".$Fname;
$value=$_REQUEST[$f_name];
if (preg_match("/^([<|>]+)(.*)$/",$value,$likes)) {
$like=$likes[1];
$likewhat=$likes[2];
$quotes="";
} else {
$like="like";
$likewhat=$value;
$quotes="'";
}
if (strlen($value)) {
$where.=" and $Fname $like $quotes".$likewhat."$quotes";
$t++;
}
}
$j++;
}
$query .= $where;
$this->db->query($query);
$this->db->next_record();
$rows=$this->db->Record[0];
if (!$export) {
print "
<table border=0 align=center>
<tr><td>
";
if ($rows == 0) {
print "No records found. ";
} else {
print "$selectie $rows records found. ";
}
if ($this->settings['socketIPforClients'] && $this->settings['socketPort']) {
if ($ReloadRatingTables) {
reloadRatingEngineTables();
} else {
$this->db->query("select var_value from settings where var_name = 'reloadRating' and var_value='1'");
if ($this->db->num_rows()) {
print " | <a href=rating_tables.phtml?ReloadRatingTables=1&table=$this->table><font color=red>Reload rating tables</font></a>";
}
}
$engineAddress=$this->settings['socketIPforClients'].":".$this->settings['socketPort'];
if ($this->checkRatingEngineConnection()) {
print " | <font color=green>Rating engine running at $engineAddress</font>";
} else {
print " | <font color=red>Cannot connect to rating engine $engineAddress</font>";
}
}
print " | <a href=doc/RATING.txt target=rating_help>Rating documentation</a>";
print "
</td>
</tr>
</table>
";
} else {
$this->maxrowsperpage=10000000;
}
if (!$next) {
$i=0;
$next=0;
} else {
$i=$next;
}
$j=0;
$z=0;
if ($rows > $this->maxrowsperpage) {
$maxrows=$this->maxrowsperpage+$next;
if ($maxrows > $rows) {
$maxrows=$rows;
$prev_rows=$maxrows;
}
} else {
$maxrows=$rows;
}
if (!$order && $this->tables[$this->table]['order']) {
$order=sprintf(" order by %s ",$this->tables[$this->table]['order']);
}
$query=sprintf("select * from %s where (1=1) %s and %s %s limit %s, %s",
$this->table,
$where,
$this->whereResellerFilter,
$order,
$i,
$this->maxrowsperpage
);
//print $query;
$this->db->query($query);
$num_fields=$this->db->num_fields();
$k=0;
if (!$export) {
print "
<table border=0 class=border align=center width=100%>
<tr bgcolor=lightgrey>
<td></td>
";
}
while ($k < $cc) {
$th=$metadata[$k]['name'];
if (!in_array($th,$this->tables[$this->table]['exceptions']) ) {
if ($this->tables[$this->table]['fields'][$th]['name']) {
$th=$this->tables[$this->table]['fields'][$th]['name'];
} else {
$th=ucfirst($th);
}
if (!$export) {
print "<td class=border><b>$th</b></td>";
} else {
if ($k) {
printf ("%s%s",$delimiter,$th);
} else {
print "Ops";
}
}
$t_columns++;
}
$k++;
}
if ($export) {
print "\n";
}
if (!$export) {
print "
<td class=border><b>Action</b></td>
</tr>";
$t_columns=$t_columns+2;
// SEARCH FORM
print "
<tr>
<td class=border colspan=$t_columns>
Use _ to match one character and % to match any. Use > or <
to find greater or smaller values.</td>
</tr>
";
// Search form
print "
<form action=$PHP_SELF method=post name=rating>
<input type=hidden name=web_task value=Search>
<tr>
<td>&nbsp; </td>";
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
$SEARCH_NAME="search_".$Fname;
$value=$_REQUEST[$SEARCH_NAME];
if ($value != "") {
$selection_made=1;
}
$maxlength=$size;
if ($this->tables[$this->table]['fields'][$Fname]['size']) {
$field_size=$this->tables[$this->table]['fields'][$Fname]['size'];
} else {
$field_size=$el_size;
}
if (!in_array($Fname,$this->tables[$this->table]['keys']) ) {
print "<td><input type=text size=$field_size maxlength=$maxlength name=search_$Fname value=\"$value\"></td>";
} else {
print "<td></td>";
}
}
$j++;
}
printf("
<script type=\"text/JavaScript\">
function jumpMenu(){
location.href=\"%s?table=\" + document.rating.table.options[document.rating.table.selectedIndex].value;
}
</script>",
$PHP_SELF
);
print "
<td>
";
printf("<select name='table' onChange=\"jumpMenu('this.form')\">\n");
$selected_table[$this->table]="selected";
foreach (array_keys($this->tables) as $tb) {
$sel_name=$this->tables[$tb]['name'];
print "<option value=$tb $selected_table[$tb]>$sel_name";
}
print "
</select>
<input type=submit name=subweb_task value=Search>
</form>
<form action=$PHP_SELF method=post target=export>
<input type=hidden name=export value=1>
";
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
$SEARCH_NAME="search_".$Fname;
$value=$_REQUEST[$SEARCH_NAME];
print "<input type=hidden name=search_$Fname value=\"$value\">";
}
$j++;
}
if ($this->table!=='prepaid_cards' ) {
printf ("
<input type=hidden name=table value=%s>
<input type=submit value=\"Export %s\">
",$this->table,$this->csv_export[$this->table]);
}
print "
</form>
";
if ($this->csv_import[$this->table]) {
print "
<form action=$PHP_SELF method='post' enctype='multipart/form-data'>
<input type=hidden name=import value=1>
";
printf ("
<input type='hidden' name=table value=%s>
<input type='submit' value=\"Import\">
<input type='hidden' name='MAX_FILE_SIZE' value=1024000>
<input type='file' name='%s'>
",$this->table,$this->table
);
print "
</form>
";
}
print "
</td>
</tr>
";
print "
<tr>
<td colspan=$t_columns><hr noshade size=2></td>
</tr>
";
if ($selection_made && !$this->tables[$this->table]['readonly']) {
// Update all form
print "
<tr><td class=border colspan=$t_columns>
Use + or - to add/substract from curent values.
Use * or / to multiply/divide curent values.</td>
</tr>";
$j=0;
print "
<form action=$PHP_SELF method=post>
<input type=hidden name=web_task value=update>
<input type=hidden name=next value=$next>
<tr>
<td>&nbsp;</td>";
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if ($this->tables[$this->table]['fields'][$Fname]['size']) {
$field_size=$this->tables[$this->table]['fields'][$Fname]['size'];
} else {
$field_size=$el_size;
}
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
if (!in_array($Fname,$this->tables[$this->table]['keys']) ) {
print "<td><input type=text size=$field_size maxlength=$size name=$Fname></td>";
} else {
print "<td></td>";
}
}
$j++;
}
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
$SEARCH_NAME="search_".$Fname;
$value=$_REQUEST[$SEARCH_NAME];
print "<input type=hidden name=search_$Fname value=\"$value\">";
}
$j++;
}
if ($subweb_task=="Delete selection" && !$confirmDelete) {
print "<td bgcolor=lightgrey>";
print "<input type=hidden name=confirmDelete value=1>";
print "<input type=submit name=subweb_task value=\"Delete selection\">";
print " ($rows records)";
} else if (!$this->tables[$this->table]['readonly']){
if ($this->table == "billing_rates" && strlen($_REQUEST['search_name'])) {
if ($subweb_task=="Copy rate" && !$confirmCopy) {
print "<td bgcolor=lightgrey>";
print "<input type=hidden name=confirmCopy value=1>";
} else {
print "<td>";
print "
<input type=submit name=subweb_task value=\"Update selection\">
<input type=submit name=subweb_task value=\"Delete selection\">
<br>";
}
print "
<input type=submit name=subweb_task value=\"Copy rate\">";
printf (" id %s to",$_REQUEST['search_name']);
$query=sprintf("select distinct(name) as name
from billing_rates where
name like '%s'
order by name DESC
limit 1",$_REQUEST['search_name']);
$this->db1->query($query);
$this->db1->next_record();
$_rateName=$this->db1->f('name');
$_rateName=preg_replace("/%/","",$_rateName);
if (preg_match("/^(.*)_(\d+)$/",$_rateName,$m)) {
$_idx=$m[2]+1;
$newRateName=$m[1]."_".$_idx;
} else {
$newRateName=$_rateName."_1";
}
printf ("<input type=hidden name=fromRate value=\"%s\">",$_REQUEST['search_name']);
$selected_newtable[$toRate]='selected';
printf ("<select name=toRate>
<option value=\"%s\" %s>Rate id %s
<option value=history %s>Rate history table
</select>",
$newRateName,
$selected_newtable[$newRateName],
$newRateName,
$selected_newtable['history']
);
} else {
print "<td>";
print "
<input type=submit name=subweb_task value=\"Update selection\">
<input type=submit name=subweb_task value=\"Delete selection\">
<br>";
}
}
print "
<td>
<input type=hidden name=table value=$this->table>
<input type=hidden name=search_text value=\"$search_text\">
</td>
</tr>
</form>
";
} else if (!$this->tables[$this->table]['readonly']){
// Insert form
$j=0;
print "
<form action=$PHP_SELF method=post>
<input type=hidden name=web_task value=update>
<input type=hidden name=next value=$next>
<tr>
<td>&nbsp; </td>
";
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if ($this->tables[$this->table]['fields'][$Fname]['size']) {
$field_size=$this->tables[$this->table]['fields'][$Fname]['size'];
} else {
$field_size=$el_size;
}
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
if (!in_array($Fname,$this->tables[$this->table]['keys']) ) {
print "<td><input type=text size=$field_size maxlength=$size name=$Fname></td>";
} else {
print "<td></td>";
}
}
$j++;
}
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
$SEARCH_NAME="search_".$Fname;
$value=$_REQUEST[$SEARCH_NAME];
print "<input type=hidden name=search_$Fname value=\"$value\">";
}
$j++;
}
print "
<td class=border>
<input type=hidden name=table value=\"$this->table\">
<input type=hidden name=search_text value=\"$search_text\">
<input type=submit name=subweb_task value=Insert>
</td>
</tr>
</form>
";
print "
<tr>
<td colspan=$t_columns><hr noshade size=2></td>
</tr>
";
}
}
while ($i<$maxrows) {
$this->db->next_record();
$id = $this->db->f('id');
$status = $this->db->f('status');
$found = $i+1;
if (!$export) {
print "
<form action=$PHP_SELF method=post>
<input type=hidden name=web_task value=update>
<input type=hidden name=next value=$next>
<input type=hidden name=id value=$id>
<tr>
";
if ($this->table == 'prepaid') {
$active_sessions = json_decode($this->db->f('active_sessions'),true);
$account=$this->db->f('account');
$extraInfo="
<table border=0 bgcolor=#CCDDFF class=extrainfo id=row$found cellpadding=0 cellspacing=0>
<form action=$PHP_SELF method=post>
<input type=hidden name=web_task value=update>
<input type=hidden name=next value=$next>
<input type=hidden name=id value=$id>
<tr>
<td valign=top>
<table border=0>
";
$t=0;
foreach (array_keys($active_sessions) as $_session) {
$t++;
$maxsessiontime=$active_sessions[$_session]['MaxSessionTime'];
$extraInfo.=sprintf ("<tr bgcolor=lightgrey><td class=border>%d. Session id</td><td>%s</td></tr>",$t,$_session);
$duration=time()-$active_sessions[$_session]['timestamp'];
foreach (array_keys($active_sessions[$_session]) as $key) {
if ($key=='timestamp') {
$extraInfo.= sprintf ("<tr><td class=border><b>StartTime</b></td><td>%s</td></tr>",Date("Y-m-d H:i",$active_sessions[$_session]['timestamp']));
$extraInfo.= sprintf ("<tr><td class=border><b>Progress</b></td><td>%s (%s s)</td></tr>",sec2hms($duration),$duration);
} else {
$extraInfo.= sprintf ("<tr><td class=border><b>%s</b></td><td>%s</td></tr>",ucfirst($key),$active_sessions[$_session][$key]);
}
}
if ($maxsessiontime < $duration ) {
$extraInfo.= sprintf ("<tr><td class=border colspan=2><font color=red><b>Session expired since %d s</b></font></td></tr>",$duration-$maxsessiontime);
$extraInfo.= sprintf("<tr><td colspan=2><input type=submit name=subweb_task value='Delete session'></td></tr>");
}
//if (!$this->readonly) {
//}
}
$extraInfo.=sprintf("
<input type=hidden name=table value='%s'>
<input type=hidden name=next value='%s'>
<input type=hidden name=sessionId value='%s'>
<input type=hidden name=search_text value='%s'>
</form>
</table>
</td>
</tr>
</table>",
$this->table,$next,$_session,$search_text
);
}
print "
<td>$found. </td>
";
}
$j=0;
while ($j < $this->db->num_fields()) {
$value=$this->db->Record[$j];
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if ($this->tables[$this->table]['fields'][$Fname]['size']) {
$field_size=$this->tables[$this->table]['fields'][$Fname]['size'];
} else {
$field_size=$el_size;
}
if ($this->tables[$this->table]['fields'][$Fname]['readonly']=="1") {
$extra_form_els="disabled=true";
} else {
$extra_form_els="";
}
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
if (!$export) {
if (!in_array($Fname,$this->tables[$this->table]['keys']) && !$this->readonly) {
if ($this->table == 'prepaid' && $Fname == 'session_counter' && $value) {
if (count($active_sessions) > 1) {
$session_counter_txt=sprintf("%d sessions",$value);
} else {
$session_counter_txt=sprintf("%d session",$value);
}
printf("<td onClick=\"return toggleVisibility('row%s')\"><a href=#>%s</td>",$found,$session_counter_txt);
} else {
print "<td>
<input type=text bgcolor=grey size=$field_size maxlength=$size name=$Fname value=\"$value\" $extra_form_els>
</td>";
}
} else {
if ($this->table == 'prepaid' && $Fname == 'session_counter' && $value) {
if (count($active_sessions) > 1) {
$session_counter_txt=sprintf("%d sessions",$value);
} else {
$session_counter_txt=sprintf("%d session",$value);
}
printf("<td onClick=\"return toggleVisibility('row%s')\"><a href=#>%s</td>",$found,$session_counter_txt);
} else {
print "<td>$value</td>";
}
}
} else {
if ($j) {
printf ("%s%s",$delimiter,$value);
} else {
print "2";
}
}
}
$j++;
}
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
$SEARCH_NAME="search_".$Fname;
$value=$_REQUEST[$SEARCH_NAME];
if (!$export) {
print "<input type=hidden name=search_$Fname value=\"$value\">";
}
}
$j++;
}
if ($export) {
print "\n";
}
if (!$export) {
if (!$this->tables[$this->table]['readonly']) {
if ($subweb_task=="Delete" && $idForDeletion == $id && !$confirmDelete) {
print "<td class=border bgcolor=lightgrey>";
print "<input type=hidden name=confirmDelete value=1>";
print "<input type=submit name=subweb_task value=Delete>";
} else {
print "
<td class=border>
<input type=submit name=subweb_task value=Update>
<input type=submit name=subweb_task value=Delete>
";
print "<input type=hidden name=confirmDelete value=1>";
}
print "
<input type=hidden name=table value=$this->table>
<input type=hidden name=next value=$next>
<input type=hidden name=search_text value=\"$search_text\">
</td>
</tr>
</form>
<td></td>
<td colspan=$t_columns>$extraInfo</td>
</tr>
";
} else {
if ($this->table=='prepaid') {
print "
<tr>
<td></td>
<td colspan=$t_columns>$extraInfo</td>
</tr>
";
}
}
}
$i++;
}
if (!$export) {
print "
</table>
<p>
";
print "
<center>
<table border=0>
<tr>
<form method=post>
<td>
";
if ($next!= 0 ) {
$show_next=$this->maxrowsperpage-$next;
if ($show_next<0) {
$mod_show_next = $show_next-2*$show_next;
}
print "
<input type=hidden name=maxrowsperpage value=$this->maxrowsperpage>
<input type=hidden name=next value=$mod_show_next>
<input type=hidden name=web_task value=Search>
<input type=hidden name=table value=$this->table>
<input type=hidden name=search_text value=\"$search_text\">
";
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
$SEARCH_NAME="search_".$Fname;
$value=$_REQUEST[$SEARCH_NAME];
print "<input type=hidden name=search_$Fname value=\"$value\">
";
}
$j++;
}
print "
<input type=submit value=\"Previous\">
";
}
print "</td>
</form>
<form method=post>
<td>
";
if ($rows>$this->maxrowsperpage && $rows!=$maxrows) {
$show_next=$this->maxrowsperpage+$next;
print "
<input type=hidden name=maxrowsperpage value=$this->maxrowsperpage>
<input type=hidden name=next value=$show_next>
<input type=hidden name=table value=$this->table>
<input type=hidden name=web_task value=Search>
";
$j=0;
while ($j < $cc ) {
$Fname=$metadata[$j]['name'];
$size=$metadata[$j]['len'];
if (!in_array($Fname,$this->tables[$this->table]['exceptions'])) {
$SEARCH_NAME="search_".$Fname;
$value=$_REQUEST[$SEARCH_NAME];
print "<input type=hidden name=search_$Fname value=\"$value\">";
}
$j++;
}
print "
<input type=hidden name=search_text value=\"$search_text\">
<input type=submit value=\"Next\">
";
}
print "
</form>
</td>
</tr>
</table>";
print "
</body>
</html>
";
}
}
function checkValues($table,$values=array()) {
if (!$table) return false;
$metadata = $this->db->metadata($table);
if (!is_array($metadata)) return false;
$k=1;
while ($k < count($metadata)) {
$db_name = $metadata[$k]['name'];
$k++;
$web_name = $this->tables[$table]['fields'][$db_name]['name'];
$value = $values[$db_name];
$checkType = $this->tables[$table]['fields'][$db_name]['checkType'];
$mustExist = $this->tables[$table]['fields'][$db_name]['mustExist'];
if ($web_name) {
$name_print=$web_name;
} else {
$name_print=$db_name;
}
if ($mustExist) {
if (!strlen($value)) {
printf ("Error: field '%s' must be filled in\n",$name_print);
return false;
}
}
if ($checkType) {
if (!strlen($value)) {
if (!$mustExist) continue;
}
if ($checkType == 'sip_account') {
if (!checkEmail($value)) {
printf ("Error: value '%s' for field '%s' must be of format 'user@domain'\n",$value,$name_print);
return false;
}
}
if ($checkType == 'domain') {
if (stristr($value,"-.") || !preg_match("/^([a-zA-Z0-9][a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}$/i",$value)) {
printf ("Error: value '%s' for field '%s' must be of format 'example.com'\n",$value,$name_print);
return false;
}
}
if ($checkType == 'ip') {
if (!preg_match("/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i",$value,$m)) {
printf ("Error: value '%s' for field '%s' must be of format 'X.X.X.X'\n",$value,$name_print);
return false;
} else {
$i=1;
while ($i<=4) {
if ($m[$i] < 1 || $m[$i] > 255) {
printf ("Error: value '%s' for field '%s' must be of a valid IP address\n",$value,$name_print);
return false;
}
$i++;
}
}
}
if ($checkType == 'numeric') {
if (!is_numeric($value)) {
printf ("Error: value '%s' for field '%s' must be of type '%s'\n",$value,$name_print,$checkType);
return false;
}
}
}
}
return true;
}
function importTable($table='') {
// import a table from web
if (!is_array($_FILES[$table]) || $_FILES[$table]['size'] == 0) return false;
foreach ($this->importFilesPatterns as $_pattern) {
if (strstr($_FILES[$table]['name'],$_pattern) && preg_match("/\.csv$/",$_FILES[$table]['name'])) {
if ($this->CDRTool['filters']['reseller']) {
$dir=$this->cvs_import_dir.'/'.$this->CDRTool['filters']['reseller'];
if (!is_dir($dir)) {
if (!mkdir($dir)) {
printf ("<font color=red>Error: cannot create directory %s</font>",$dir);
return false;
}
}
$fullPath=$this->cvs_import_dir.'/'.$this->CDRTool['filters']['reseller'].'/'.$_FILES[$table]['name'];
} else {
$fullPath=$this->cvs_import_dir.'/'.$_FILES[$table]['name'];
}
if (!is_file($fullPath)) {
if ($fp = fopen($fullPath, "w")) {
} else {
printf ("<font color=red>Error: cannot open file %s for writing</font>",$fullPath);
return false;
}
} else {
list($basename,$extension)=explode('.',$_FILES[$table]['name']);
$j=0;
while (1) {
$j++;
if ($this->CDRTool['filters']['reseller']) {
$fullPath=$this->cvs_import_dir.'/'.$this->CDRTool['filters']['reseller'].'/'.$basename.'-'.$j.'.'.$extension;
} else {
$fullPath=$this->cvs_import_dir.'/'.$basename.'-'.$j.'.'.$extension;
}
if (is_file($fullPath)) continue;
if ($fp = fopen($fullPath, "w")) {
break;
} else {
printf ("<font color=red>Error: cannot open file %s for writing</font>",$fullPath);
return false;
}
}
}
$content=fread(fopen($_FILES[$table]['tmp_name'], "r"), $_FILES[$table]['size']);
fwrite($fp,$content);
fclose($fp);
printf ("<p><font color=green>Imported %s bytes into %s</font>",$_FILES[$table]['size'],$fullPath);
break;
}
}
}
}
class OpenSIPSQuota {
var $localDomains = array();
var $quotaGroup = 'quota'; // group set if subscriber was blocked by quota
var $timeout = 5; // soap connection timeout
function OpenSIPSQuota(&$parent) {
global $DATASOURCES;
$this->CDRdb = &$parent->CDRdb;
$this->table = &$parent->table;
$this->CDRTool = &$parent->CDRTool;
$this->cdr_source = &$parent->cdr_source;
$this->path=$this->CDRTool['Path'];
$this->db_subscribers = &$parent->db_subscribers;
if (!class_exists($this->db_subscribers)) {
print("Info: No database defined for SIP accounts $this->cdr_source.\n");
return false;
}
$this->AccountsDB = new $this->db_subscribers;
$this->enableThor = $parent->enableThor;
$parent->LoadDomains();
$this->localDomains = &$parent->localDomains;
$this->cdr_source = &$parent->cdr_source;
$this->BillingPartyIdField = &$parent->CDRFields['BillingPartyId'];
$this->parent = &$parent;
$this->db = new DB_cdrtool;
$this->db->Halt_On_Error="no";
$this->db1 = new DB_cdrtool;
$this->db1->Halt_On_Error="no";
$this->db1 = new DB_cdrtool;
$this->db1->Halt_On_Error="no";
$this->CDRS = &$parent;
$this->quota_init_flag = &$parent->quota_init_flag;
$this->quota_reset_flag = &$parent->quota_reset_flag;
// load e-mail addresses for quota notifications
$query="select * from settings where var_module = 'notifications'";
if ($this->db->query($query) && $this->db->num_rows()) {
while ($this->db->next_record()) {
$_bp =$this->db->f('billing_party');
$_name =$this->db->f('var_name');
$_value =$this->db->f('var_value');
if ($_bp && $_name && $_value) {
$this->notificationAddresses[$_bp][$_name]=$_value;
}
}
}
if ($DATASOURCES[$this->cdr_source]['soapEngineId']) {
require("/etc/cdrtool/ngnpro_engines.inc");
require_once("ngnpro_soap_library.php");
if (in_array($DATASOURCES[$this->cdr_source]['soapEngineId'],array_keys($soapEngines))) {
$this->SOAPurl = $soapEngines[$DATASOURCES[$this->cdr_source]['soapEngineId']]['url'];
$log=sprintf("Using SOAP engine %s to block accounts at %s\n",$DATASOURCES[$this->cdr_source]['soapEngineId'],$this->SOAPurl);
syslog(LOG_NOTICE, $log);
$this->SOAPlogin = array(
"username" => $soapEngines[$DATASOURCES[$this->cdr_source]['soapEngineId']]['username'],
"password" => $soapEngines[$DATASOURCES[$this->cdr_source]['soapEngineId']]['password'],
"admin" => true
);
$this->SoapAuth=array('auth', $this->SOAPlogin , 'urn:AGProjects:NGNPro', 0, '');
$this->soapclient = new WebService_NGNPro_SipPort($this->SOAPurl);
$this->soapclient->setOpt('curl', CURLOPT_SSL_VERIFYPEER, 0);
$this->soapclient->setOpt('curl', CURLOPT_SSL_VERIFYHOST, 0);
$this->soapclient->_options['timeout'] = $this->timeout;
} else {
$e=$DATASOURCES[$this->cdr_source]['soapEngineId'];
$log=sprintf("Error: soap engine id $e not found in /etc/cdrtool/ngnpro_engines.inc\n");
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
} else {
$log=sprintf("Using database queries to block accounts\n");
syslog(LOG_NOTICE, $log);
}
}
function ShowAccountsWithQuota($treshhold='') {
$query=sprintf("select * from quota_usage where datasource = '%s' and quota > 0 and cost > 0",$this->CDRS->cdr_source);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
while ($this->db->next_record()) {
if ($this->db->f('blocked')) {
$blockedStatus="blocked";
} else {
$blockedStatus='';;
}
$usageRatio=$this->db->f('cost')*100/$this->db->f('quota');
if ($treshhold && $treshhold > $usageRatio) continue;
$usageStatus=sprintf("usage=%-10s",$this->db->f('cost'));
printf ("%-35s quota=%-6s %s %.2f%s %s\n",
$this->db->f('account'),
$this->db->f('quota'),
$usageStatus,
$usageRatio,
'%',
$blockedStatus
);
}
}
function deblockAccounts($reset_quota_for=array()) {
// deblock users blocked by quota
if (!$this->db_subscribers) {
print("Info: No database defined for SIP accounts.\n");
return false;
}
if ($this->enableThor) {
$query=sprintf("select username,domain from sip_accounts where (1=1) ");
if (count($reset_quota_for)) {
$k=0;
foreach ($reset_quota_for as $_account) {
if ($k) $usage_keys.= ", ";
$usage_keys.="'".$_account."'";
$k++;
}
$query.= "and CONCAT(username,'@',domain) in (".$usage_keys.")";
}
if (!$this->AccountsDB->query($query)) {
$log=sprintf("Error: %s (%s)",$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return false;
}
while ($this->AccountsDB->next_record()) {
$i++;
$_account=$this->AccountsDB->f('username')."@".$this->AccountsDB->f('domain');
$_profile=json_decode(trim($this->AccountsDB->f('profile')));
if (in_array('quota',$_profile->groups)) {
$blockedAccounts[]=$this->AccountsDB->f('account');
}
if ($i%30000 == 0) {
print "$i accounts checked for deblocking\n";
flush();
}
}
} else {
$query=sprintf("select CONCAT(username,'@',domain) as account from grp where grp = '%s'",$this->quotaGroup);
if (count($reset_quota_for)) {
$k=0;
foreach ($reset_quota_for as $_account) {
if ($k) $usage_keys.= ", ";
$usage_keys.="'".$_account."'";
$k++;
}
$query.= "and CONCAT(username,'@',domain) in (".$usage_keys.")";
}
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error: %s (%s)",$this->AccountsDB->Error,$this->AccountsDB->Errno);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
$blockedAccounts=array();
while ($this->AccountsDB->next_record()) {
$i++;
$blockedAccounts[]=$this->AccountsDB->f('account');
if ($i%10000 == 0) {
print "$i accounts checked for deblocking\n";
flush();
}
}
}
if (count($reset_quota_for)) {
$blockedAccounts=array_intersect($blockedAccounts,$reset_quota_for);
}
if (count($blockedAccounts) >0 ) {
$this->unBlockRemoteAccounts($blockedAccounts);
if (!$this->enableThor) {
$query=sprintf("delete from grp where grp = '%s'",$this->quotaGroup);
if (count($reset_quota_for)) {
$k=0;
foreach ($reset_quota_for as $_account) {
if ($k) $usage_keys.= ", ";
$usage_keys.="'".$_account."'";
$k++;
}
$query.= "and CONCAT(username,'@',domain) in (".$usage_keys.")";
}
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error: %s (%s)",$this->AccountsDB->Error,$this->AccountsDB->Errno);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
}
}
if (count($blockedAccounts)) {
$log=sprintf ("Reset %d users blocked by quota\n",count($blockedAccounts));
print $log;
syslog(LOG_NOTICE, $log);
}
}
function initMonthlyUsageFromDatabase($month="",$reset_quota_for=array()) {
if (!$month) {
$this->startTime=Date("Y-m-01 00:00",time());
} else {
$this->startTime=$month."-01 00:00";
}
$j=0;
$usage_keys='';
if (count($reset_quota_for)) {
$log=sprintf ("Init quota of data source %s for %d accounts\n",$this->CDRS->cdr_source,count($reset_quota_for));
print $log;
syslog(LOG_NOTICE, $log);
$k=0;
foreach ($reset_quota_for as $_account) {
if ($k) $usage_keys.= ", ";
$usage_keys.="'".$_account."'";
$k++;
}
$usage_keys="and ".$this->BillingPartyIdField. " in (".$usage_keys.")";
} else {
if (count($this->localDomains)) {
$domain_filter="and Realm in (";
$t=0;
foreach (array_keys($this->localDomains) as $_domain) {
if (!$_domain) continue;
if ($t) $domain_filter .= ",";
$domain_filter .= sprintf("'%s'",$_domain);
$t++;
}
$domain_filter .= ") ";
}
$log=sprintf ("Init quota of data source %s for all accounts\n",$this->CDRS->cdr_source);
print $log;
syslog(LOG_NOTICE, $log);
}
$query=sprintf("select %s,
count(*) as calls,
sum(AcctSessionTime) as duration,
sum(Price) as cost,
sum(AcctInputOctets + AcctOutputOctets)/2 as traffic
from %s
where AcctStartTime >= '%s'
and Normalized = '1'
%s
%s
group by %s\n",
addslashes($this->BillingPartyIdField),
addslashes($this->table),
addslashes($this->startTime),
$domain_filter,
$usage_keys,
addslashes($this->BillingPartyIdField)
);
if (!$this->CDRdb->query($query)) {
if ($this->CDRdb->Errno != 1146) {
$log=sprintf ("Database error: %s (%s)",$this->CDRdb->Error,$this->CDRdb->Errno);
print $log;
syslog(LOG_NOTICE,$log);
return false;
}
}
$rows=$this->CDRdb->num_rows();
$log=sprintf ("%d callers generated traffic in %s for data source %s\n",$rows,Date("Y-m",time()),$this->CDRS->cdr_source);
print $log;
flush();
syslog(LOG_NOTICE, $log);
$j=0;
$progress=0;
while($this->CDRdb->next_record()) {
if ($rows > 1000) {
if ($j > $progress*$rows/100) {
$progress++;
if ($progress%10 == 0) {
print "$progress% ";
flush();
}
}
}
unset($accounts);
$accounts[$this->CDRdb->f($this->BillingPartyIdField)]['usage']['calls'] = $this->CDRdb->f('calls');
$accounts[$this->CDRdb->f($this->BillingPartyIdField)]['usage']['duration'] = $this->CDRdb->f('duration');
$accounts[$this->CDRdb->f($this->BillingPartyIdField)]['usage']['cost'] = $this->CDRdb->f('cost');
$accounts[$this->CDRdb->f($this->BillingPartyIdField)]['usage']['traffic'] = $this->CDRdb->f('traffic');
$this->CDRS->cacheMonthlyUsage(&$accounts);
$j++;
}
}
function checkQuota($notify) {
global $UserQuota;
$this->initMonthlyUsage();
$query=sprintf("select * from quota_usage where datasource = '%s' and quota > 0 and cost > quota",$this->CDRS->cdr_source);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$toNotify=array();
$_checks=0;
while ($this->db->next_record()) {
$account=$this->db->f('account');
list($username,$domain)=explode("@",$account);
if ($this->db->f('cost') >= $this->db->f('quota')) {
$exceeding_accounts++;
if (!$this->db->f('blocked')) {
$reason='Cost exceeded';
if (!$seen_title) {
$line=sprintf ("%40s %6s %8s %8s %13s %s\n","User","Calls","Price","Minutes","Traffic","Reason");
print $line;
$email_body=$line;
$seen_title++;
}
$line = sprintf ("%40s %6s %8s %8s %10s MB %s\n",
$account,
$this->db->f('calls'),
$this->db->f('cost'),
number_format($this->db->f('duration')/60,0,"",""),
number_format($this->db->f('traffic')/1024/1024,2),
$reason
);
$email_body = $email_body.$line;
print $line;
if ($this->enableThor) {
$this->domain_table = "sip_domains";
} else {
$this->domain_table = "domain";
}
$query=sprintf("select * from %s where domain = '%s'",$this->domain_table,$prepaidDomain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error: %s (%d) %s\n",$this->AccountsDB->Error,$this->AccountsDB->Errno,$query);
syslog(LOG_NOTICE,$log);
}
if ($this->AccountsDB->num_rows()){
$this->AccountsDB->next_record();
$_reseller=$this->AccountsDB->f('reseller_id');
} else {
$_reseller=0;
}
$log=sprintf("Monthly quota exceeded for %s (%s > %s)",$account,$this->db->f('cost'), $this->db->f('quota'));
syslog(LOG_NOTICE, $log);
$log_query=sprintf("insert into log
(date,login,ip,datasource,results,description,reseller_id)
values (NOW(),'quotacheck','localhost','QuotaCheck','1','%s',%d)",
addslashes($log),
$_reseller
);
if (!$this->db1->query($log_query)) {
$log=sprintf ("Database error: %s (%s)",$this->db1->Error,$this->db1->Errno);
print $log;
syslog(LOG_NOTICE,$log);
}
if ($this->blockAccount($account)) {
if ($notify) {
$toNotify[]=$account;
}
$blocked_now++;
$blockedAccountsNow=$blockedAccountsNow.$account."\n";
}
} else {
$blockedAccountsPrevious=$blockedAccountsPrevious.$account."\n";
$blocked_previous++;
}
}
$_checks++;
}
if ($exceeding_accounts) {
$line=sprintf("%6d accounts have exceeded their traffic limits\n",$exceeding_accounts);
print $line;
$email_body=$email_body.$line;
} else {
$log=sprintf("No quota has been exceeded\n");
syslog(LOG_NOTICE, $log);
}
if ($blocked_now) {
$line=sprintf("%6d accounts have been blocked now\n",$blocked_now);
$email_body=$email_body.$line;
}
if ($blockedAccountsNow) {
$line="Blocked accounts now:\n".$blockedAccountsNow;
print $line;
$email_body=$email_body.$line.$batch_block;
}
if ($blockedAccountsPrevious) {
$line="Blocked acccounts previously:\n".$blockedAccountsPrevious;
print $line;
$email_body=$email_body.$line.$batch_unblock;
}
// send notification to the provider
if ($this->CDRTool['provider']['toEmail'] && $blockedAccountsNow) {
$from = $this->CDRTool['provider']['fromEmail'];
$to = $this->CDRTool['provider']['toEmail'];
$bcc = $this->CDRTool['provider']['bccEmail'];
$service = $this->CDRTool['provider']['service'];
if (!$service) $service = "SIP";
if ($from) $extraHeaders="From: $from\r\nBCC: $from";
if ($bcc) $extraHeaders=$extraHeaders.",".$bcc;
print("Notify CDRTool provider at $to\n");
mail($to, "$service platform - CDRTool quota check", $email_body, $extraHeaders);
}
if ($notify && is_array($toNotify) && count($toNotify) >0) {
// send notification to accounts
foreach($toNotify as $rcpt) {
$this->notify($rcpt);
}
}
}
function notify($account) {
global $DATASOURCES;
list($username,$domain)=explode("@",$account);
if (!$DATASOURCES[$this->cdr_source]['UserQuotaNotify']) {
return false;
}
// get account information
if ($this->enableThor) {
$query=sprintf("select first_name,last_name,email from sip_accounts where username = '%s' and domain = '%s'",$username,$domain);
} else {
$query=sprintf("select first_name,last_name,email_address as email from subscriber where username = '%s' and domain = '%s'",$username,$domain);
}
if (!$this->AccountsDB->query($query)) {
$log=sprintf("Database error: %s (%s)",$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return false;
}
if (!$this->AccountsDB->num_rows()) return false;
$this->AccountsDB->next_record();
$fullname = $this->AccountsDB->f('first_name')." ".$this->AccountsDB->f('last_name');
$toEmail = $this->AccountsDB->f('email');
$providerName=$this->notificationAddresses[$domain]['providerName'];
if (!$providerName) $providerName="your SIP service provider";
$body=sprintf("Dear __NAME__,\n\n".
"Your SIP account %s has been temporarily blocked\n".
"because your monthly quota has been exceeded.\n\n".
"To unblock your account you may contact %s.\n\n".
"N.B. This is an automatically generated message. Do not reply to it.\n",
$account,
$providerName);
$fromEmail = $this->CDRTool['provider']['fromEmail'];
$bccEmail = $this->CDRTool['provider']['bccEmail'];
$seen_bcc[$bccEmail]++;
if ($this->notificationAddresses[$domain]['fromEmail']) {
$fromEmail=$this->notificationAddresses[$domain]['fromEmail'];
}
if ($this->notificationAddresses[$domain]['quotaBody']) {
$body=$this->notificationAddresses[$domain]['quotaBody'];
}
if ($this->notificationAddresses[$domain]['quotaSubject']) {
$subject=$this->notificationAddresses[$domain]['quotaSubject'];
}
$body=preg_replace("/__NAME__/",$fullname,$body);
$body=preg_replace("/__ACCOUNT__/",$account,$body);
if (!$subject) {
$subject=sprintf("Monthly quota exceeded for account %s",$account);
} else {
$subject=preg_replace("/__ACCOUNT__/",$account,$subject);
}
if (!$toEmail || !$fromEmail) {
return false;
}
$seen_bcc[$toEmail]++;
$extraHeaders="From: $fromEmail";
if ($this->notificationAddresses[$domain][bccEmail]) {
if ($bccEmail) $bccEmail.= ",";
$bccEmail.=$this->notificationAddresses[$domain][bccEmail];
}
if ($bccEmail) $extraHeaders = $extraHeaders."\r\nBCC: ".$bccEmail;
mail($toEmail,$subject,$body, $extraHeaders);
$log_msg=sprintf("Monthly quota exceeded for %s. Notified To:%s From:%s\n",$account, $toEmail,$fromEmail);
syslog(LOG_NOTICE, $log_msg);
print $log_msg;
}
function blockAccount($account) {
list($username,$domain)=explode("@",$account);
if (is_object($this->soapclient)) {
return $this->blockAccountRemote($account);
} else {
$query=sprintf("insert into grp
(username,domain,grp,last_modified)
values
('%s','%s','%s',NOW())",
addslashes($username),
addslashes($domain),
addslashes($this->quotaGroup)
);
if (!$this->AccountsDB->query($query)) {
if ($this->AccountsDB->Errno != 1062) {
$log=sprintf ("Database error: %s (%s)",$this->AccountsDB->Error,$this->AccountsDB->Errno);
print $log;
syslog(LOG_NOTICE,$log);
return false;
} else {
return true;
}
} else {
$this->markBlocked($account);
return true;
}
}
}
function blockAccountRemote($account) {
list($username,$domain)=explode("@",$account);
if (!$username || !$domain) {
$log=sprintf("Error: misssing username/domain in blockAccountRemote()");
syslog(LOG_NOTICE, $log);
return false;
}
$this->soapclient->addHeader($this->SoapAuth);
$result = $this->soapclient->addToGroup(array("username" => $username,"domain"=> $domain), "quota");
if (PEAR::isError($result)) {
$error_msg = $result->getMessage();
$error_fault = $result->getFault();
$error_code = $result->getCode();
$log=sprintf("Error from %s: %s (%s)",$this->SOAPurl,$error_fault->faultstring,$error_fault->faultcode);
syslog(LOG_NOTICE, $log);
print $log;
if ($error_fault->detail->exception->errorcode != "1030") {
$from = $this->CDRTool['provider']['fromEmail'];
$to = $this->CDRTool['provider']['toEmail'];
$extraHeaders = "From: $from";
$email_body = "Remote SOAP request failure when calling blockAccountRemote(): \n\n".
$log;
mail($to, "CDRTool SOAP client failure", $email_body, $extraHeaders);
}
return false;
} else {
$log=sprintf ("Block account %s at %s",$account,$this->SOAPurl );
syslog(LOG_NOTICE, $log);
$this->markBlocked($account);
return true;
}
}
function unBlockRemoteAccounts($accounts) {
if (!is_object($this->soapclient)) {
return;
}
foreach ($accounts as $account) {
list($username,$domain)=explode("@",$account);
if (!$username || !$domain) return true;
$this->soapclient->addHeader($this->SoapAuth);
$result = $this->soapclient->removeFromGroup(array("username" => $username,"domain"=> $domain), "quota");
if (PEAR::isError($result)) {
$error_msg = $result->getMessage();
$error_fault = $result->getFault();
$error_code = $result->getCode();
if ($error_fault->detail->exception->errorcode &&
$error_fault->detail->exception->errorcode != "1030" &&
$error_fault->detail->exception->errorcode != "1031"
) {
$from = $this->CDRTool[provider][fromEmail];
$to = $this->CDRTool[provider][toEmail];
$extraHeaders="From: $from";
$email_body="SOAP request failure: \n\n".
$log=sprintf ("SOAP client error: %s %s\n",$error_fault->detail->exception->errorcode,$error_fault->detail->exception->errorstring);
syslog(LOG_NOTICE, $log);
mail($to, "CDRTool SOAP failure", $email_body, $extraHeaders);
}
} else {
$log=sprintf ("Unblock remote account %s at %s",$account,$this->SOAPurl);
syslog(LOG_NOTICE, $log);
}
}
}
function saveQuotaInitFlag() {
$query=sprintf("insert into memcache (`key`,`value`) values ('%s','1')",$this->quota_init_flag);
if (!$this->db->query($query)) {
if ($this->db->Errno != '1062') {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
}
return true;
}
function deleteQuotaInitFlag() {
$query=sprintf("delete from memcache where `key` in ('%s','%s')",$this->quota_init_flag,$this->quota_reset_flag);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
return true;
}
function deleteMonthlyUsageFromCache ($reset_quota_for=array()) {
$query=sprintf("delete from quota_usage where datasource = '%s' ",$this->CDRS->cdr_source);
if (count($reset_quota_for)) {
$query.= " and account in (";
$t=0;
foreach($reset_quota_for as $_account) {
if ($t) $query.=",";
$query.= sprintf("'%s'",$_account);
$t++;
}
$query.=")";
}
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->affected_rows()) {
$log=sprintf("Deleted %d keys from cache\n",$this->db->affected_rows());
print $log;
syslog(LOG_NOTICE, $log);
}
return true;
}
function initMonthlyUsage() {
$query=sprintf("select value from memcache where `key` = '%s'",$this->quota_init_flag);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) return true;
$lockName=sprintf("%s:%s",$this->CDRS->cdr_source,$this->CDRS->table);
if (!$this->CDRS->getNormalizeLock($lockName)) {
$log=sprintf("Error: cannot initialize now the quota because a normalization process in progress\n");
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
$query=sprintf("select value from memcache where `key` = '%s'",$this->quota_reset_flag);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
if ($this->db->num_rows()) {
$this->db->next_record();
$reset_quota_for = json_decode($this->db->f('value'));
}
if ($reset_quota_for) {
$this->deblockAccounts($reset_quota_for);
}
$this->deleteMonthlyUsageFromCache($reset_quota_for);
$this->initMonthlyUsageFromDatabase('',$reset_quota_for);
if ($this->CDRS->status['cached_keys']['saved_keys']) {
$log=sprintf("Saved %d accounts in quota cache\n",$this->CDRS->status['cached_keys']['saved_keys']);
print $log;
syslog(LOG_NOTICE, $log);
}
if ($this->CDRS->status['cached_keys']['failed_keys']) {
$log=sprintf("Error: failed to save %d account\n",$this->CDRS->status['cached_keys']['failed_keys']);
print $log;
syslog(LOG_NOTICE, $log);
}
if ($this->saveQuotaInitFlag()) {
$query=sprintf("delete from memcache where `key` = '%s'",$this->quota_reset_flag);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return false;
}
return true;
} else {
$log=sprintf ("Error: failed to save key quotaCheckInit");
syslog(LOG_NOTICE, $log);
return false;
}
}
function markBlocked($account) {
$query=sprintf("update quota_usage set blocked = '1', notified = NOW() where account = '%s' and datasource = '%s'",$account,$this->CDRS->cdr_source);
if (!$this->db1->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db1->Error,$this->db1->Errno);
print $log;
syslog(LOG_NOTICE, $log);
return 0;
}
}
}
class RatingEngine {
var $method = '';
var $log_runtime = false;
var $prepaid_table = "prepaid";
var $init_ok = false;
function RatingEngine () {
global $RatingEngine; // set in global.inc
global $DATASOURCES; // set in global.inc
if (!strlen($RatingEngine['socketIP']) || !$RatingEngine['socketPort'] || !$RatingEngine['cdr_source']) {
$log=sprintf("Please define \$RatingEngine['socketIP'], \$RatingEngine['socketPort'] and \$RatingEngine['cdr_source'] in /etc/cdrtool/global.inc\n");
syslog(LOG_NOTICE,$log);
return false;
}
if (!is_array($DATASOURCES[$RatingEngine['cdr_source']])) {
$log=sprintf("Datasource '%s' does not exist in /etc/cdrtool/global.inc\n",$RatingEngine['cdr_source']);
syslog(LOG_NOTICE,$log);
return false;
}
$this->settings = $RatingEngine;
if ($this->settings['log_runtime']) {
$this->log_runtime=true;
}
// init database
$this->db = new DB_CDRTool;
$query=sprintf("delete from memcache where `key` = 'destinations_sip' or `key` = 'destinations'");
if (!$this->db->query($query)) {
$log=sprintf ("Database error: %s (%s) for query %s",$db->Error,$db->Errno,$query);
syslog(LOG_NOTICE,$log);
}
// init CDR datasource
$CDR_class = $DATASOURCES[$RatingEngine['cdr_source']]['class'];
$this->CDRS = new $CDR_class($RatingEngine['cdr_source']);
// load Rating Tables
$this->CDRS->RatingTables = new RatingTables();
$this->CDRS->RatingTables->LoadRatingTables();
// init subscribers database
$this->db_subscribers_class = &$this->CDRS->db_subscribers;
if (!class_exists($this->db_subscribers_class)) {
syslog(LOG_NOTICE,"Error: No database defined for SIP accounts");
return false;
}
$this->AccountsDB = new $this->db_subscribers_class;
$this->enableThor = $this->CDRS->enableThor;
$this->init_ok = true;
}
function reloadRatingTables () {
$query="delete from memcache where `key` in ('destinations','destinations_sip','ENUMtlds')";
if (!$this->db->query($query)) {
$log=sprintf ("Database error: %s (%s)",$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE,$log);
}
$this->CDRS->RatingTables->LoadRatingTables();
$this->CDRS->LoadDestinations();
if ($d > 0 ) syslog(LOG_NOTICE, "Reloaded rating tables in $d seconds");
$this->db->query("update settings set var_value = '' where var_name = 'reloadRating'");
return 1;
}
function reloadCustomers ($customerFilter) {
return 1;
}
function reloadDomains () {
return 1;
}
function reloadQuota($account) {
if (!$account) return false;
$quota = $this->getQuota($account);
$blocked = $this->getBlockedByQuotaStatus($account);
$query=sprintf("update quota_usage set
quota = '%s',
blocked = '%s'
where datasource = '%s'
and account = '%s'",
$quota,
intval($blocked),
$this->CDRS->cdr_source,
$account);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return 0;
}
return 1;
}
function getBalanceHistory($account,$limit=50) {
list($username,$domain)=explode("@",$account);
if (!$username || !$domain) return 0;
$query=sprintf("select * from prepaid_history where username = '%s' and domain = '%s' order by id desc",
addslashes($username),
addslashes($domain)
);
if ($limit) $query.= sprintf (" limit %d",$limit);
if (!$this->db->query($query)) {
$log=sprintf ("getBalanceHistory error: %s (%s)",$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return 0;
}
while ($this->db->next_record()) {
$history[]=array('account' => $account,
'action' => $this->db->f('action'),
'description' => $this->db->f('description'),
'value' => $this->db->f('value'),
'balance' => $this->db->f('balance')
);
}
$line=json_encode($history);
return $line;
}
function DebitBalance($account,$balance,$session_id,$duration,$force=false) {
$els=explode(":",$account);
if (count($els) == 2) {
$account=$els[1];
}
if (!$account) {
syslog(LOG_NOTICE, "DebitBalance() error: missing account");
return 0;
}
if (!is_numeric($balance)) {
syslog(LOG_NOTICE, "DebitBalance() error: balance must be numeric");
return 0;
}
if (!$session_id) {
syslog(LOG_NOTICE, "DebitBalance() error: missing call id");
return 0;
}
$query=sprintf("select * from %s where account = '%s'",
addslashes($this->prepaid_table),
addslashes($account)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE,$log);
$this->logRuntime();
return 0;
}
if (!$this->db->num_rows()) {
$log=sprintf ("DebitBalance() error: account $account does not exist");
syslog(LOG_NOTICE, $log);
$this->logRuntime();
return 0;
}
$this->db->next_record();
if (strlen($this->db->f('active_sessions'))) {
// remove active session
$active_sessions=array();
$old_active_sessions = json_decode($this->db->f('active_sessions'),true);
$destination=$old_active_sessions[$session_id]['Destination'];
if (!$force) {
if (!in_array($session_id,array_keys($old_active_sessions))) {
$this->sessionDoesNotExist=true;
$log=sprintf("Error: session %s of %s does not exist",$session_id,$account);
syslog(LOG_NOTICE, $log);
return 0;
}
}
foreach (array_keys($old_active_sessions) as $_key) {
if ($_key==$session_id) continue;
$active_sessions[$_key]=$old_active_sessions[$_key];
}
} else {
if (!$force) {
$this->sessionDoesNotExist=true;
$log=sprintf ("Error: session %s for %s does not exist",$session_id,$account);
syslog(LOG_NOTICE, $log);
return 0;
}
}
$next_balance=$this->db->f('balance')-$balance;
//get parallel calls and remaining_balance
$this->getActivePrepaidSessions($active_sessions,$next_balance,$account);
// calculate the updated maxsessiontime
$maxsessiontime=$this->getAggregatedMaxSessiontime($this->parallel_calls,$this->remaining_balance,$account);
$query=sprintf("update %s
set balance = balance - '%s',
change_date = NOW(),
active_sessions = '%s',
session_counter = '%s'
where account = '%s'",
$this->prepaid_table,
$balance,
addslashes(json_encode($active_sessions)),
count($active_sessions),
addslashes($account)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return 0;
}
if ($balance > 0) {
list($prepaidUser,$prepaidDomain)=explode("@",$account);
if ($this->enableThor) {
$this->domain_table = "sip_domains";
} else {
$this->domain_table = "domain";
}
$query=sprintf("select * from %s where domain = '%s'",$this->domain_table,$prepaidDomain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error: %s (%d) %s\n",$this->AccountsDB->Error,$this->AccountsDB->Errno,$query);
syslog(LOG_NOTICE,$log);
}
if ($this->AccountsDB->num_rows()){
$this->AccountsDB->next_record();
$_reseller=$this->AccountsDB->f('reseller_id');
} else {
$_reseller=0;
}
$query=sprintf("insert into prepaid_history
(username,domain,action,description,value,balance,date,session,duration,destination,reseller_id)
values
('%s','%s','Debit balance','Session to %s for %ds','-%s','%s',NOW(),'%s','%d','%s',%d)",
addslashes($prepaidUser),
addslashes($prepaidDomain),
addslashes($destination),
$duration,
$balance,
$next_balance,
addslashes($session_id),
$duration,
addslashes($destination),
$_reseller
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
}
}
return $maxsessiontime;
}
function CreditBalance($account,$balance) {
if (!is_numeric($balance)) {
syslog(LOG_NOTICE, "CreditBalance() error: balance \"$balance\"is invalid");
return 0;
}
$els=explode(":",$account);
if (count($els) == 2) {
$account=$els[1];
}
if (!$account) {
syslog(LOG_NOTICE, "CreditBalance() error: missing account");
return 0;
}
list($prepaidUser,$prepaidDomain)=explode("@",$account);
$query=sprintf("select * from %s where account = '%s'",
addslashes($this->prepaid_table),
addslashes($account)
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE,$log);
$this->logRuntime();
return 0;
}
if ($this->db->num_rows()) {
$this->db->next_record();
$current_balance = $this->db->f('balance');
$query=sprintf("update %s
set balance = balance + '%s',
change_date = NOW()
where account = '%s'",
$this->prepaid_table,
$balance,
addslashes($account)
);
$this->db->query($query);
if ($this->db->affected_rows()) {
$new_balance = $current_balance + $balance;
$log=sprintf ("Prepaid account $account credited with $balance");
syslog(LOG_NOTICE, $log);
// log to prepaid_history
$query=sprintf("insert into prepaid_history
(username,domain,action,description,value,balance,date)
values
('%s','%s','Set balance','Manual update','%s','%s',NOW())",
addslashes($prepaidUser),
addslashes($prepaidDomain),
$balance,
$new_balance
);
if (!$this->db->query($query)) {
$log=sprintf("Error: %s (%s)",$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
}
return 1;
} else {
$log=sprintf ("CreditBalance() error: failed to credit balance: %s (%s)",$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return 0;
}
} else {
$query=sprintf("insert into %s (balance, account, change_date) values ('%s','%s',NOW())",
$this->prepaid_table,
$balance,
addslashes($account)
);
$this->db->query($query);
if ($this->db->affected_rows()) {
$log=sprintf ("Added prepaid account $account with balance=$balance");
syslog(LOG_NOTICE, $log);
// log to prepaid_history
$query=sprintf("insert into prepaid_history
(username,domain,action,description,value,balance,date)
values
('%s','%s','Set balance','Manual update','%s','%s',NOW())",
addslashes($prepaidUser),
addslashes($prepaidDomain),
$balance,
$balance
);
if (!$this->db->query($query)) {
$log=sprintf("Error: %s (%s)",$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
}
return 1;
} else {
$log=sprintf ("CreditBalance() error: failed to credit balance: %s (%s)",$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return 0;
}
}
}
function DeleteBalance($account) {
$els=explode(":",$account);
if (count($els) == 2) {
$account=$els[1];
}
if (!$account) {
syslog(LOG_NOTICE, "DeleteBalance() error: missing account");
return 0;
}
$query=sprintf("delete from %s where account = '%s'",
$this->prepaid_table,addslashes($account)
);
if (!$this->db->query($query)) {
$log=sprintf("DeleteBalance error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return 0;
}
$log=sprintf ("Prepaid account %s has been deleted",$account);
syslog(LOG_NOTICE, $log);
return 1;
}
function DeleteBalanceHistory($account) {
$account=trim($account);
$els=explode(":",$account);
if (count($els) == 2) {
$account=$els[1];
}
if (!$account) {
syslog(LOG_NOTICE, "DeleteBalanceHistory() error: missing account");
return 0;
}
list($username,$domain)=explode('@',$account);
$query=sprintf("delete from prepaid_history where username = '%s' and domain = '%s'",
addslashes($username),
addslashes($domain)
);
if (!$this->db->query($query)) {
$log=sprintf("DeleteBalanceHistory error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return 0;
}
$log=sprintf ("History of prepaid account %s has been deleted",$account);
syslog(LOG_NOTICE, $log);
return 1;
}
function GetEntityProfiles($entity) {
if (!$entity) {
syslog(LOG_NOTICE, "GetEntityProfiles");
return 0;
}
$query=sprintf("select * from billing_customers where
subscriber = '%s' or domain = '%s' or gateway = '%s'",
addslashes($entity),
addslashes($entity),
addslashes($entity)
);
if (!$this->db->query($query)) {
$log=sprintf ("GetEntityProfiles error: %s (%s)",$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return 0;
}
if ($this->db->num_rows() ==1 ) {
$this->db->next_record();
$entity = array('entity' => $entity,
'profileWeekday' => $this->db->f('profile_name1'),
'profileWeekdayAlt' => $this->db->f('profile_name1_alt'),
'profileWeekend' => $this->db->f('profile_name2'),
'profileWeekendAlt' => $this->db->f('profile_name2_alt'),
'timezone' => $this->db->f('timezone'),
'increment' => $this->db->f('increment'),
'min_duration' => $this->db->f('min_duration')
);
}
$line=json_encode($entity);
return $line;
}
function SetEntityProfiles($entity,$profiles) {
if (!$entity) {
syslog(LOG_NOTICE, "SetEntityProfiles");
return 0;
}
}
function showHelp() {
$help=
"Version\n".
"Help\n".
"ShowClients\n".
"MaxSessionTime CallId=6432622xvv@1 From=sip:123@example.com To=sip:0031650222333@example.com Duration=7200 Gateway=10.0.0.1 Lock=1\n".
"ShowPrice From=sip:123@example.com To=sip:0031650222333@example.com Gateway=10.0.0.1 Duration=59\n".
"DebitBalance CallId=6432622xvv@1 From=sip:123@example.com To=sip:0031650222333@example.com Gateway=10.0.0.1 Duration=59\n".
"AddBalance From=123@example.com Value=10.00\n".
"GetBalance From=123@example.com\n".
"GetBalanceHistory From=123@example.com\n".
"DeleteBalance From=123@example.com\n".
"DeleteBalanceHistory From=123@example.com\n".
"ReloadQuota Account=abc@example.com\n".
"GetEntityProfiles Entity=abc@example.com\n".
"ReloadRatingTables\n".
"ReloadDomains\n".
"ShowProfiles\n".
"ShowENUMtlds\n"
;
return $help;
}
function logRuntime() {
if (!$this->log_runtime) return;
$t=0;
$log='';
foreach (array_keys($this->runtime) as $_key) {
$stamp=$this->runtime[$_key];
if ($prev_stamp) {
$_exec_time=$stamp-$prev_stamp;
$log .= sprintf("%s=%1.7f ",$_key,$_exec_time);
}
$prev_stamp=$stamp;
$t++;
}
syslog(LOG_NOTICE, $log);
}
function processNetworkInput($tinput) {
// Read key=value pairs from input
// Strip any unnecessary spaces
$this->runtime=array();
$tinput=preg_replace("/\s+/"," ",$tinput);
$_els=explode(" ",trim($tinput));
$this->runtime['start']=microtime_float();
syslog(LOG_NOTICE, $tinput);
if (!$_els[0]) return 0;
// read fields from input
unset($NetFields);
unset($seenField);
$i=0;
while ($i < count($_els)) {
$i++;
$_dict = explode("=",$_els[$i]);
$_key = strtolower(trim($_dict[0]));
if ($_key == 'callid') {
$_value = trim($_dict[1]);
} else {
$_value = strtolower(trim($_dict[1]));
}
if ($_key && $seenField[$_key]) {
$log=sprintf ("Error: '$_key' attribute is present more than once in $tinput");
syslog(LOG_NOTICE, $log);
return 0;
} else {
if ($_key) {
$NetFields[$_key]=$_value;
$seenField[$_key]++;
}
}
}
$NetFields['action']=strtolower($_els[0]);
$this->method = $NetFields['action'];
// begin processing
if ($NetFields['action']=="maxsessiontime") {
if (!$NetFields['from']) {
$log=sprintf ("error: missing From parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!$NetFields['to']) {
$log=sprintf ("error: missing To parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!$NetFields['gateway']) {
$log=sprintf ("error: missing gateway parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!$NetFields['callid']) {
$log=sprintf ("error: missing Call Id parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!$NetFields['duration'] && $this->settings['MaxSessionTime']) {
$NetFields['duration']=$this->settings['MaxSessionTime'];
}
$CDRStructure=array (
$this->CDRS->CDRFields['callId'] => $NetFields['callid'],
$this->CDRS->CDRFields['aNumber'] => $NetFields['from'],
$this->CDRS->CDRFields['CanonicalURI'] => $NetFields['to'],
$this->CDRS->CDRFields['gateway'] => $NetFields['gateway'],
$this->CDRS->CDRFields['duration'] => floor($NetFields['duration']),
$this->CDRS->CDRFields['timestamp'] => time(),
'skip_fix_prepaid_duration' => true
);
$CDR = new $this->CDRS->CDR_class(&$this->CDRS, &$CDRStructure);
$CDR->normalize();
$this->runtime['normalize_cdr']=microtime_float();
$query=sprintf("select * from %s where account = '%s'",addslashes($this->prepaid_table),addslashes($CDR->BillingPartyId));
if (!$this->db->query($query)) {
$log=sprintf ("Database error for query '%s': %s (%s), link_id =%s, query_id =%s",$query,$this->db->Error,$this->db->Errno,$this->db->Link_ID,$this->db->Query_ID);
syslog(LOG_NOTICE,$log);
$this->logRuntime();
$log=sprintf("Error: database error for query '%s': %s (%s)",$query,$this->db->Error,$this->db->Errno);
return 0;
}
if (!$this->db->num_rows()) {
$log=sprintf ("MaxSessionTime=unlimited Type=postpaid CallId=%s BillingParty=%s",$NetFields['callid'],$CDR->BillingPartyId);
syslog(LOG_NOTICE, $log);
return "none";
}
$this->db->next_record();
$Balance = $this->db->f('balance');
$session_counter = $this->db->f('session_counter');
$max_sessions = $this->db->f('max_sessions');
if (strlen($this->db->f('active_sessions'))) {
// load active sessions
$active_sessions = json_decode($this->db->f('active_sessions'),true);
if (count($active_sessions)) {
$active_sessions_new=array();
$expired=0;
foreach (array_keys($active_sessions) as $_session) {
$expired_since=time() - $active_sessions[$_session]['timestamp'] - $active_sessions[$_session]['MaxSessionTime'];
if ($expired_since > 120) {
// this session has passed its maxsessiontime plus its reasonable setup time of 2 minutes,
// it could be stale
// because the call control module did not call debitbalance, so we purge it
$log = sprintf ("Session %s for %s has expired since %d seconds",
$_session,
$active_sessions[$_session]['BillingPartyId'],
$expired_since);
syslog(LOG_NOTICE, $log);
$expired++;
} else {
$active_sessions_new[$_session]=$active_sessions[$_session];
}
}
if ($expired) {
$active_sessions=$active_sessions_new;
}
}
} else {
$active_sessions=array();
}
$session_counter=count($active_sessions);
if ($session_counter >= $max_sessions) {
$log = sprintf ("Locked: too many parallel calls, $max_sessions allowed");
syslog(LOG_NOTICE, $log);
return 'Locked';
}
if (!$Balance) {
$log=sprintf ("No balance found");
syslog(LOG_NOTICE,$log);
$this->logRuntime();
return 0;
}
if (!preg_match("/^0/",$CDR->CanonicalURINormalized)) {
/*
$log = sprintf ("Call to %s, no limit imposed",$CDR->CanonicalURINormalized);
syslog(LOG_NOTICE, $log);
*/
$this->logRuntime();
return "none";
} else {
if (!$CDR->DestinationId) {
$log = sprintf ("error: cannot figure out the destination id for %s",$CDR->CanonicalURI);
$this->logRuntime();
syslog(LOG_NOTICE, $log);
return "0";
}
}
$maxduration=0;
// Build Rate dictionary containing normalized CDR fields plus customer Balance
if (count($active_sessions)) {
// set $this->remaining_balance and $this->parallel_calls for ongoing calls:
if (!$this->getActivePrepaidSessions($active_sessions,$Balance,$CDR->BillingPartyId,array($CDR->callId))) {
return 0;
}
$this->runtime['get_parallel_calls']=microtime_float();
// add current call to the list of parallel calls
$RateDictionary=array(
'duration' => $CDR->duration,
'callId' => $CDR->callId,
'Balance' => $this->remaining_balance,
'timestamp' => $CDR->timestamp,
'DestinationId' => $CDR->DestinationId,
'domain' => $CDR->domain,
'gateway' => $CDR->gateway,
'BillingPartyId' => $CDR->BillingPartyId,
'ENUMtld' => $CDR->ENUMtld,
'RatingTables' => &$this->CDRS->RatingTables
);
$Rate = new Rate($this->settings, $this->db);
$_maxduration = round($Rate->MaxSessionTime($RateDictionary));
$log = sprintf ("Maximum duration for session %s of %s to destination %s having balance=%s is %s",
$CDR->callId,
$CDR->BillingPartyId,
$CDR->DestinationId,
$this->remaining_balance,
$_maxduration);
syslog(LOG_NOTICE, $log);
if ($_maxduration > 0) {
$this->parallel_calls[$CDR->callId]=array('pricePerSecond' => $this->remaining_balance/$_maxduration);
} else {
$log = sprintf ("Maxduration for session %s of %s will become negative",$CDR->callId,$CDR->BillingPartyId);
syslog(LOG_NOTICE, $log);
return 0;
}
$this->parallel_calls[$CDR->callId]=array('pricePerSecond' => $this->remaining_balance/$_maxduration);
$maxduration=$this->getAggregatedMaxSessiontime($this->parallel_calls,$this->remaining_balance,$CDR->BillingPartyId);
} else {
$RateDictionary=array(
'duration' => $CDR->duration,
'callId' => $CDR->callId,
'Balance' => $Balance,
'timestamp' => $CDR->timestamp,
'DestinationId' => $CDR->DestinationId,
'domain' => $CDR->domain,
'gateway' => $CDR->gateway,
'BillingPartyId' => $CDR->BillingPartyId,
'ENUMtld' => $CDR->ENUMtld,
'RatingTables' => &$this->CDRS->RatingTables
);
$Rate = new Rate($this->settings, $this->db);
$this->runtime['instantiate_rate']=microtime_float();
$maxduration = round($Rate->MaxSessionTime($RateDictionary));
}
// add new active session
$active_sessions[$CDR->callId]= array('timestamp' => $CDR->timestamp,
'duration' => $CDR->duration,
'BillingPartyId' => $CDR->BillingPartyId,
'MaxSessionTime' => $maxduration,
'domain' => $CDR->domain,
'gateway' => $CDR->gateway,
'Destination' => $CDR->destinationPrint,
'DestinationId' => $CDR->DestinationId,
'connectCost' => $Rate->connectCost
);
if ($CDR->ENUMtld) {
$active_sessions[$CDR->callId]['ENUMtld']=$CDR->ENUMtld;
}
$this->runtime['calculate_maxduration']=microtime_float();
if ($maxduration < 0) {
$log = sprintf ("error: maxduration %s is negative",$maxduration);
syslog(LOG_NOTICE, $log);
return $log;
}
if ($Rate->min_duration && $maxduration < $Rate->min_duration) {
$log = sprintf ("Notice: maxduration of %s is less then min_duration (%s)",$maxduration,$Rate->min_duration);
syslog(LOG_NOTICE, $log);
return 0;
}
if (!$Rate->billingTimezone) {
$log = sprintf ("error: cannot figure out the billing timezone");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!$Rate->startTimeBilling) {
$log = sprintf ("error: cannot figure out the billing start time");
syslog(LOG_NOTICE, $log);
return $log;
}
$log=sprintf ("MaxSessionTime=%s Type=prepaid CallId=%s BillingParty=%s DestId=%s Balance=%s Spans=%d",
$maxduration,
$NetFields['callid'],
$CDR->BillingPartyId,
$CDR->DestinationId,
$RateDictionary['Balance'],
$Rate->MaxSessionTimeSpans
);
syslog(LOG_NOTICE, $log);
if ($maxduration > 0) {
$query=sprintf("update %s
set
active_sessions = '%s',
session_counter = '%s'
where account = '%s'",
addslashes($this->prepaid_table),
addslashes(json_encode($active_sessions)),
count($active_sessions),
addslashes($CDR->BillingPartyId));
if (!$this->db->query($query)) {
$log=sprintf ("Database error for %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE,$log);
$log=sprintf ("error: database error %s (%s)",$this->db->Error,$this->db->Errno);
return $log;
}
}
$this->runtime['update_prepaid']=microtime_float();
$this->logRuntime();
return $maxduration;
} else if ($NetFields['action'] == "debitbalance") {
if (!$NetFields['from']) {
$log=sprintf ("error: missing From parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!$NetFields['to']) {
$log=sprintf ("error: missing To parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!strlen($NetFields['duration'])) {
$log=sprintf ("error: missing Duration parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!$NetFields['gateway']) {
$log=sprintf ("error: missing gateway parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if (!$NetFields['callid']) {
$log=sprintf ("error: missing Call Id parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
if ($NetFields['force']) {
$force=true;
} else {
$force=false;
}
$timestamp=time();
$CDRStructure=array (
$this->CDRS->CDRFields['callId'] => $NetFields['callid'],
$this->CDRS->CDRFields['aNumber'] => $NetFields['from'],
$this->CDRS->CDRFields['CanonicalURI'] => $NetFields['to'],
$this->CDRS->CDRFields['gateway'] => $NetFields['gateway'],
$this->CDRS->CDRFields['ENUMtld'] => $NetFields['enumtld'],
$this->CDRS->CDRFields['duration'] => floor($NetFields['duration']),
$this->CDRS->CDRFields['timestamp'] => time(),
'skip_fix_prepaid_duration' => true
);
// Init CDR
$CDR = new $this->CDRS->CDR_class($this->CDRS, $CDRStructure);
$CDR->normalize();
$this->runtime['normalize_cdr']=microtime_float();
// Build Rate dictionary containing normalized CDR fields plus customer Balance
$RateDictionary=array(
'callId' => $NetFields['callid'],
'timestamp' => $CDR->timestamp,
'duration' => $CDR->duration,
'DestinationId' => $CDR->DestinationId,
'domain' => $CDR->domain,
'gateway' => $CDR->gateway,
'BillingPartyId' => $CDR->BillingPartyId,
'ENUMtld' => $CDR->ENUMtld,
'RatingTables' => &$this->CDRS->RatingTables
);
$Rate = new Rate($this->settings, $this->db);
$this->runtime['instantiate_rate']=microtime_float();
$Rate->calculate($RateDictionary);
$this->runtime['calculate_rate']=microtime_float();
$this->sessionDoesNotExist=false;
$result = $this->DebitBalance($CDR->BillingPartyId,$Rate->price,$NetFields['callid'],$CDR->duration,$force);
if ($this->sessionDoesNotExist) {
return "Failed";
}
$this->runtime['debit_balance']=microtime_float();
if ($CDR->duration) {
$log = sprintf ("Price=%s Duration=%s CallId=%s BillingParty=%s DestId=%s MaxSessionTime=%d",
$Rate->price,
$CDR->duration,
$NetFields['callid'],
$CDR->BillingPartyId,
$CDR->DestinationId,
$result
);
syslog(LOG_NOTICE, $log);
}
$RateReturn = "Ok";
$RateReturn.= sprintf("\nMaxSessionTime=%d",$result);
if (strlen($Rate->price)) {
$RateReturn.="\n".$Rate->price;
if ($Rate->rateInfo) {
$RateReturn.="\n".trim($Rate->rateInfo);
}
}
return $RateReturn;
} else if ($NetFields['action'] == "addbalance") {
if (!$NetFields['from']) {
$log=sprintf ("Error: Missing From parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
if (!is_numeric($NetFields['value'])) {
$log=sprintf ("Error: Missing Value parameter, it must be numeric");
syslog(LOG_NOTICE, $log);
return 0;
}
return $this->CreditBalance($NetFields['from'],$NetFields['value']);
} else if ($NetFields['action'] == "deletebalance") {
if (!$NetFields['from']) {
$log=sprintf ("Error: Missing From parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
return $this->DeleteBalance($NetFields['from']);
} else if ($NetFields['action'] == "deletebalancehistory") {
if (!$NetFields['from']) {
$log=sprintf ("Error: Missing From parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
return $this->DeleteBalanceHistory($NetFields['from']);
} else if ($NetFields['action'] == "showprice") {
if (!$NetFields['from']) {
$log=sprintf ("Error: Missing From parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
if (!$NetFields['to']) {
$log=sprintf ("Error: Missing To parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
if (!strlen($NetFields['duration'])) {
$log=sprintf ("Error: Missing Duration parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
if ($NetFields['timestamp']) {
$timestamp=$NetFields['timestamp'];
} else {
$timestamp=time();
}
if (!$NetFields['gateway']) {
$log=sprintf ("error: missing gateway parameter");
syslog(LOG_NOTICE, $log);
return $log;
}
$CDRStructure=array (
$this->CDRS->CDRFields['callId'] => $NetFields['callid'],
$this->CDRS->CDRFields['aNumber'] => $NetFields['from'],
$this->CDRS->CDRFields['CanonicalURI'] => $NetFields['to'],
$this->CDRS->CDRFields['gateway'] => $NetFields['gateway'],
$this->CDRS->CDRFields['ENUMtld'] => $NetFields['enumtld'],
$this->CDRS->CDRFields['duration'] => floor($NetFields['duration']),
$this->CDRS->CDRFields['timestamp'] => time(),
'skip_fix_prepaid_duration' => true
);
$CDR = new $this->CDRS->CDR_class(&$this->CDRS, &$CDRStructure);
$CDR->normalize();
$Rate = new Rate($this->settings, $this->db);
$RateDictionary=array(
'callId' => $CDR->callId,
'timestamp' => $CDR->timestamp,
'duration' => $CDR->duration,
'DestinationId' => $CDR->DestinationId,
'domain' => $CDR->domain,
'gateway' => $CDR->gateway,
'BillingPartyId' => $CDR->BillingPartyId,
'ENUMtld' => $CDR->ENUMtld,
'RatingTables' => &$this->CDRS->RatingTables
);
$Rate->calculate($RateDictionary);
$this->runtime['calculate_rate']=microtime_float();
if (strlen($Rate->price)) {
$RateReturn=$Rate->price;
if ($Rate->rateInfo) {
$RateReturn.="\n".trim($Rate->rateInfo);
}
} else {
$RateReturn="0";
}
return $RateReturn;
} else if ($NetFields['action'] == "getbalance") {
if (!$NetFields['from']) {
$log=sprintf ("Error: Missing From parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
$query=sprintf("select * from %s where account = '%s'",
addslashes($this->prepaid_table),
addslashes($NetFields['from'])
);
if (!$this->db->query($query)) {
$log=sprintf ("Database error for %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE,$log);
$this->logRuntime();
return 0;
}
if ($this->db->num_rows()) {
$this->db->next_record();
return number_format($this->db->f('balance'),4,".","");
} else {
return sprintf("%0.4f",0);
}
} else if ($NetFields['action'] == "getbalancehistory") {
if (!$NetFields['from']) {
$log=sprintf ("Error: Missing From parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
$history=$this->getBalanceHistory($NetFields['from']);
return trim($history);
} else if ($NetFields['action'] == "getentityprofiles") {
if (!$NetFields['entity']) {
$log=sprintf ("Error: Missing Entity parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
$entity=$this->GetEntityProfiles($NetFields['entity']);
return trim($entity);
} else if ($NetFields['action'] == "showprofiles") {
return trim($this->CDRS->RatingTables->showProfiles());
} else if ($NetFields['action'] == "showenumtlds") {
return trim($this->CDRS->RatingTables->showENUMtlds());
} else if ($NetFields['action'] == "version") {
$version_file=$this->CDRS->CDRTool['Path']."/version";
$version="CDRTool version ".trim(file_get_contents($version_file));
return $version;
} else if ($NetFields['action'] == "help") {
return $this->showHelp();
} else if ($NetFields['action'] == "reloadratingtables") {
return $this->reloadRatingTables();
} else if ($NetFields['action'] == "keepalive") {
return $this->keepAlive();
} else if ($NetFields['action'] == "reloadquota") {
if (!$NetFields['account']) {
$log=sprintf ("Error: Missing Account parameter");
syslog(LOG_NOTICE, $log);
return 0;
}
return $this->reloadQuota($NetFields['account']);
} else if ($NetFields['action'] == "reloaddomains") {
return $this->CDRS->LoadDomains();
} else if ($NetFields['action'] == "reloadcustomers") {
if ($NetFields['customer'] && $NetFields['type']) {
$_customerFilter=array('customer'=>$NetFields['customer'],
'type'=>$NetFields['type']);
}
return $this->reloadCustomers($_customerFilter);
} else {
$log=sprintf ("Error: Invalid request");
syslog(LOG_NOTICE, $log);
return 0;
}
}
function getQuota($account) {
if (!$account) return;
list($username,$domain) = explode("@",$account);
if ($this->enableThor) {
$query=sprintf("select * from sip_accounts where username = '%s' and domain = '%s'",$username,$domain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
$this->AccountsDB->next_record();
$_profile=json_decode(trim($this->AccountsDB->f('profile')));
return $_profile->quota;
} else {
return 0;
}
} else {
$query=sprintf("select quota from subscriber where username = '%s' and domain = '%s'",$username,$domain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
$this->AccountsDB->next_record();
return $this->AccountsDB->f('quota');
} else {
return 0;
}
}
}
function getBlockedByQuotaStatus($account) {
if (!$account) return 0;
list($username,$domain) = explode("@",$account);
if ($this->enableThor) {
$query=sprintf("select * from sip_accounts where username = '%s' and domain = '%s'",$username,$domain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
$this->AccountsDB->next_record();
$_profile=json_decode(trim($this->AccountsDB->f('profile')));
if (in_array('quota',$_profile->groups)) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
} else {
$query=sprintf("select CONCAT(username,'@',domain) as account from grp where grp = 'quota' and username = '%s' and domain = '%s'",$username,$domain);
if (!$this->AccountsDB->query($query)) {
$log=sprintf ("Database error for query %s: %s (%s)",$query,$this->AccountsDB->Error,$this->AccountsDB->Errno);
syslog(LOG_NOTICE,$log);
return 0;
}
if ($this->AccountsDB->num_rows()) {
return 1;
} else {
return 0;
}
}
return 0;
}
function getActivePrepaidSessions($active_sessions,$Balance,$BillingPartyId,$exceptSessions=array()) {
$this->parallel_calls=array();
$this->remaining_balance=$Balance;
$ongoing_rates=array();
foreach (array_keys($active_sessions) as $_session) {
if (in_array($_session,$exceptSessions)) {
/*
$log = sprintf ("Ongoing prepaid session %s for %s updated",
$_session,
$BillingPartyId
);
syslog(LOG_NOTICE, $log);
*/
continue;
}
$Rate_session = new Rate($this->settings, $this->db);
$passed_time=time()-$active_sessions[$_session]['timestamp'];
$active_sessions[$_session]['passed_time']=$passed_time;
$RateDictionary_session=array(
'duration' => $passed_time,
'callId' => $_session,
'timestamp' => $active_sessions[$_session]['timestamp'],
'DestinationId' => $active_sessions[$_session]['DestinationId'],
'domain' => $active_sessions[$_session]['domain'],
'BillingPartyId' => $active_sessions[$_session]['BillingPartyId'],
'ENUMtld' => $active_sessions[$_session]['ENUMtld'],
'RatingTables' => &$this->CDRS->RatingTables
);
$Rate_session->calculate($RateDictionary_session);
$log = sprintf ("Ongoing prepaid session %s for %s to %s: duration=%s, price=%s ",
$_session,
$BillingPartyId,
$active_sessions[$_session]['Destination'],
$passed_time,
$Rate_session->price
);
syslog(LOG_NOTICE, $log);
$ongoing_rates[$_session] = array(
'duration' => $passed_time,
'price' => $Rate_session->price
);
}
if (count($ongoing_rates)) {
// calculate de virtual balance of the user at this moment in time
$due_balance=0;
foreach (array_keys($ongoing_rates) as $_o) {
$due_balance = $due_balance+$ongoing_rates[$_o]['price'];
}
$this->remaining_balance = $this->remaining_balance-$due_balance;
$log = sprintf ("Balance for %s having %d ongoing sessions: database=%s, due=%s, real=%s",
$BillingPartyId,count($ongoing_rates),sprintf("%0.4f",$Balance),sprintf("%0.4f",$due_balance),sprintf("%0.4f",$this->remaining_balance));
syslog(LOG_NOTICE, $log);
}
foreach (array_keys($active_sessions) as $_session) {
if (in_array($_session,$exceptSessions)) {
continue;
}
$RateDictionary_session=array(
'callId' => $_session,
'timestamp' => time(),
'Balance' => $this->remaining_balance,
'DestinationId' => $active_sessions[$_session]['DestinationId'],
'domain' => $active_sessions[$_session]['domain'],
'BillingPartyId' => $active_sessions[$_session]['BillingPartyId'],
'ENUMtld' => $active_sessions[$_session]['ENUMtld'],
'RatingTables' => &$this->CDRS->RatingTables,
'skipConnectCost' => true
);
if ($active_sessions[$_session]['duration']) {
$RateDictionary_session['duration'] = $active_sessions[$_session]['duration']-$active_sessions[$_session]['passed_time'];
}
$Rate = new Rate($this->settings, $this->db);
$_maxduration = round($Rate->MaxSessionTime($RateDictionary_session));
$log = sprintf("Maximum duration for session %s of %s to destination %s having balance=%s is %s",
$_session,
$BillingPartyId,
$active_sessions[$_session]['DestinationId'],
$this->remaining_balance,
$_maxduration);
syslog(LOG_NOTICE, $log);
if ($_maxduration > 0) {
$this->parallel_calls[$_session]=array('pricePerSecond' => $this->remaining_balance/$_maxduration);
} else {
/*
$log = sprintf ("Maxduration for session %s of %s will be negative",$_session,$active_sessions[$_session]['BillingPartyId']);
syslog(LOG_NOTICE, $log);
*/
return 0;
}
}
return 1;
}
function getAggregatedMaxSessiontime($parallel_calls=array(),$balance,$BillingPartyId) {
$maxduration=0;
$sum_price_per_second=0;
foreach (array_keys($parallel_calls) as $_call) {
$sum_price_per_second=$sum_price_per_second+$parallel_calls[$_call]['pricePerSecond'];
}
if ($sum_price_per_second >0 ) {
$maxduration=intval($balance/$sum_price_per_second);
if (count($parallel_calls) > 1) {
$log = sprintf ("Maximum duration agregated for %s is (Balance=%s)/(Sum of price per second for each destination=%s)=%s s",
$BillingPartyId,$balance,sprintf("%0.4f",$sum_price_per_second),$maxduration);
syslog(LOG_NOTICE, $log);
}
} else {
/*
$log = sprintf ("Error: sum_price_per_second for %s is negative",$BillingPartyId);
syslog(LOG_NOTICE, $log);
*/
$maxduration = 0;
}
return round($maxduration);
}
function keepAlive() {
$query=sprintf("select * from auth_user");
if (!$this->db->query($query) || !$this->db->num_rows()) {
$log=sprintf ("Database error for keepalive query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno);
syslog(LOG_NOTICE, $log);
return false;
}
$log=sprintf("Keepalive successful");
syslog(LOG_NOTICE, $log);
return true;
}
}
function reloadRatingEngineTables () {
global $RatingEngine;
if (strlen($RatingEngine['socketIP']) && $RatingEngine['socketPort']) {
if ($RatingEngine['socketIP']=='0.0.0.0' || $RatingEngine['socketIP'] == '0') {
$RatingEngine['socketIPforClients']= '127.0.0.1';
} else {
$RatingEngine['socketIPforClients']=$RatingEngine['socketIP'];
}
if ($fp = fsockopen ($RatingEngine['socketIPforClients'], $RatingEngine['socketPort'], $errno, $errstr, 2)) {
fputs($fp, "ReloadRatingTables\n");
fclose($fp);
return true;
}
}
return false;
}
function keepAliveRatingEngine() {
global $RatingEngine;
if (strlen($RatingEngine['socketIP']) && $RatingEngine['socketPort']) {
if ($RatingEngine['socketIP']=='0.0.0.0' || $RatingEngine['socketIP'] == '0') {
$RatingEngine['socketIPforClients']= '127.0.0.1';
} else {
$RatingEngine['socketIPforClients']=$RatingEngine['socketIP'];
}
if ($fp = fsockopen ($RatingEngine['socketIPforClients'], $RatingEngine['socketPort'], $errno, $errstr, 2)) {
fputs($fp, "KeepAlive\n");
fclose($fp);
return true;
}
}
return false;
}
function testRatingTables () {
global $RatingEngine;
if (!strlen($RatingEngine['socketIP']) || !$RatingEngine['socketPort']) {
return false;
}
if ($RatingEngine['socketIP']=='0.0.0.0' || $RatingEngine['socketIP'] == '0') {
$RatingEngine['socketIPforClients']= '127.0.0.1';
} else {
$RatingEngine['socketIPforClients']=$RatingEngine['socketIP'];
}
$i=0;
$b=time();
while ($i < 1000) {
if (!$fp = fsockopen ($RatingEngine['socketIPforClients'], $RatingEngine['socketPort'], $errno, $errstr, 2)) {
print "Error connecting to rating engine\n";
break;
}
$i++;
$number='00'.RandomNumber(1,true).RandomNumber(12).'@example.com';
$duration=RandomNumber(3,true);
$command=sprintf("ShowPrice From=sip:123@example.com To=sip:%s Gateway=10.0.0.1 Duration=%d\n",$number,$duration);
fputs($fp, $command,strlen($command));
$response = fgets($fp, 8192);
fclose($fp);
}
$e=time();
$d=$e-$b;
if ($d) printf("Commands=%d, Time=%s seconds, Speed=%s cps\n",$i,$d,number_format($i/$d,1));
}
?>

File Metadata

Mime Type
text/x-diff
Expires
Sat, Feb 1, 5:57 AM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3488824
Default Alt Text
(617 KB)

Event Timeline