Operator: Create ability to export CDR to CSV/TSV
authorBen Klang <ben@alkaloid.net>
Tue, 12 Jan 2010 23:00:20 +0000 (18:00 -0500)
committerBen Klang <ben@alkaloid.net>
Tue, 12 Jan 2010 23:04:01 +0000 (18:04 -0500)
operator/config/conf.xml
operator/export.php [new file with mode: 0644]
operator/lib/Driver/asterisksql.php
operator/lib/Form/SearchCDR.php
operator/lib/Operator.php

index 3990892..54de86f 100644 (file)
@@ -1,5 +1,4 @@
 <?xml version="1.0"?>
-<!-- $Horde: incubator/operator/config/conf.xml,v 1.2 2008/07/05 14:38:23 bklang Exp $ -->
 <configuration>
  <configsection name="storage">
   <configheader>Storage System Settings</configheader>
 
  <configsection name="menu">
   <configheader>Menu Settings</configheader>
+  <configboolean name="export" desc="Should we display an Export
+  link in Operator's menu?">true</configboolean>
   <configmultienum name="apps" desc="Select any applications that should be
   linked in Operator's menu">
    <values>
-    <configspecial name="list-horde-apps" />
+    <configspecial name="list-horde-apps"/>
    </values>
   </configmultienum>
  </configsection>
