UnfuddleMigration.inc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. <?php
  2. /**
  3. * @file
  4. * Migration class to import user accounts from an Unfuddle dump.
  5. *
  6. * @copyright Coyright (c) 2011 Ouest Systèmes Informatiques (OSI, OSInet)
  7. *
  8. * @license Licensed under the General Public License version 2 and later, and the CeCILL 2.0 license.
  9. */
  10. /**
  11. * Information/utility class about CaseTracker in a Migrate context.
  12. */
  13. class CaseTrackerMeta {
  14. public static function getFields() {
  15. $fields = array(
  16. 'pid' => t('Case project'),
  17. 'case_number' => t('Case number'),
  18. 'assign_to' => t('Case assigned to'),
  19. 'case_priority_id' => t('Case priority'),
  20. 'case_type_id' => t('Case type'),
  21. 'case_status_id' => t('Case status'),
  22. );
  23. return $fields;
  24. }
  25. }
  26. class MigrateDestinationCommentFollowup extends MigrateDestinationComment {
  27. public function __construct() {
  28. parent::__construct(MigrateDestinationNodeCase::ct_bundle);
  29. }
  30. /**
  31. * Could also be implemented as a migrate hook_migrate_fields() in module.
  32. * Which is best ?
  33. *
  34. * (non-PHPdoc)
  35. * @see MigrateDestinationComment::fields()
  36. */
  37. public function fields() {
  38. $fields = parent::fields();
  39. $fields['revision_id'] = t('Ticket revision ID');
  40. $fields += CaseTrackerMeta::getFields();
  41. return $fields;
  42. }
  43. public function prepare($entity, stdClass $source_row) {
  44. $ticket = node_load($entity->nid);
  45. // ticket->casetracker is assigned by CT to both new and old, so clone is
  46. // required, to avoid overwriting one state with the other
  47. $entity->casetracker = clone $ticket->casetracker;
  48. // CT needs a name in assign_to, not an uid
  49. $entity->casetracker->assign_to = casetracker_get_name($entity->casetracker->assign_to);
  50. //print_r($entity);
  51. $entity->revision_id = $ticket->vid;
  52. }
  53. }
  54. /**
  55. * Casetracker-specific destination node handling
  56. */
  57. class MigrateDestinationNodeCase extends MigrateDestinationNode {
  58. const ct_bundle = 'casetracker_basic_case';
  59. public function __construct() {
  60. parent::__construct(self::ct_bundle);
  61. }
  62. /**
  63. * Could also be implemented as a migrate hook_migrate_fields() in module.
  64. * Which is best ?
  65. *
  66. * (non-PHPdoc)
  67. * @see MigrateDestinationNode::fields()
  68. */
  69. public function fields() {
  70. $fields = parent::fields();
  71. $fields += CaseTrackerMeta::getFields();
  72. return $fields;
  73. }
  74. /**
  75. * Casetracker saves its fields in a separate stdClass extra field.
  76. *
  77. * (non-PHPdoc)
  78. * @see MigrateDestinationEntity::prepare()
  79. */
  80. public function prepare($entity, stdClass $source_row) {
  81. $extraFields = array(
  82. 'pid',
  83. 'case_priority_id',
  84. 'case_type_id',
  85. 'assign_to',
  86. 'case_status_id',
  87. );
  88. $entity->casetracker = new stdClass();
  89. foreach ($extraFields as $extra) {
  90. if (isset($entity->$extra)) {
  91. $entity->casetracker->$extra = $entity->$extra;
  92. unset($entity->extra);
  93. }
  94. }
  95. $entity->casetracker->case_type_id = 9; // Bug. Case type is not a default Unfuddle field
  96. }
  97. }
  98. abstract class UnfuddleMigration extends XMLMigration {
  99. protected static $fields = array();
  100. public $item_ID_xpath = 'id';
  101. public $items_url;
  102. public $unmigratedDestinations = array();
  103. public $unmigratedSources = array();
  104. public function __construct($group) {
  105. parent::__construct($group);
  106. $this->items_url = file_directory_path() . '/unfuddle/backup.xml';
  107. }
  108. public function addXPathFieldMapping($destination_field, $source_field, $xpath = NULL) {
  109. if (!isset($xpath)) {
  110. $xpath = $source_field;
  111. }
  112. return $this->addFieldMapping($destination_field, $source_field)
  113. ->xpath($xpath);
  114. }
  115. public function afterConstruct() {
  116. $this->addUnmigratedDestinations($this->unmigratedDestinations, t('White hole'));
  117. $this->addUnmigratedSources($this->unmigratedSources, t('Black hole'));
  118. }
  119. /**
  120. * Return a field-name-indexed hash of field descriptions.
  121. *
  122. * @return array
  123. */
  124. protected static function getFieldInfo() {
  125. return self::$fields;
  126. }
  127. }
  128. /**
  129. * Import "people" data from the Unfuddle backup into Drupal users
  130. */
  131. class UnfuddlePeopleMigration extends UnfuddleMigration {
  132. public $unmigratedDestinations = array(
  133. 'pass',
  134. 'roles',
  135. 'theme',
  136. 'signature',
  137. 'signature_format',
  138. 'language',
  139. 'picture',
  140. );
  141. public $unmigratedSources = array(
  142. 'account-id',
  143. 'first-name',
  144. 'identity-url',
  145. 'is-administrator',
  146. 'last-name',
  147. 'notification-frequency',
  148. 'notification-ignore-self',
  149. 'notification-last-sent',
  150. 'notification-scope-messages',
  151. 'notification-scope-milestones',
  152. 'notification-scope-notebooks',
  153. 'notification-scope-source',
  154. 'notification-scope-tickets',
  155. 'text-markup',
  156. 'updated-at',
  157. );
  158. public function __construct() {
  159. parent::__construct(MigrateGroup::getInstance('Unfuddle'));
  160. $item_xpath = '/account/people/person';
  161. $items_class = new MigrateItemsXML($this->items_url, $item_xpath, $this->item_ID_xpath);
  162. $this->source = new MigrateSourceMultiItems($items_class, self::getFieldInfo());
  163. $source_key = array(
  164. 'id' => array(
  165. 'type' => 'int',
  166. 'unsigned' => TRUE,
  167. 'not null' => TRUE,
  168. )
  169. );
  170. $this->destination = new MigrateDestinationUser();
  171. $this->map = new MigrateSQLMap('unfuddle_people',
  172. $source_key,
  173. MigrateDestinationUser::getKeySchema());
  174. // Do not map uid: this prevents user_save from creating the accounts.
  175. $this->addFieldMapping('uid', NULL)
  176. ->defaultValue(0);
  177. $this->addXPathFieldMapping('created', 'created-at');
  178. $this->addXPathFieldMapping('mail', 'email');
  179. $this->addXPathFieldMapping('init', 'email')
  180. ->defaultValue('support@osinet.fr')
  181. ->description(t('Unfuddle does not keep the original email address'));
  182. $this->addXPathFieldMapping('status', 'is-removed')
  183. ->description(t('Inverted when converting to user.status'));
  184. $this->addXPathFieldMapping('login', 'last-signed-in');
  185. $this->addXPathFieldMapping('access', 'last-signed-in')
  186. ->description(t('Unfuddle does not log access time, only sign in.'));
  187. $this->addXPathFieldMapping('timezone', 'time-zone')
  188. ->description(t('Only Paris and London currently supported, without DST'));
  189. $this->addXPathFieldMapping('name', 'username');
  190. $this->afterConstruct();
  191. }
  192. protected static function getFieldInfo() {
  193. self::$fields = array(
  194. 'account-id' => t('Unfuddle account Id'),
  195. 'id' => t('User account ID'),
  196. 'created-at' => t('User account creation timestamp'),
  197. 'email' => t('Current email address for the account'),
  198. 'first-name' => t('User given name'),
  199. 'identity-url' => t('OpenID URL for the user account'),
  200. 'is-administrator' => t('User is an Unfuddle project administrator'),
  201. 'is-removed' => t('User account has been removed'),
  202. 'last-name' => t('User last name'),
  203. 'last-signed-in' => t('Latest login timestamp'),
  204. 'notification-frequency' => t('Unfuddle notification frequency'),
  205. 'notification-ignore-self' => t('Ignore self when sending notifications'),
  206. 'notification-last-sent' => t('Timestamp of latest notification sent'),
  207. 'notification-scope-messages' => t('Send message notifications'),
  208. 'notification-scope-milestones' => t('Send milestones notifications'),
  209. 'notification-scope-notebooks' => t('Send notebooks notifications'),
  210. 'notification-scope-source' => t('Send source notifications'),
  211. 'notification-scope-tickets' => t('Send tickets notifications'),
  212. 'text-markup' => t('Preferred markup format for issues'),
  213. 'time-zone' => t('Timezone'),
  214. 'updated-at' => t('Timestamp of latest user account change'),
  215. 'username' => t('The user account login name'),
  216. );
  217. return parent::getFieldInfo();
  218. }
  219. /**
  220. * - Timestamp format conversion not needed, as per MigrationBase::timestamp()
  221. * - Invert is-removed to convert to status
  222. * - Convert Unfuddle timezones to Drupal offset seconds
  223. *
  224. * Do not forget type casts when reading SimpleXML elements.
  225. *
  226. * @param object $row
  227. */
  228. public function prepareRow(stdClass $row) {
  229. // Automatic via MigrationBase::timestamp()
  230. // $row->xml->{'created-at'} = strtotime($row->xml->{'created-at'});
  231. // $row->xml->{'last-signed-in'} = strtotime($row->xml->{'last-signed-in'});
  232. $row->xml->{'is-removed'} = ((int) $row->xml->{'is-removed'}) ? 0 : 1;
  233. // @todo TODO Primitive and does not account for DST
  234. $timezones = array(
  235. 'London' => 0,
  236. 'Paris' => 7200,
  237. );
  238. $timezone = (string) $row->xml->{'time-zone'};
  239. $row->xml->{'time-zone'} = empty($timezones[$timezone])
  240. ? 0
  241. : $timezones[$timezone];
  242. }
  243. }
  244. /**
  245. * Import "project" core data from Unfuddle into CaseTracker project nodes
  246. */
  247. class UnfuddleProjectMigration extends UnfuddleMigration {
  248. public $unmigratedDestinations = array(
  249. 'promote',
  250. 'sticky',
  251. 'revision',
  252. 'language',
  253. );
  254. public $unmigratedSources = array(
  255. 'account-id',
  256. 'assignee-on-resolve',
  257. 'backup-frequency',
  258. 'close-ticket-simultaneously-default',
  259. 'default-ticket-report-id',
  260. 'disk-usage',
  261. 'enable-time-tracking',
  262. 's3-access-key-id',
  263. 's3-backup-enabled',
  264. 's3-bucket-name',
  265. 'theme',
  266. 'categories', // @todo TODO Taxonomy ?
  267. );
  268. public function __construct() {
  269. parent::__construct(MigrateGroup::getInstance('Unfuddle'));
  270. $item_xpath = '/account/projects/project';
  271. $items_class = new MigrateItemsXML($this->items_url, $item_xpath, $this->item_ID_xpath);
  272. $this->source = new MigrateSourceMultiItems($items_class, self::getFieldInfo());
  273. $source_key = array(
  274. 'id' => array(
  275. 'type' => 'int',
  276. 'unsigned' => TRUE,
  277. 'not null' => TRUE,
  278. )
  279. );
  280. $this->dependencies = array('UnfuddlePeople');
  281. $this->destination = new MigrateDestinationNode('casetracker_basic_project');
  282. $this->map = new MigrateSQLMap('unfuddle_project',
  283. $source_key,
  284. MigrateDestinationNode::getKeySchema());
  285. $this->addXPathFieldMapping('created', 'created-at');
  286. $this->addXPathFieldMapping('status', 'archived')
  287. ->description(t('Inverted when converting to node.status'));
  288. $this->addXPathFieldMapping('body', 'description')
  289. ->description(t('Applying site default input format: will often be wrong'));
  290. $this->addXPathFieldMapping('teaser', node_teaser('description', FILTER_FORMAT_DEFAULT))
  291. ->description(t('Applying site default input format: will often be wrong'));
  292. $this->addXPathFieldMapping('changed', 'updated-at');
  293. $this->addXPathFieldMapping('title', 'title');
  294. $this->addXPathFieldMapping('uid', NULL)
  295. ->sourceMigration('UnfuddlePeople')
  296. ->defaultValue(1);
  297. $this->addXPathFieldMapping('revision_uid', NULL)
  298. ->sourceMigration('UnfuddlePeople')
  299. ->defaultValue(1);
  300. $this->addXPathFieldMapping('path', 'short-name')
  301. ->description(t('Auto-aliased to project/(short-name)'));
  302. $this->afterConstruct();
  303. }
  304. protected static function getFieldInfo() {
  305. self::$fields = array(
  306. 'account-id' => t('Unfuddle account Id'),
  307. 'archived' => t('Unpublished'),
  308. 'assignee-on-resolve' => t('The user being assigned a ticket once it is resolved'),
  309. 'backup-frequency' => t('Current email address for the account'),
  310. 'close-ticket-simultaneously-default' => t('Close tickets on Resolved status'),
  311. 'created-at' => t('Project creation timestamp'),
  312. 'default-ticket-report-id' => t('User reporting tickets by default'),
  313. 'description' => t('Description'),
  314. 'disk-usage' => t('Disk usage on Unfuddle, in bytes'),
  315. 'enable-time-tracking' => t('Enable issue time tracking'),
  316. 's3-access-key-id' => t('Amazon S3 key'),
  317. 's3-backup-enabled' => t('Periodic backups sent to Amazon S3'),
  318. 's3-bucket-name' => t('Amazon S3 destination bucket for backups'),
  319. 'short-name' => t('Short project name (machine name)'),
  320. 'theme' => t('Unfuddle main color for project theme'),
  321. /**
  322. * @todo TODO insert as CCK fields
  323. *
  324. 'ticket-fieldn-active' => (boolean),
  325. 'ticket-fieldn-disposition' => 'list',
  326. 'ticket-fieldn-title' => (label)
  327. *
  328. */
  329. 'title' => t('Title'),
  330. 'updated-at' => t('Timestamp of latest project change'),
  331. 'categories' => t('Categories'),
  332. /*
  333. * Ignored:
  334. components
  335. custom-field-values
  336. messages
  337. milestones
  338. notebooks
  339. severities
  340. tickets -> separate import
  341. ticket-reports (views)
  342. versions
  343. *
  344. */
  345. );
  346. return self::$fields;
  347. }
  348. public function preparerow(stdClass $row) {
  349. $row->xml->{'archived'} = ((int) $row->xml->{'archived'}) ? 0 : 1;
  350. $row->xml->{'short-name'} = 'project/'. drupal_clean_css_identifier($row->xml->{'short-name'});
  351. }
  352. }
  353. /**
  354. * Import ticket follow-up (comments) data from Unfuddle into CaseTracker project comments
  355. */
  356. class UnfuddleTicketFollowupMigration extends UnfuddleMigration {
  357. public $unmigratedDestinations = array(
  358. 'hostname',
  359. 'status',
  360. 'thread',
  361. 'name',
  362. 'mail',
  363. 'homepage',
  364. 'language',
  365. 'revision_id',
  366. );
  367. public $unmigratedSources = array(
  368. 'created-at',
  369. 'body-format',
  370. 'parent-type',
  371. );
  372. protected static function getFieldInfo() {
  373. self::$fields = array(
  374. 'author-id' => t('Comment author'),
  375. 'body' => t('Comment text. Applying site default input format: will often be wrong'),
  376. 'body-format' => t('Comment text input format. No direct mapping to Drupal format defaults'),
  377. 'created-at' => t('The follow-up creation timestamp'),
  378. 'updated-at' => t('The follow-up latest update timestamp'),
  379. 'parent-id' => t('The ticket to which the follow-up applies'),
  380. 'parent-type' => t('The follow-up parent type: always Ticket'),
  381. 'x-subject' => t('Virtual: from body'),
  382. );
  383. return self::$fields;
  384. }
  385. public function __construct() {
  386. parent::__construct(MigrateGroup::getInstance('Unfuddle'));
  387. $item_xpath = '/account/projects/project/tickets/ticket/comments/comment';
  388. $items_class = new MigrateItemsXML($this->items_url, $item_xpath, $this->item_ID_xpath);
  389. $this->source = new MigrateSourceMultiItems($items_class, self::getFieldInfo());
  390. $source_key = array(
  391. 'id' => array(
  392. 'type' => 'int',
  393. 'unsigned' => TRUE,
  394. 'not null' => TRUE,
  395. )
  396. );
  397. $this->dependencies = array('UnfuddlePeople', 'UnfuddleProject', 'UnfuddleTicket');
  398. $this->destination = new MigrateDestinationCommentFollowup();
  399. $this->map = new MigrateSQLMap('unfuddle_ticket_followup',
  400. $source_key,
  401. MigrateDestinationNode::getKeySchema());
  402. $ar1 = drupal_map_assoc($this->unmigratedDestinations);
  403. $ar2 = CaseTrackerMeta::getFields();
  404. $this->unmigratedDestinations = array_keys($ar1 + $ar2); // Note: PID ignored
  405. $this->addXPathFieldMapping('uid', 'author-id')
  406. ->sourceMigration('UnfuddlePeople');
  407. $this->addXPathFieldMapping('comment', 'body')
  408. ->description(t('Applying site default input format: will often be wrong'));
  409. $this->addXPathFieldMapping('nid', 'parent-id')
  410. ->sourceMigration('UnfuddleTicket');
  411. $this->addXPathFieldMapping('timestamp', 'updated-at');
  412. $this->addXPathFieldMapping('subject', 'x-subject')
  413. ->description(t('Generated'));
  414. $this->afterConstruct();
  415. }
  416. public function preparerow(stdClass $row) {
  417. $row->xml->{'x-subject'} = strip_tags(node_teaser($row->xml->{'body'}, FILTER_FORMAT_DEFAULT, 40));
  418. // echo "SOURCE " . __FUNCTION__ . PHP_EOL;
  419. // print_r($row->xml);
  420. }
  421. }
  422. /**
  423. * Import ticket data from Unfuddle into CaseTracker project nodes
  424. */
  425. class UnfuddleTicketMigration extends UnfuddleMigration {
  426. public $unmigratedDestinations = array(
  427. 'status',
  428. 'promote',
  429. 'sticky',
  430. 'revision',
  431. 'language',
  432. );
  433. public $unmigratedSources = array(
  434. 'component-id',
  435. 'description-format',
  436. 'due-on',
  437. 'hours-estimate-current',
  438. 'hours-estimate-initial',
  439. 'milestone-id',
  440. 'resolution',
  441. 'resolution-description',
  442. 'severity-id',
  443. 'version-id',
  444. );
  445. public function __construct() {
  446. parent::__construct(MigrateGroup::getInstance('Unfuddle'));
  447. $item_xpath = '/account/projects/project/tickets/ticket';
  448. $items_class = new MigrateItemsXML($this->items_url, $item_xpath, $this->item_ID_xpath);
  449. $this->source = new MigrateSourceMultiItems($items_class, self::getFieldInfo());
  450. $source_key = array(
  451. 'id' => array(
  452. 'type' => 'int',
  453. 'unsigned' => TRUE,
  454. 'not null' => TRUE,
  455. )
  456. );
  457. $this->dependencies = array('UnfuddlePeople', 'UnfuddleProject');
  458. $this->destination = new MigrateDestinationNodeCase();
  459. $this->map = new MigrateSQLMap('unfuddle_ticket',
  460. $source_key,
  461. MigrateDestinationNode::getKeySchema());
  462. $this->addXPathFieldMapping('assign_to', 'assignee-id')
  463. ->sourceMigration('UnfuddlePeople');
  464. $this->addXPathFieldMapping('created', 'created-at');
  465. $this->addXPathFieldMapping('body', 'description')
  466. ->description(t('Applying site default input format: will often be wrong'));
  467. $this->addXPathFieldMapping('teaser', node_teaser('description', FILTER_FORMAT_DEFAULT))
  468. ->description(t('Applying site default input format: will often be wrong'));
  469. $this->addXPathFieldMapping('case_priority_id', 'priority');
  470. $this->addXPathFieldMapping('pid', 'project-id')
  471. ->sourceMigration('UnfuddleProject');
  472. $this->addXPathFieldMapping('uid', 'reporter-id')
  473. ->sourceMigration('UnfuddlePeople');
  474. $this->addXPathFieldMapping('revision_uid', 'reporter-id')
  475. ->sourceMigration('UnfuddlePeople');
  476. $this->addXPathFieldMapping('case_status_id', 'status');
  477. $this->addXPathFieldMapping('case_number', 'number');
  478. $this->addXPathFieldMapping('title', 'summary');
  479. $this->addXPathFieldMapping('path', 'slug'); // slugged
  480. $this->addXPathFieldMapping('changed', 'updated-at');
  481. $this->addFieldMapping('case_type_id', NULL);
  482. $this->afterConstruct();
  483. }
  484. protected static function getFieldInfo() {
  485. self::$fields = array(
  486. 'assignee-id' => t('User Id'),
  487. 'component-id' => t('The project component (unused)'),
  488. 'created-at' => t('Ticket creation timestamp'),
  489. 'description' => t('Description'),
  490. 'description-format' => t('The ticket body format - does not match Drupal formats'),
  491. 'due-on' => t('Timestamp the ticket should be resolved by'),
  492. /*
  493. 'fieldn-value-id' => t('The value for custom field n'),
  494. */
  495. 'hours-estimate-current' => t('The current estimate for ticket resolution'),
  496. 'hours-estimate-initial' => t('The initial estimate for ticket resolution'),
  497. 'milestone-id' => t('The milestone for which resolution of this ticket is due'),
  498. 'number' => t('An apparent duplicate of the ticket id'),
  499. 'priority' => t('The priority level for the ticket'),
  500. 'project-id' => t('The project this ticket belongs to'),
  501. 'reporter-id' => t('The user reporting the ticket'),
  502. 'resolution' => t('The way the ticket was resolved'),
  503. 'resolution-description' => t('Details about the way the ticket was resolved'),
  504. 'severity-id' => t('The ticket severity level'),
  505. 'status' => t('The ticket status'),
  506. 'summary' => t('The ticket summary'),
  507. 'updated-at' => t('Timestamp of latest ticket change'),
  508. 'version-id' => t('The ticket version'),
  509. 'slug' => t('Virtual: ticket summary, converted to slug'),
  510. );
  511. return self::$fields;
  512. }
  513. public function preparerow(stdClass $row) {
  514. /*
  515. $row->xml->{'archived'} = ((int) $row->xml->{'archived'}) ? 0 : 1;
  516. $row->xml->{'short-name'} = 'project/'. $row->xml->{'short-name'};
  517. */
  518. $row->xml->{'priority'} = 2; // "Normal"
  519. $status = (string) $row->xml->{'status'};
  520. $ct_status_map = array(
  521. 'Open' => 4,
  522. 'Resolved' => 5,
  523. 'Deferred' => 6, // Is not a status in Unfuddle
  524. 'Duplicate' => 7, // Is not a status in Unfuddle
  525. 'Closed' => 8,
  526. );
  527. $u_status_map = array(
  528. 'fixed' => $ct_status_map['Resolved'],
  529. 'closed' => $ct_status_map['Closed'],
  530. 'reopened' => $ct_status_map['Open'],
  531. 'reassigned' => $ct_status_map['Open'],
  532. 'new' => $ct_status_map['Open'],
  533. 'accepted' => $ct_status_map['Open'],
  534. );
  535. $row->xml->{'status'} = isset($u_status_map[$status])
  536. ? $u_status_map[$status]
  537. : $ct_status_map['Open'];
  538. $row->xml->{'slug'} = 'case/'. drupal_strtolower(drupal_clean_css_identifier((string) $row->xml->{'summary'}));
  539. }
  540. }