PageRenderTime 296ms CodeModel.GetById 81ms app.highlight 135ms RepoModel.GetById 66ms app.codeStats 1ms

/web/admin/importcsv.php

https://bitbucket.org/yoander/mtrack
PHP | 326 lines | 282 code | 44 blank | 0 comment | 43 complexity | e8876ee907f4395fca622466948ac12e MD5 | raw file
  1<?php # vim:ts=2:sw=2:et:
  2include '../../inc/common.php';
  3
  4MTrackACL::requireAllRights("Tickets", 'create');
  5session_start();
  6
  7$field_aliases = array(
  8  'state' => 'status',
  9  'pri' => 'priority',
 10  'id' => 'ticket',
 11  'type' => 'classification',
 12);
 13$supported_fields = array(
 14  'classification',
 15  'ticket',
 16  'milestone',
 17  '-milestone',
 18  '+milestone',
 19  'summary',
 20  'status',
 21  'priority',
 22  'estimated',
 23  'owner',
 24  'type',
 25  'component',
 26  '-component',
 27  '+component',
 28  'description'
 29);
 30foreach ($supported_fields as $i => $f) {
 31  unset($supported_fields[$i]);
 32  $supported_fields[$f] = $f;
 33}
 34
 35$C = MTrackTicket_CustomFields::getInstance();
 36foreach ($C->getFields() as $f) {
 37  $name = substr($f->name, 2);
 38  $supported_fields[$f->name] = $f->name;
 39  if (!isset($field_aliases[$name])) {
 40    $field_aliases[$name] = $f->name;
 41  }
 42}
 43
 44if ($_SERVER['REQUEST_METHOD'] == 'POST') {
 45
 46  if (isset($_FILES['csvfile']) && $_FILES['csvfile']['error'] == 0
 47      && is_uploaded_file($_FILES['csvfile']['tmp_name'])) {
 48    ini_set('auto_detect_line_endings', true);
 49    $fp = fopen($_FILES['csvfile']['tmp_name'], 'r');
 50    $header = fgetcsv($fp);
 51    $err = array();
 52    $output = array();
 53    foreach ($header as $i => $name) {
 54      $name = strtolower($name);
 55      if (isset($field_aliases[$name])) {
 56        $name = $field_aliases[$name];
 57      }
 58      if (!isset($supported_fields[$name])) {
 59        $err[] = "Unsupported field: $name";
 60      }
 61      $header[$i] = $name;
 62    }
 63    $db = MTrackDB::get();
 64    $db->beginTransaction();
 65    MTrackChangeset::$use_txn = false;
 66    $todo = array();
 67    do {
 68      $line = fgetcsv($fp);
 69      if ($line === false) break;
 70
 71      $item = array();
 72      foreach ($header as $i => $name) {
 73        $item[$name] = $line[$i];
 74      }
 75
 76      if (isset($item['ticket']) && strlen($item['ticket'])) {
 77        $id = $item['ticket'];
 78        if ($id[0] == '#') {
 79          $id = substr($id, 1);
 80        }
 81        try {
 82          $tkt = MTrackIssue::loadByNSIdent($id);
 83          if ($tkt == null) {
 84            $err[] = "No such ticket $id";
 85            continue;
 86          }
 87        } catch (Exception $e) {
 88          $err[] = $e->getMessage();
 89          continue;
 90        }
 91        $output[] = "<b>Updating ticket $tkt->nsident</b><br>\n";
 92      } else {
 93        $tkt = new MTrackIssue;
 94        $tkt->priority = 'normal';
 95        $tkt->get_next_nsident();
 96        $output[] = "<b>Creating ticket $tkt->nsident<b><br>\n";
 97      }
 98      $CS = MTrackChangeset::begin("ticket:X", $_POST['comment']);
 99      if (strlen(trim($_POST['comment']))) {
100        $tkt->addComment($_POST['comment']);
101      }
102      foreach ($item as $name => $value) {
103        if ($name == 'ticket') {
104          continue;
105        }
106        $output[] = "$name => $value<br>\n";
107        try {
108          switch ($name) {
109            case 'summary':
110            case 'description':
111            case 'classification':
112            case 'priority':
113            case 'severity':
114            case 'changelog':
115            case 'owner':
116            case 'cc':
117            case 'estimated':
118            case 'status':
119              $tkt->$name = strlen($value) ? $value : null;
120              break;
121            case 'milestone':
122              if (strlen($value)) {
123                foreach ($tkt->getMilestones() as $mid) {
124                  $tkt->dissocMilestone($mid);
125                }
126                $tkt->assocMilestone($value);
127              }
128              break;
129            case '+milestone':
130              if (strlen($value)) {
131                $tkt->assocMilestone($value);
132              }
133              break;
134            case '-milestone':
135              if (strlen($value)) {
136                $tkt->dissocMilestone($value);
137              }
138              break;
139            case 'component':
140              if (strlen($value)) {
141                foreach ($tkt->getComponents() as $mid) {
142                  $tkt->dissocComponent($mid);
143                }
144                $tkt->assocComponent($value);
145              }
146              break;
147            case '+component':
148              if (strlen($value)) {
149                $tkt->assocComponent($value);
150              }
151              break;
152            case '-component':
153              if (strlen($value)) {
154                $tkt->dissocComponent($value);
155              }
156              break;
157            default:
158              if (!strncmp($name, 'x_', 2)) {
159                $tkt->{$name} = $value;
160              }
161              break;
162          }
163        } catch (Exception $e) {
164          $err[] = $e->getMessage();
165        }
166      }
167      $tkt->save($CS);
168      $CS->setObject("ticket:" . $tkt->tid);
169
170    } while (true);
171    $_SESSION['admin.import.result'] = array($err, $output);
172    if (count($err)) {
173      $db->rollback();
174    } else {
175      $db->commit();
176    }
177  }
178  header("Location: {$ABSWEB}admin/importcsv.php");
179  exit;
180}
181
182if (isset($_SESSION['admin.import.result'])) {
183  list($err, $info) = $_SESSION['admin.import.result'];
184  unset($_SESSION['admin.import.result']);
185
186  mtrack_head(count($err) ? 'Import Failed' : 'Import Complete');
187
188  foreach ($info as $line) {
189    echo $line;
190  }
191
192  if (count($err)) {
193    echo "The following errors were encountered:<br>\n";
194    foreach ($err as $msg) {
195      echo htmlentities($msg) . "<br>\n";
196    }
197    echo "<br><b>No changes were committed</b><br>\n";
198  } else {
199    echo "<br><b>Done!</b>\n";
200  }
201
202  mtrack_foot();
203  exit;
204}
205
206mtrack_head('Import');
207
208?>
209<h1>Import/Update via CSV</h1>
210
211<p>
212You may use this facility to change ticket properties en-masse by uploading
213a CSV file.
214</p>
215
216<ul>
217  <li>If a ticket column is present and non-empty,
218    that ticket will be updated</li>
219  <li>If there is no ticket column, or the ticket column is empty,
220    then a ticket will be created</li>
221  <li>If any errors are detected, none of the changes from the CSV file
222    will be applied</li>
223</ul>
224
225<p>
226The input file must be a CSV file with the field names on the first line.
227</p>
228
229<p>
230The following fields are supported:
231</p>
232
233<dl>
234  <dt>ticket</dt>
235  <dd>The ticket number</dd>
236
237  <dt>milestone</dt>
238  <dd>The value to use for the milestone.  If updating an existing ticket,
239   this field will remove any other milestones in the ticket and set it to
240   only this value.
241  </dd>
242
243  <dt>-milestone</dt>
244  <dd>Removes a milestone; if the ticket is associated with the named milestone,
245   it will be removed from that milestone.
246  </dd>
247
248  <dt>+milestone</dt>
249  <dd>Associates the ticket with the named milestone, preserving any other
250  milestones currently associated with the ticket.
251  </dd>
252
253  <dt>summary</dt>
254  <dd>Sets the summary for the ticket</dd>
255
256  <dt>status or state</dt>
257  <dd>Sets the state of the ticket; can be one of the configured ticket states
258  </dd>
259
260  <dt>priority</dt>
261  <dd>Sets the priority; can be one of the configured priorities</dd>
262
263  <dt>owner</dt>
264  <dd>Sets the owner</dd>
265
266  <dt>type</dt>
267  <dd>Sets the ticket type</dd>
268
269  <dt>estimated</dt>
270  <dd>Estimated time (hours)</dd>
271
272  <dt>component</dt>
273  <dd>Sets the component, replacing all other component associations</dd>
274
275  <dt>-component</dt>
276  <dd>Removes association with the named component</dd>
277
278  <dt>+component</dt>
279  <dd>Associates with the named component, preserving existing associations</dd>
280
281  <dt>description</dt>
282  <dd>Sets the description of the ticket</dd>
283
284<?php
285
286foreach ($C->getFields() as $f) {
287  $name = substr($f->name, 2);
288  if (!isset($field_aliases[$name]) || $field_aliases[$name] != $f->name) {
289    $name = $f->name;
290    echo "<dt>$name</dt>\n";
291  } else {
292    echo "<dt>$name</dt>\n";
293    echo "<dt>$f->name</dt>\n";
294  }
295  echo "<dd>" . htmlentities($f->label, ENT_QUOTES, 'utf-8') . "\n";
296
297  if ($f->type == 'select') {
298    echo "<br>Value may be one of:<br>";
299    $data = $f->ticketData();
300    foreach ($data['options'] as $opt) {
301      echo " <tt>" . htmlentities($opt, ENT_QUOTES, 'utf-8') . "</tt><br>";
302    }
303  }
304
305  echo "</dd>\n";
306}
307
308?>
309
310</dl>
311
312<h2>Import</h2>
313
314<p>Enter a comment in the box below; it will be added as a comment to
315all affected tickets</p>
316
317<form method='post' enctype='multipart/form-data'>
318  <textarea name='comment' id='comment'
319    class='code wiki' rows='4' cols='78'></textarea>
320  <input type='file' name='csvfile'>
321  <input type='submit' value='Import'>
322</form>
323
324<?php
325mtrack_foot();
326