final move of files
[web.mtrack] / admin / importcsv.php
diff --git a/admin/importcsv.php b/admin/importcsv.php
new file mode 100644 (file)
index 0000000..a79651a
--- /dev/null
@@ -0,0 +1,325 @@
+<?php # vim:ts=2:sw=2:et:
+include '../../inc/common.php';
+
+MTrackACL::requireAllRights("Tickets", 'create');
+session_start();
+
+$field_aliases = array(
+  'state' => 'status',
+  'pri' => 'priority',
+  'id' => 'ticket',
+  'type' => 'classification',
+);
+$supported_fields = array(
+  'classification',
+  'ticket',
+  'milestone',
+  '-milestone',
+  '+milestone',
+  'summary',
+  'status',
+  'priority',
+  'owner',
+  'type',
+  'component',
+  '-component',
+  '+component',
+  'description'
+);
+foreach ($supported_fields as $i => $f) {
+  unset($supported_fields[$i]);
+  $supported_fields[$f] = $f;
+}
+
+$C = MTrackTicket_CustomFields::getInstance();
+foreach ($C->fields as $f) {
+  $name = substr($f->name, 2);
+  $supported_fields[$f->name] = $f->name;
+  if (!isset($field_aliases[$name])) {
+    $field_aliases[$name] = $f->name;
+  }
+}
+
+if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+  if (isset($_FILES['csvfile']) && $_FILES['csvfile']['error'] == 0
+      && is_uploaded_file($_FILES['csvfile']['tmp_name'])) {
+    ini_set('auto_detect_line_endings', true);
+    $fp = fopen($_FILES['csvfile']['tmp_name'], 'r');
+    $header = fgetcsv($fp);
+    $err = array();
+    $output = array();
+    foreach ($header as $i => $name) {
+      $name = strtolower($name);
+      if (isset($field_aliases[$name])) {
+        $name = $field_aliases[$name];
+      }
+      if (!isset($supported_fields[$name])) {
+        $err[] = "Unsupported field: $name";
+      }
+      $header[$i] = $name;
+    }
+    $db = MTrackDB::get();
+    $db->beginTransaction();
+    MTrackChangeset::$use_txn = false;
+    $todo = array();
+    do {
+      $line = fgetcsv($fp);
+      if ($line === false) break;
+
+      $item = array();
+      foreach ($header as $i => $name) {
+        $item[$name] = $line[$i];
+      }
+
+      if (isset($item['ticket'])) {
+        $id = $item['ticket'];
+        if ($id[0] == '#') {
+          $id = substr($id, 1);
+        }
+        try {
+          $tkt = MTrackIssue::loadByNSIdent($id);
+          if ($tkt == null) {
+            $err[] = "No such ticket $id";
+            continue;
+          }
+        } catch (Exception $e) {
+          $err[] = $e->getMessage();
+          continue;
+        }
+        $output[] = "<b>Updating ticket $tkt->nsident</b><br>\n";
+      } else {
+        $tkt = new MTrackIssue;
+        $tkt->priority = 'normal';
+        list($tkt->nsident) = MTrackDB::q(
+          'select max(cast(nsident as integer)) + 1 from tickets')
+          ->fetchAll(PDO::FETCH_COLUMN, 0);
+        if ($tkt->nsident === null) {
+          $tkt->nsident = 1;
+        }
+        $output[] = "<b>Creating ticket $tkt->nsident<b><br>\n";
+      }
+      $CS = MTrackChangeset::begin("ticket:X", $_POST['comment']);
+      if (strlen(trim($_POST['comment']))) {
+        $tkt->addComment($_POST['comment']);
+      }
+      foreach ($item as $name => $value) {
+        if ($name == 'ticket') {
+          continue;
+        }
+        $output[] = "$name => $value<br>\n";
+        try {
+          switch ($name) {
+            case 'summary':
+            case 'description':
+            case 'classification':
+            case 'priority':
+            case 'severity':
+            case 'changelog':
+            case 'owner':
+            case 'cc':
+              $tkt->$name = strlen($value) ? $value : null;
+              break;
+            case 'milestone':
+              if (strlen($value)) {
+                foreach ($tkt->getMilestones() as $mid) {
+                  $tkt->dissocMilestone($mid);
+                }
+                $tkt->assocMilestone($value);
+              }
+              break;
+            case '+milestone':
+              if (strlen($value)) {
+                $tkt->assocMilestone($value);
+              }
+              break;
+            case '-milestone':
+              if (strlen($value)) {
+                $tkt->dissocMilestone($value);
+              }
+              break;
+            case 'component':
+              if (strlen($value)) {
+                foreach ($tkt->getComponents() as $mid) {
+                  $tkt->dissocComponent($mid);
+                }
+                $tkt->assocComponent($value);
+              }
+              break;
+            case '+component':
+              if (strlen($value)) {
+                $tkt->assocComponent($value);
+              }
+              break;
+            case '-component':
+              if (strlen($value)) {
+                $tkt->dissocComponent($value);
+              }
+              break;
+            default:
+              if (!strncmp($name, 'x_', 2)) {
+                $tkt->{$name} = $value;
+              }
+              break;
+          }
+        } catch (Exception $e) {
+          $err[] = $e->getMessage();
+        }
+      }
+      $tkt->save($CS);
+      $CS->setObject("ticket:" . $tkt->tid);
+
+    } while (true);
+    $_SESSION['admin.import.result'] = array($err, $output);
+    if (count($err)) {
+      $db->rollback();
+    } else {
+      $db->commit();
+    }
+  }
+  header("Location: {$ABSWEB}admin/importcsv.php");
+  exit;
+}
+
+if (isset($_SESSION['admin.import.result'])) {
+  list($err, $info) = $_SESSION['admin.import.result'];
+  unset($_SESSION['admin.import.result']);
+
+  mtrack_head(count($err) ? 'Import Failed' : 'Import Complete');
+
+  foreach ($info as $line) {
+    echo $line;
+  }
+
+  if (count($err)) {
+    echo "The following errors were encountered:<br>\n";
+    foreach ($err as $msg) {
+      echo htmlentities($msg) . "<br>\n";
+    }
+    echo "<br><b>No changes were committed</b><br>\n";
+  } else {
+    echo "<br><b>Done!</b>\n";
+  }
+
+  mtrack_foot();
+  exit;
+}
+
+mtrack_head('Import');
+
+?>
+<h1>Import/Update via CSV</h1>
+
+<p>
+You may use this facility to change ticket properties en-masse by uploading
+a CSV file.
+</p>
+
+<ul>
+  <li>If a ticket column is present and non-empty,
+    that ticket will be updated</li>
+  <li>If there is no ticket column, or the ticket column is empty,
+    then a ticket will be created</li>
+  <li>If any errors are detected, none of the changes from the CSV file
+    will be applied</li>
+</ul>
+
+<p>
+The input file must be a CSV file with the field names on the first line.
+</p>
+
+<p>
+The following fields are supported:
+</p>
+
+<dl>
+  <dt>ticket</dt>
+  <dd>The ticket number</dd>
+
+  <dt>milestone</dt>
+  <dd>The value to use for the milestone.  If updating an existing ticket,
+   this field will remove any other milestones in the ticket and set it to
+   only this value.
+  </dd>
+
+  <dt>-milestone</dt>
+  <dd>Removes a milestone; if the ticket is associated with the named milestone,
+   it will be removed from that milestone.
+  </dd>
+
+  <dt>+milestone</dt>
+  <dd>Associates the ticket with the named milestone, preserving any other
+  milestones currently associated with the ticket.
+  </dd>
+
+  <dt>summary</dt>
+  <dd>Sets the summary for the ticket</dd>
+
+  <dt>status or state</dt>
+  <dd>Sets the state of the ticket; can be one of the configured ticket states
+  </dd>
+
+  <dt>priority</dt>
+  <dd>Sets the priority; can be one of the configured priorities</dd>
+
+  <dt>owner</dt>
+  <dd>Sets the owner</dd>
+
+  <dt>type</dt>
+  <dd>Sets the ticket type</dd>
+
+  <dt>component</dt>
+  <dd>Sets the component, replacing all other component associations</dd>
+
+  <dt>-component</dt>
+  <dd>Removes association with the named component</dd>
+
+  <dt>+component</dt>
+  <dd>Associates with the named component, preserving existing associations</dd>
+
+  <dt>description</dt>
+  <dd>Sets the description of the ticket</dd>
+
+<?php
+
+foreach ($C->fields as $f) {
+  $name = substr($f->name, 2);
+  if (!isset($field_aliases[$name]) || $field_aliases[$name] != $f->name) {
+    $name = $f->name;
+    echo "<dt>$name</dt>\n";
+  } else {
+    echo "<dt>$name</dt>\n";
+    echo "<dt>$f->name</dt>\n";
+  }
+  echo "<dd>" . htmlentities($f->label, ENT_QUOTES, 'utf-8') . "\n";
+
+  if ($f->type == 'select') {
+    echo "<br>Value may be one of:<br>";
+    $data = $f->ticketData();
+    foreach ($data['options'] as $opt) {
+      echo " <tt>" . htmlentities($opt, ENT_QUOTES, 'utf-8') . "</tt><br>";
+    }
+  }
+
+  echo "</dd>\n";
+}
+
+?>
+
+</dl>
+
+<h2>Import</h2>
+
+<p>Enter a comment in the box below; it will be added as a comment to
+all affected tickets</p>
+
+<form method='post' enctype='multipart/form-data'>
+  <textarea name='comment' id='comment'
+    class='code wiki' rows='4' cols='78'></textarea>
+  <input type='file' name='csvfile'>
+  <input type='submit' value='Import'>
+</form>
+
+<?php
+mtrack_foot();
+