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 Tue, 15 Dec 2009 19:59:07 +0100 + -- Adrian Georgescu 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 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 Wed, 21 Oct 2009 14:24:01 +0200 cdrtool (6.9.7) unstable; urgency=low * Added geoip.dat to debian package -- Adrian Georgescu 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 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 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 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 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 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 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 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 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 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 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 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 Sat, 04 Apr 2009 16:31:21 +0200 cdrtool (6.7.5) unstable; urgency=low * Fixed display of rating info -- Adrian Georgescu 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 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 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 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 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 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 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 Wed, 01 Oct 2008 00:11:35 +0200 cdrtool (6.6.8) unstable; urgency=low * Log debit balance to prepaid_history table -- Adrian Georgescu 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 Sun, 28 Sep 2008 00:20:09 +0200 cdrtool (6.6.5) unstable; urgency=low * Fixed purging of expired prepaid sessions -- Adrian Georgescu 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 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 Fri, 12 Sep 2008 18:27:15 +0200 cdrtool (6.6.2) unstable; urgency=low * Fixed concurrent prepaid calls handling -- Adrian Georgescu 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 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 Mon, 08 Sep 2008 13:37:20 +0200 cdrtool (6.5.8) unstable; urgency=low * Fixed wsdl for deleteGatewayGroup function -- Adrian Georgescu 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Tue, 12 Feb 2008 19:32:04 +0100 cdrtool (6.2.0) unstable; urgency=high * Fixed corrupted phone_images.php file -- Adrian Georgescu 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 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 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 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 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 Thu, 31 Jan 2008 16:11:56 +0100 cdrtool (6.1.4) unstable; urgency=low * Remove sample mrtg files -- Adrian Georgescu 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 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 Tue, 08 Jan 2008 11:18:35 +0100 cdrtool (6.1.1) unstable; urgency=low * Corrected default login account sql insert statement -- Adrian Georgescu 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 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 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 Tue, 23 Oct 2007 23:09:27 +0200 cdrtool (5.3.2) unstable; urgency=low * Fixed memory leak -- Adrian Georgescu 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 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 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 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 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 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 Tue, 31 Jul 2007 13:14:43 +0200 cdrtool (5.2.2) unstable; urgency=low * Added SIP trace for SIP Thor datasource -- Adrian Georgescu 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 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 Tue, 03 Jul 2007 14:51:40 +0200 cdrtool (5.1.9) unstable; urgency=low * Removed print of sql query -- Adrian Georgescu Thu, 28 Jun 2007 18:26:27 +0200 cdrtool (5.1.8) unstable; urgency=low * Fixed saving the agreement with license page -- Adrian Georgescu 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 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 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 Thu, 07 Jun 2007 10:01:32 +0200 cdrtool (5.1.4) unstable; urgency=low * Fixed a typo in cdrlib.phtml -- Adrian Georgescu Mon, 14 May 2007 08:47:24 +0200 cdrtool (5.1.3) unstable; urgency=low * Fixed pagination of exported records -- Adrian Georgescu 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Thu, 10 Aug 2006 15:57:20 +0200 cdrtool (4.6-5) unstable; urgency=low * Fixed search in query logs by string -- Adrian Georgescu 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 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 Sun, 06 Aug 2006 18:05:05 +0200 cdrtool (4.6-2) unstable; urgency=low * debian packaging improvements -- Adrian Georgescu 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 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 @@ '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 "
"; $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 ""; } print " "; print "
url_edit\" >Refine search | url_run\">Refresh | url_export\" target=_new>Export results to file | Want to share the results with others? Give this query a name:
"; if (!$hide_rows) { print "
"; if ($this->rows == 0) { print "No records found."; } else { print "$this->rows records found. "; } print "
"; } } } function showResultsMenuSubscriber($hide_rows="") { global $loginname; if (!$this->export) { print "
"; $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 " | url_export\" target=_new>Export results to file"; } print " "; } print "
url_edit\">Refine search | url_run\">Refresh | Save a description for this query:
"; if (!$hide_rows) { print "
"; if ($this->rows == 0) { print "No records found."; } else { print "$this->rows records found. "; } print "
"; } } } function showDateTimeElements($f) { print " Start Time "; 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 " Stop Time "; 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 " "; } function showDataSources ($f) { global $perm; print " Data Source "; $f->show_element("cdr_source",""); if (count($this->tables) > 0) { print " Table: "; $this->f->show_element("cdr_table",""); } print " "; } function showPagination($next,$maxrows) { $PHP_SELF=$_SERVER["PHP_SELF"]; if (!$this->export) { print "