diff --git a/operator/export.php b/operator/export.php
new file mode 100644 (file)
index 0000000..2b67572
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Ben Klang <ben@alkaloid.net>
+ */
+
+require_once dirname(__FILE__) . '/lib/Application.php';
+
+$operator = new Operator_Application(array('init' => true));
+$cache = &$GLOBALS['cache'];
+
+require_once OPERATOR_BASE . '/lib/Form/SearchCDR.php';
+
+$renderer = new Horde_Form_Renderer();
+$vars = Horde_Variables::getDefaultVariables();
+$data = array();
+
+if (!$vars->exists('rowstart')) {
+    $rowstart = 0;
+} elseif (!is_numeric($rowstart = $vars->get('rowstart'))) {
+    $notification->push(_("Invalid number for row start.  Using 0."));
+    $rowstart = 0;
+}
+
+if (isset($_SESSION['operator']['lastdata'])) {
+    $data = $_SESSION['operator']['lastdata'];
+}
+
+$form = new ExportCDRForm(_("Export Call Detail Records"), $vars);
+if ($form->isSubmitted() && $form->validate($vars, true)) {
+    try {
+        $_SESSION['operator']['lastsearch']['params'] = array(
+            'accountcode' => $vars->get('accountcode'),
+            'dcontext' => $vars->get('dcontext'),
+            'startdate' => $vars->get('startdate'),
+            'enddate' => $vars->get('enddate'));
+        $_SESSION['operator']['lastdata'] = $data;
+
+        $form->execute();
+
+    } catch (Exception $e) {
+        //$notification->push(_("Invalid date requested."));
+        $notification->push($e);
+        $data = array();
+    }
+} else {
+    if (isset($_SESSION['operator']['lastsearch']['params'])) {
+        foreach($_SESSION['operator']['lastsearch']['params'] as $var => $val) {
+            $vars->set($var, $val);
+        }
+    }
+}
+
+$title = _("Export Call Detail Records");
+
+require OPERATOR_TEMPLATES . '/common-header.inc';
+require OPERATOR_TEMPLATES . '/menu.inc';
+$notification->notify();
+$form->renderActive($renderer, $vars);
+
+$columns = unserialize($prefs->getValue('columns'));
+if (!empty($data)) {
+    require OPERATOR_TEMPLATES . '/search.inc';
+}
+
+require $registry->get('templates', 'horde') . '/common-footer.inc';
index 80d50c5..d9e7717 100644 (file)
@@ -79,7 +79,7 @@ class Operator_Driver_asterisksql extends Operator_Driver {
      * @throws Operator_Exception|Horde_Date_Exception
      */
     protected function _getRecords($start, $end, $accountcode = null, $dcontext = null,
-                         $rowstart = 0, $rowlimit = 100)
+                         $rowstart = 0, $rowlimit = null)
     {
 
         // Use the query to make the MySQL driver look like the CDR-CSV driver
@@ -95,7 +95,7 @@ class Operator_Driver_asterisksql extends Operator_Driver {
             Horde::logMessage('Invalid start row requested.', __FILE__, __LINE__, PEAR_LOG_ERR);
             throw new Operator_Exception(_("Internal error.  Details have been logged for the administrator."));
         }
-        if (!is_numeric($rowlimit)) {
+        if (!is_null($rowlimit) && !is_numeric($rowlimit)) {
             Horde::logMessage('Invalid row limit requested.', __FILE__, __LINE__, PEAR_LOG_ERR);
             throw new Operator_Exception(_("Internal error.  Details have been logged for the administrator."));
         }
@@ -130,7 +130,11 @@ class Operator_Driver_asterisksql extends Operator_Driver {
         Horde::logMessage(sprintf('Operator_Driver_asterisksql::getData(): %s', $sql), __FILE__, __LINE__, PEAR_LOG_DEBUG);
 
         /* Execute the query. */
-        $res = $this->_db->limitQuery($sql, $rowstart, $rowlimit, $values);
+        if (is_null($rowlimit)) {
+            $res = $this->_db->query($sql, $values);
+        } else {
+            $res = $this->_db->limitQuery($sql, $rowstart, $rowlimit, $values);
+        }
         if (is_a($res, 'PEAR_Error')) {
             Horde::logMessage($res, __FILE__, __LINE__, PEAR_LOG_ERR);
             throw new Operator_Exception(_("Internal error.  Details have been logged for the administrator."));
index 261c73e..0899b54 100644 (file)
@@ -17,7 +17,7 @@ class SearchCDRForm extends Horde_Form {
 
     public function __construct($title, &$vars)
     {
-        parent::Horde_Form($vars, $title);
+        parent::__construct($vars, $title);
 
         // FIXME: Generate a list of clients from Turba?
         //$clients =
@@ -66,9 +66,68 @@ class SearchCDRForm extends Horde_Form {
         $params = array($start_year, $end_year, $picker, $format_in,
                         $format_out, $show_seconds);
 
-        $this->addVariable(_("Account Code"), 'accountcode', 'enum', false, false, null, array($accountcodes));
-        $this->addVariable(_("Destination Context"), 'dcontext', 'text', false, false, _("An empty destination context will match all destination contexts."));
-        $this->addVariable(_("Start Date & Time"), 'startdate', 'datetime', true, false, null, $params);
-        $this->addVariable(_("End Date & Time"), 'enddate', 'datetime', true, false, null, $params);
+        $this->addVariable(_("Account Code"), 'accountcode', 'enum', false,
+                           false, null, array($accountcodes));
+        $this->addVariable(_("Destination Context"), 'dcontext', 'text', false,
+                           false, _("An empty destination context will match all destination contexts."));
+        $this->addVariable(_("Start Date & Time"), 'startdate', 'datetime',
+                           true, false, null, $params);
+        $this->addVariable(_("End Date & Time"), 'enddate', 'datetime', true,
+                           false, null, $params);
+    }
+}
+
+class ExportCDRForm extends SearchCDRForm
+{
+    public function __construct($title, &$vars)
+    {
+        parent::__construct($title, $vars);
+
+        $formats = array(
+            Horde_Data::EXPORT_CSV => 'Comma-Delimited (CSV)',
+            Horde_Data::EXPORT_TSV => 'Tab-Delimited',
+        );
+
+        $this->addVariable(_("Data Format"), 'format', 'enum', true, false,
+                           null, array($formats));
+    }
+
+    public function execute()
+    {
+        global $operator;
+        if (empty($operator) || empty($operator->driver)) {
+            $operator = new Operator_Application(array('init' => true));
+        }
+
+        $start = new Horde_Date($this->_vars->get('startdate'));
+        $end = new Horde_Date($this->_vars->get('enddate'));
+        $accountcode = $this->_vars->get('accountcode');
+        $dcontects = $this->_vars->get('dcontext');
+        if (empty($dcontext)) {
+            $dcontext = '%';
+        }
+        list($stats, $data) = $operator->driver->getRecords($start, $end,
+                                                            $accountcode,
+                                                            $dcontext, 0,
+                                                            null);
+        switch($this->_vars->get('format')) {
+        case Horde_Data::EXPORT_CSV:
+            $ext = 'csv';
+            $fmt = Horde_Data::singleton('csv');
+            break;
+
+        case Horde_Data::EXPORT_TSV:
+            $ext = 'tsv';
+            $fmt = Horde_Data::singleton('tsv');
+            break;
+
+        default:
+            throw new Operator_Exception(_("Invalid data format requested."));
+            break;
+        }
+
+        $filename = 'export-' . uniqid() . '.' . $ext;
+        $fmt->exportFile($filename, $data, true);
+        exit;
     }
 }
index 951cc4f..26e0ba1 100644 (file)
@@ -2,8 +2,6 @@
 /**
  * Operator Base Class.
  *
- * $Horde: incubator/operator/lib/Operator.php,v 1.18 2009/12/01 12:52:49 jan Exp $
- *
  * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
  *
  * See the enclosed file COPYING for license information (GPL). If you
@@ -17,13 +15,18 @@ class Operator {
     /**
      * Build Operator's list of menu items.
      */
-    function getMenu($returnType = 'object')
+    public static function getMenu($returnType = 'object')
     {
         global $conf, $registry, $browser, $print_link;
 
         $menu = new Horde_Menu(Horde_Menu::MASK_ALL);
-        $menu->add(Horde::applicationUrl('viewgraph.php'), _("View Graphs"), 'graphs.png', null, null, null, basename($_SERVER['PHP_SELF']) == 'index.php' ? 'current' : null);
-        $menu->add(Horde::applicationUrl('search.php'), _("Search"), 'search.png', $registry->getImageDir('horde'));
+        $menu->add(Horde::applicationUrl('viewgraph.php'), _("_View Graphs"), 'graphs.png', null, null, null, basename($_SERVER['PHP_SELF']) == 'index.php' ? 'current' : null);
+        $menu->add(Horde::applicationUrl('search.php'), _("_Search"), 'search.png', $registry->getImageDir('horde'));
+
+        /* Export */
+        if ($GLOBALS['conf']['menu']['export']) {
+            $menu->add(Horde::applicationUrl('export.php'), _("_Export"), 'data.png', $GLOBALS['registry']->getImageDir('horde'));
+        }
 
         if ($returnType == 'object') {
             return $menu;
@@ -32,7 +35,7 @@ class Operator {
         }
     }
 
-    function getColumns()
+    public static function getColumns()
     {
         #static $columns = array(
         $columns = array(
@@ -59,13 +62,13 @@ class Operator {
     }
 
 
-    function getColumnName($column)
+    public static function getColumnName($column)
     {
         $columns = Operator::getColumns();
         return $columns[$column];
     }
 
-    function getAMAFlagName($flagid)
+    public static function getAMAFlagName($flagid)
     {
         // See <asterisk/cdr.h> for definitions
         switch($flagid) {
@@ -86,13 +89,13 @@ class Operator {
      *
      * @return array  List of valid account codes.
      */
-    function getAccountCodes($permfilter = false)
+    public static function getAccountCodes($permfilter = false)
     {
         global $operator;
         if (empty($operator) || empty($operator->driver)) {
             $operator = new Operator_Application(array('init' => true));
         }
-        
+
         // Set up arrays for filtering
         $keys = $values = $operator->driver->getAccountCodes();
 
@@ -146,32 +149,41 @@ class Operator {
         return $accountcodes;
     }
 
-    function getGraphInfo($graphid)
+    public static function getGraphInfo($graphid = null)
     {
-        switch($graphid) {
-        case 'numcalls':
-            return array(
-                'title' => _("Number of Calls by Month"),
-                'axisX' => _("Month"),
-                'axisY' => _("Number of Calls"),
-            );
-            break;
-         case 'minutes':
-            return array(
-                'title' => _("Total Minutes Used by Month"),
-                'axisX' => _("Month"),
-                'axisY' => _("Minute"),
-                'numberformat' => '%0.1f',
-            );
-            break;
-         case 'failed':
-            return array(
-                'title' => _("Number of Failed Calls by Month"),
-                'axisX' => _("Month"),
-                'axisY' => _("Failed Calls"),
-            );
-            break;
-         }
+        static $graphs;
+
+        if (empty($graphs)) {
+            $graphs = array(
+                'numcalls' => array(
+                    'title' => _("Number of Calls by Month"),
+                    'axisX' => _("Month"),
+                    'axisY' => _("Number of Calls"),
+                ),
+                'minutes' => array(
+                    'title' => _("Total Minutes Used by Month"),
+                    'axisX' => _("Month"),
+                    'axisY' => _("Minute"),
+                    'numberformat' => '%0.1f',
+                ),
+
+                'failed' => array(
+                    'title' => _("Number of Failed Calls by Month"),
+                    'axisX' => _("Month"),
+                    'axisY' => _("Failed Calls"),
+                ),
+             );
+        }
+
+        if ($graphid === null) {
+            return $graphs;
+        }
+
+        if (isset($graphs[$graphid])) {
+            return $graphs[$graphid];
+        } else {
+            throw new Operator_Exception(_("Invalid graph type."));
+        }
     }
 
 }