"; 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 "Previous "; } print " "; 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 "Next"; } print "
"; } } 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 "

$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 ""; print " "; if ($this->domain) { print " "; } else { print " "; } print " "; } function showFooter() { print "
User@ Domain SIP UA contact NAT address User Agent Expires Remain Users@ Domain
$this->total users@ $this->domains domains
"; } 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 " $found $onlines users@ $ld "; if ($this->domain) { print " "; } print " "; } } $this->showfooter(); /* print "

"; $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 " $found $username@ $domain $transport $c_els[0] $r_els[0] $user_agent $expires $remain "; $seen[$username]++; $seen[$domain]++; } } function showUAdistribution () { print ""; print " "; print ""; print ""; print ""; print " "; while (list($k,$v) = each($this->countUA)) { $users=$users+$v; $count++; print " "; print ""; print ""; print ""; print ""; } print " "; print ""; print ""; print ""; print " "; print "
User agentUsers
$count$k$v
$this->domain$users
"; } } 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 @@ '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 "

From $begin_datetime to $end_datetime"; } print "
"; } 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 "

From $begin_datetime to $end_datetime "; } print "

Id Start Time SIP Caller Location Sip Proxy SIP Destination Dur Price KBIn KBOut Status Codecs
"; } 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 "

From $begin_datetime to $end_datetime "; } print "

Id Start Time SIP Caller Location Sip Proxy SIP Destination Duration Price KBIn KBOut
"; if ($perm->have_perm("showPrice")) { $pricePrint=number_format($price,4,".",""); } else { $pricePrint='x.xxx'; } print " ",$url_calls,$traceField,$traceValue_enc,$traceField); print " "; } 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 "
"; } 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 "
Calls Seconds Minutes Hours Price TrafficIn(MB) TrafficOut(MB) Success Failure $group_byPrint Description Action
"; $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 " "; print " "; print " "; print " "; print " "; print " "; print " "; print "
SIP Call Id / Source IP "; $this->f->show_element("call_id",""); print " / "; $this->f->show_element("gateway",""); print " Sip Proxy "; $this->f->show_element("sip_proxy",""); print "
User Agent / Media Codecs "; $this->f->show_element("UserAgent",""); print " Codec: "; $this->f->show_element("SipCodec",""); print "
SIP Billing Party (Username) "; $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 "
SIP Caller Party (From URI) "; $this->f->show_element("a_number_comp",""); $this->f->show_element("a_number"); print "
SIP Destination (Canonical URI) "; $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 "
Duration / Application / Status "; $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 "
Order by / Group by "; $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 " "; if (!$perm->have_perm('readonly')) { print ";   ReNormalize"; print " "; } print "

"; $this->f->show_element("submit",""); $this->f->finish(); print "
"; } function searchFormSubscriber() { global $perm; $this->initForm(); $this->f->start("","POST","","","datasource"); print " "; $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 " "; print " "; print " "; print " "; print " "; print "
SIP Caller Party "; $this->f->show_element("a_number",""); print "
SIP Billing Party "; $this->f->show_element("UserName",""); print "
SIP Destination "; $this->f->show_element("c_number_comp",""); $this->f->show_element("c_number",""); //$this->f->show_element("DestinationId",""); print "
SIP Session duration "; $this->f->show_element("duration",""); print " Application "; $this->f->show_element("application",""); print "
Order by "; $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 "

"; $this->f->show_element("submit",""); $this->f->finish(); print "
"; } 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 "

"; 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 "

$found $calls $seconds_print $minutes_print $hours$pricePrint $AcctInputOctets $AcctOutputOctets $success% ($nonzero calls) $failure% ($zero calls) $mygroup_print $description "; printf("Display calls
"; } } 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 "
"; } } $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 ("

Missed sessions

"); $i=0; foreach ($sessions['missed'] as $_session) { $i++; if ($i >= $count) break; $htmlBody.=sprintf ("", $i, $_session['date'], $_session['from'], $_session['from'] ); $txtBody.=sprintf ("%s,%s,%s,%s,%s\n", $i, $_session['date'], $_session['from'], $_session['to'] ); } $htmlBody.="
Date and time Caller address
%s%ssip:%s
"; } if (count($sessions['diverted'])) { // diverted sessions $textBody .= sprintf ("Diverted sessions\n\n Id,Date,From,Diverted to\n "); $htmlBody .= sprintf ("

Diverted sessions

"); $i=0; foreach ($sessions['diverted'] as $_session) { $i++; if ($i >= $count) break; $htmlBody.=sprintf ("", $i, $_session['date'], $_session['from'], $_session['from'], $_session['canonical'] ); $txtBody.=sprintf ("%s,%s,%s,%s\n", $i, $_session['date'], $_session['from'], $_session['canonical'] ); } $htmlBody.="
Date and time Caller address Diverted to
%s%ssip:%s%s
"; } if (count($sessions['received'])) { // received sessions $textBody .= sprintf ("Received sessions\n\n Id,Date,From,Duration\n"); $htmlBody .= sprintf ("

Received sessions

"); $i=1; foreach ($sessions['received'] as $_session) { if ($i >= $count) break; $htmlBody.=sprintf ("", $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.="
Date and time Caller address Duration
%s%ssip:%s%s
"; } $htmlBody.="

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="
"; $this->cdr_details.= sprintf(" ", $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="SipProxyServer', 'Trace', 'toolbar=0,status=0,menubar=0,scrollbars=1,resizable=1,width=1000,height=600')\">Click here to see the SIP trace for this call  "; $this->cdr_details.= " "; } $this->cdr_details.= sprintf(" ", $this->traceLink); $this->cdr_details.= " "; $this->cdr_details.= sprintf(" ",$this->geo_location); $this->cdr_details.= " "; if ($this->CanonicalURI) { $this->cdr_details.= sprintf(" ",htmlentities($this->CanonicalURI)); } $this->cdr_details.= sprintf(" ",htmlentities($this->RemoteAddress)); if ($this->DestinationId) { $this->cdr_details.= " "; } if ($this->ENUMtld && $this->ENUMtld != 'none' && $this->ENUMtld != 'N/A') { $this->cdr_details.= " "; } if ($this->SipRPID && $this->SipRPID!='n/a') { $this->cdr_details .= " "; } $this->cdr_details.= "
SIP Signalling
Click here to show only this call id
Call id: $this->callId
%s
From/to tags: $this->SipFromTag/$this->SipToTag
Start Time: $this->startTime $providerTimezone
Stop Time: $this->stopTime
Country: %s
Method: $this->SipMethod from $this->SourceIP:$this->SourcePort
From: $this->aNumberPrint
From Header: $this->FromHeaderPrint
User Agent: $this->UserAgentPrint
Domain: $this->domain
To (dialed URI): $this->cNumberPrint
Canonical URI: %s
Next Hop URI: %s
Destination: $this->destinationName ($this->DestinationId)
ENUM TLD: $this->ENUMtld
Caller ID: $this->SipRPIDPrint
Billing Party: $this->BillingPartyIdPrint
Reseller: $this->ResellerId
"; $this->cdr_details.= " "; if ($this->SipCodec && $this->CDRS->mediaTrace) { $media_trace_datasource = $this->CDRS->mediaTrace; $this->mediaTraceLink="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  "; $this->cdr_details.= sprintf(" ", $this->mediaTraceLink); } $this->cdr_details.= " "; if ($this->SipCodec) { $this->SipCodec = quoted_printable_decode($this->SipCodec); $this->cdr_details.= " "; if ($this->MediaTimeout) { $this->cdr_details.= " "; } 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.= " "; } 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 ("\n", $_desc,$this->QoS[$_key]); } } } $this->cdr_details.= "
Media Streams
%s
Applications: $this->applicationType_print
Codecs: $this->SipCodec
Caller RTP: $this->inputTrafficPrint KB
Called RTP: $this->outputTrafficPrint KB
Media Info: $this->MediaTimeout
Caller SIP UA: $callerUA
Called SIP UA: $calledUA
%s%s
"; $this->cdr_details.= "
"; if ($perm->have_perm("showPrice") && $this->normalized) { $this->cdr_details.= " "; if ($this->price > 0 || $this->rate) { $this->ratePrint=nl2br($this->rate); $this->cdr_details.= " "; } else { $this->cdr_details.= " "; } $this->cdr_details.= "
Rating
$this->ratePrint
Free call
"; } $this->cdr_details.= "
"; } 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= "". "In". ""; } 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= "". "Out". ""; } 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 " $found_print $this->startTime $this->aNumberPrint $this->geo_location $this->SipProxyServer $this->destinationPrint "; if ($this->DestinationId) { if ($this->DestinationId != $this->CanonicalURI) { print " ($this->destinationName $this->DestinationId)"; } else { print " ($this->destinationName)"; } } print ""; if (!$this->normalized){ if ($this->duration > 0 ) { print "$this->duration(s)"; } else { print "in progress"; } } else { print " $this->durationPrint $this->pricePrint $this->inputTrafficPrint $this->outputTrafficPrint "; } $SIPclass=substr($this->disconnect,0,1); if ($SIPclass=="6") { $status_color=""; } else if ($SIPclass=="5" ) { $status_color=""; } else if ($SIPclass=="4" ) { $status_color=""; } else if ($SIPclass=="3" ) { $status_color=""; } else if ($SIPclass=="2" ) { $status_color=""; } else { $status_color=""; } print " $status_color $this->disconnectPrint $this->SipCodec $this->cdr_details "; } 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 " $found_print $this->startTime $timezone_print $this->aNumberPrint $this->geo_location $this->SipProxyServer $this->destinationPrint $this->destinationName $this->durationPrint "; if ($this->CDRS->rating) { print "$this->pricePrint"; } print " $this->inputTrafficPrint $this->outputTrafficPrint "; print " $this->cdr_details "; } 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 ("

Error: soapEngineID not defined in datasource %s",$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("

Error: database class '%s' is not defined",$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("Error from %s: %s: %s",$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 "

Error: no database connection defined"; 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 "

SIP trace for session id $callid is not available."; return; } print "

CDRTool SIP trace

SIP session $callid $authorize

"; 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 " "; } else { print "
This SIP trace is visible "; print "URLs for this trace: HTML | TEXT"; } if ($this->mediaTrace) { $this->mediaTraceLink=sprintf("

Click here for the media trace", $this->mediaTrace, urlencode($callid), urlencode($fromtag), urlencode($totag), $proxyIP ); } print "

Click on each trace entry to see the full message. $this->mediaTraceLink
"; 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 "

"; foreach (array_keys($this->column) as $_key) { $IPels=explode(":",$_key); if ($this->hostnames[$IPels[0]]) { $_hostname=$this->hostnames[$IPels[0]]; } else { $_hostname=$_key; } print ""; } print ""; $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=""; } else if ($SIPclass=="5" ) { $status_color=""; } else if ($SIPclass=="4" ) { $status_color=""; } else if ($SIPclass=="3" ) { $status_color=""; } else if ($SIPclass=="2" ) { $status_color=""; } else if ($SIPclass=="1" ) { $status_color=""; } else { $status_color=""; } $_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 = " $status_color $_els[0] "; if ($status) $cell_content.=" for ".$status_for_method.""; if (is_array($diversions)) { foreach ($diversions as $_diversion) { $cell_content.="
$_diversion"; } } if (is_array($media['streams'])) { foreach (array_keys($media['streams']) as $_key) { $_stream=$media['streams'][$_key].':'.$media['ip'].':'.$_key; $cell_content.="
$_stream"; } } $cell_content.="
"; print "
"; $column_current++; if ($arrow_direction=='loop') $seen_msg[$md5]++; } print ""; 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 " "; } print "
Id Size Time"; if ($proxyIP != $IPels[0] && $this->isProxy($IPels[0],$proxyIP)) { $trace_link=sprintf("%s:%s (Trace) ", urlencode($this->cdr_source), urlencode($callid), urlencode($fromtag), urlencode($totag), $IPels[0], $_hostname, $this->column_port[$_key] ); printf ("%s",$trace_link); } else { printf ("%s",$_hostname); } print "
"; if ($timeline && !$_seen_timeline[$timeline]) { printf ("%s+%ds ",$status_color,$timeline); $_seen_timeline[$timeline]++; } $len=strlen($msg); print " $status_color$i/$this->rows  $len bytes $status_color$date "; $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 ""; if ($arrow_direction == 'left') { print ""; if (!$isProxy && $direction=='in' && $sip_phone_img && $sip_phone_img!='unknown.png') print ""; } else { if (!$isProxy && $direction=='in' && $sip_phone_img && $sip_phone_img!='unknown.png') print ""; print ""; } print "
"; if ($transport == 'tls') print " "; 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 "
"; } if ($msg_possition == $column_current) { print $cell_content; print "
"; 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 " "; } print "
"; print "
"; if ($direction == "out") { print "

SIP Proxy

"; } else { if ($sip_phone_img) print ""; } if ($timeline > 0) { printf ("

+%s s
(%s)",$timeline,sec2hms($timeline)); } $msg=nl2br(htmlentities($msg)); print "

$status_color $msg
"; print "
"; } function showText($proxyIP,$callid,$fromtag,$totag) { $this->getTrace($proxyIP,$callid,$fromtag,$totag); print "

";
 
         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 "
"; } 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("

Error: database class %s is not defined in datasource %s",$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 "

Error: proxyIP or callid or fromtag are not defined"; return false; } if (!is_object($this->soapclient)) { print "

Error: soap client is not defined"; 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("Error from %s: %s: %s",$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 "

Error: no database connection defined"; 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 ("

Database error for query %s: %s (%s)",$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 "

";
         print_r($this->info);
         print "
"; */ } 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 "

No information available."; return false; } if (!count($this->info->streams)) { print "

No media streams have been established for this session."; return; } print "

CDRTool media trace

Media session $callid

"; 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 "

Media information

"; print ""; printf ("",$this->info->duration); list($relay_ip,$relay_port)=explode(":",$this->info->streams[0]->caller_local); printf ("",$relay_ip); print "
Call duration%s
Media relay%s
"; print "

Media streams

"; print ""; print ""; foreach (array_values($media_types) as $_type) { printf ("",ucfirst($_type)); } print ""; foreach ($this->info->streams[0] as $_val => $_value) { printf ("",ucfirst(preg_replace("/_/"," ",$_val))); $j=0; while ($j < count($media_types)) { printf ("",$this->info->streams[$j]->$_val); $j++; } printf ("\n"); } print "
%s
%s%s
"; print "

Stream succession

"; $w=500; $w1=30; $stamps=array_keys($seen_stamp); sort($stamps); $w2=$w+$w1; print ""; 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 ""; //$t2=$_val->end_time-$_val->timeout_wait; $t2=$_val->end_time-$_val->start_time; if (!$t2) $t2=''; $t3=$this->info->duration; print ""; } print "
$_val->media_type "; print ""; print ""; if ($_val->timeout_wait) { print ""; } else { print ""; } print "
$t2$t3
"; print "
"; print "

Legend

"; print "
 Session data
 Timeout period
"; } } 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 @@ 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 ("Database error: %s (%s)",$this->db->Error,$this->db->Errno); } } } } else { printf ("Database error for query '%s': %s (%s)",$query,$this->db->Error,$this->db->Errno); } } else { print "

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 ("Database error: %s (%s)",$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 ("Database error for %s: %s (%s)",$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 ("Database error: %s",$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 ("Database error: %s (%s)",$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 ("Database error for %s: %s (%s)",$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 ("Database error: %s",$this->db->Error); } unset($confirmDelete); } else { print "

"; print "Please confirm the deletion by pressing the Delete button again. "; print ""; print ""; } } 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 ("Database error for %s: %s (%s)",$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 ("Database error: %s",$this->db->Error); } $log_entity="rate=$toRate"; } else { print "

"; print "Please confirm the copy of rate $fromRate to $toRate. "; print ""; } } elseif ($subweb_task == "Insert") { //print "

Insert

"; 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 = ????
"; $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 ("Database error for query %s: %s (%s)",$query,$this->db->Error,$this->db->Errno); } } else { print " Error: The insert statement contains an empty key! "; } } else { print "

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 ("Database error: %s",$this->db->Error); } unset($confirmDelete); } else { $idForDeletion=$id; print "

"; print "Please confirm the deletion by pressing the Delete button again. "; print ""; print ""; } } 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 "

Prepaid card generator"; } // 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 "
"; 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 " | table>Reload rating tables"; } } $engineAddress=$this->settings['socketIPforClients'].":".$this->settings['socketPort']; if ($this->checkRatingEngineConnection()) { print " | Rating engine running at $engineAddress"; } else { print " | Cannot connect to rating engine $engineAddress"; } } print " | Rating documentation"; print "
"; } 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 " "; } 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 ""; } else { if ($k) { printf ("%s%s",$delimiter,$th); } else { print "Ops"; } } $t_columns++; } $k++; } if ($export) { print "\n"; } if (!$export) { print " "; $t_columns=$t_columns+2; // SEARCH FORM print " "; // Search form print " "; $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 ""; } else { print ""; } } $j++; } printf(" ", $PHP_SELF ); print " "; print " "; if ($selection_made && !$this->tables[$this->table]['readonly']) { // Update all form print " "; $j=0; print " "; 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 ""; } else { print ""; } } $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 ""; } $j++; } if ($subweb_task=="Delete selection" && !$confirmDelete) { print " "; } else if (!$this->tables[$this->table]['readonly']){ // Insert form $j=0; print " "; 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 ""; } else { print ""; } } $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 ""; } $j++; } print " "; print " "; } } while ($i<$maxrows) { $this->db->next_record(); $id = $this->db->f('id'); $status = $this->db->f('status'); $found = $i+1; if (!$export) { print " "; if ($this->table == 'prepaid') { $active_sessions = json_decode($this->db->f('active_sessions'),true); $account=$this->db->f('account'); $extraInfo="
$thAction
Use _ to match one character and % to match any. Use > or < to find greater or smaller values.
  "; printf("
"; $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 ""; } $j++; } if ($this->table!=='prepaid_cards' ) { printf (" ",$this->table,$this->csv_export[$this->table]); } print "
"; if ($this->csv_import[$this->table]) { print "
"; printf (" ",$this->table,$this->table ); print "
"; } print "

Use + or - to add/substract from curent values. Use * or / to multiply/divide curent values.
 "; print ""; print ""; 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 ""; print ""; } else { print ""; print "
"; } print " "; 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 ("",$_REQUEST['search_name']); $selected_newtable[$toRate]='selected'; printf ("", $newRateName, $selected_newtable[$newRateName], $newRateName, $selected_newtable['history'] ); } else { print "
"; print "
"; } } print "
table>
  table\">

"; $t=0; foreach (array_keys($active_sessions) as $_session) { $t++; $maxsessiontime=$active_sessions[$_session]['MaxSessionTime']; $extraInfo.=sprintf ("",$t,$_session); $duration=time()-$active_sessions[$_session]['timestamp']; foreach (array_keys($active_sessions[$_session]) as $key) { if ($key=='timestamp') { $extraInfo.= sprintf ("",Date("Y-m-d H:i",$active_sessions[$_session]['timestamp'])); $extraInfo.= sprintf ("",sec2hms($duration),$duration); } else { $extraInfo.= sprintf ("",ucfirst($key),$active_sessions[$_session][$key]); } } if ($maxsessiontime < $duration ) { $extraInfo.= sprintf ("",$duration-$maxsessiontime); $extraInfo.= sprintf(""); } //if (!$this->readonly) { //} } $extraInfo.=sprintf("
%d. Session id%s
StartTime%s
Progress%s (%s s)
%s%s
Session expired since %d s
", $this->table,$next,$_session,$search_text ); } print " $found. "; } $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("%s",$found,$session_counter_txt); } else { print " "; } } 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("%s",$found,$session_counter_txt); } else { print "$value"; } } } 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 ""; } } $j++; } if ($export) { print "\n"; } if (!$export) { if (!$this->tables[$this->table]['readonly']) { if ($subweb_task=="Delete" && $idForDeletion == $id && !$confirmDelete) { print ""; print ""; print ""; } else { print " "; print ""; } print " table> $extraInfo "; } else { if ($this->table=='prepaid') { print " $extraInfo "; } } } $i++; } if (!$export) { print "

"; print "

"; if ($next!= 0 ) { $show_next=$this->maxrowsperpage-$next; if ($show_next<0) { $mod_show_next = $show_next-2*$show_next; } print " maxrowsperpage> table> "; $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 " "; } $j++; } print " "; } print "
"; if ($rows>$this->maxrowsperpage && $rows!=$maxrows) { $show_next=$this->maxrowsperpage+$next; print " maxrowsperpage> table> "; $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 ""; } $j++; } print " "; } print "
"; print " "; } } 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 ("Error: cannot create directory %s",$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 ("Error: cannot open file %s for writing",$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 ("Error: cannot open file %s for writing",$fullPath); return false; } } } $content=fread(fopen($_FILES[$table]['tmp_name'], "r"), $_FILES[$table]['size']); fwrite($fp,$content); fclose($fp); printf ("

Imported %s bytes into %s",$_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)); } ?>