node_convert.module 41.7 KB
Newer Older
1
2
3
<?php
// $Id$

4
5
6
7
8
9
10
11
/**
 * @file
 * The node_convert module converts nodes from one type to another.
 *
 * The node_convert module converts nodes from one type to another
 * along with all the cck fields it may have.
 */

12
/**
13
 * Implementation of hook_help().
14
 */
15
function node_convert_help($path, $arg) {
16
17
18

  $output = '';

19
  switch ($path) {
20
    case "admin/help#node_convert":
21
22
23
24
25
26
27
28
29
      $output .= '<p>'. t("This module allows to convert one or many nodes between different node types. It can transfer most cck fields, and node-specific options for book and forum types. Support of more basic types will be in future releases. Also the module provides an API for converting nodes and cck fields, hooks for processing additional options of custom node types, integrates with hook_node_operations and Drupal's Action API.") .'</p>';
      $output .= '<p>'. t('<strong>I. Single node conversion:</strong>') .'</p>';
      $output .= t("<ol><li>Go to <a href=\"@permissions\">permissions page</a> and set 'administer conversion' and 'convert to x', 'convert from y' permissions.</li><li>Go to /node/x/convert and follow the provided steps to convert the node.</li></ol>", array('@permissions' => url('admin/user/permissions')));
      $output .= '<p>'. t('<strong>II. Multiple node conversion (using hook_node_operations):</strong>') .'</p>';
      $output .= t('<ol><li>Set appropriate permissions.</li><li>Go to <a href="@node_convert_templates">Node Convert templates</a>.</li><li>Create a new template following the the provided steps.</li><li>Go to the <a href="@content">content page</a>.</li><li>Select the correct nodes.</li><li>Choose "Convert template: x" (based on the template name created above) from the update options.</li><li>Click Update.</li></ol>', array('@node_convert_template' => url('admin/build/node_convert_template'), '@content' => url('admin/content/node')));
      $output .= '<p>'. t('<strong>III. Multiple node conversion (using Actions API + Views Bulk Operations):</strong><br />Note: This requires the contributed modules Views and Views Bulk Operations') .'</p>';
      $output .= t('<ol><li>Set appropriate permissions.</li><li>Go to <a href="@node_convert_templates">Node Convert templates</a>.</li><li>Create a new template following the the provided steps (also check Create Action).</li><li>Create a new <a href="@view">view</a> with the options you require.</li><li>Select Views Bulk Operations as the style.</li><li>Configure all options as necessary</li><li>Select as an operation one of the convert templates.<br />Note: Most probably there will be duplicates of the same template, this is because VBO uses both Actions API and hook_node_operations to show possible operations</li><li>Save the view. View it.</li><li>Select the necessary nodes and click the Convert x button.</li></ol>', array('@node_convert_template' => url('admin/build/node_convert_template'), '@view' => url('admin/build/views')));
      $output .= '<p>'. t('Useful API calls:<br />node_convert_node_convert($nid, $dest_node_type, $source_fields, $dest_fields, $no_fields_flag, $hook_options = NULL);<br />node_convert_field_convert($nid, $source_field, $dest_field);<br />hook_node_convert_change($data, $op);') .'</p>';
      return $output;
30
31
32
  }
}

33
/**
34
 * Implementation of hook_init().
35
36
 */
function node_convert_init() {
37
38
  require_once './'. drupal_get_path('module', 'node_convert') .'/includes/book.node_convert.inc';
  require_once './'. drupal_get_path('module', 'node_convert') .'/includes/forum.node_convert.inc';
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
}

/**
 * Implementation of hook_perm().
 */
function node_convert_perm() {
  $types = node_get_types();
  $permissions = array('administer conversion');
  foreach ($types as $type => $parameters) {
    $permissions[] = 'convert from '. $type;
    $permissions[] = 'convert to '. $type;
  }
  return $permissions;
}

54
55
56
/**
 * Implementation of hook_menu().
 */
57
function node_convert_menu() {
58
59
  $items = array();

60
61
62
63
  $items['node/%node/convert'] = array(
    'title' => 'Convert',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('node_convert_conversion_form', 1),
64
65
    'access callback' => 'node_convert_check_access',
    'access arguments' => array(1),
66
67
68
    'weight' => 6,
    'type' => MENU_LOCAL_TASK,
  );
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

  $items['admin/build/node_convert_templates'] = array(
    'title' => 'Node Convert templates',
    'description' => 'List of templates used for converting nodes using Actions and Node Operations.',
    'page callback' => 'node_convert_templates',
    'access arguments' => array('administer conversion'),
    'type' => MENU_NORMAL_ITEM,
  );

  $items['admin/build/node_convert_templates/list'] = array(
    'title' => 'List',
    'access arguments' => array('administer conversion'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );

  $items['admin/build/node_convert_templates/add'] = array(
    'title' => 'Add template',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('node_convert_add_template'),
    'access arguments' => array('administer conversion'),
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
  );

  $items['admin/build/node_convert_templates/%'] = array(
    'title' => 'Template info',
    'page callback' => 'node_convert_template_info',
    'page arguments' => array(3),
    'access arguments' => array('administer conversion'),
    'type' => MENU_CALLBACK,
  );

  $items['admin/build/node_convert_templates/delete/%'] = array(
    'title' => 'Delete template',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('node_convert_template_delete_confirm', 4),
    'access arguments' => array('administer conversion'),
    'type' => MENU_CALLBACK,
  );

109
  return $items;
110
111
}

112
113
114
115
116
/**
 * Implementation of hook_node_operations().
 */
function node_convert_node_operations() {
  $operations = array();
117
  $result = db_query("SELECT name, nctid FROM {node_convert_templates}");
118
  while ($row = db_fetch_array($result)) {
119
    $access = node_convert_check_template_permission_user(array('template_id' => $row['nctid']));
120
    if ($access) {
121
      $operations['node_convert_'. $row['nctid']] = array(
122
123
        'label' => 'Convert template: '. $row['name'],
        'callback' => 'node_convert_convert_nodes_using_template',
124
        'callback arguments' => array($row['nctid']),
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
      );
    }
  }
  return $operations;
}

/**
 * Implementation of hook_action_info().
 */
function node_convert_action_info() {
  return array(
    'node_convert_convert_action' => array(
      'description' => t("Convert a node"),
      'type' => 'node',
      'configurable' => TRUE,
      'hooks' => array('any' => TRUE),
    )
  );
}

/**
 * Implementation of hook_theme().
 */
function node_convert_theme() {
  return array(
    'node_convert_conversion_form' => array(
      'arguments' => array('form' => NULL),
    ),
    'node_convert_add_template' => array(
      'arguments' => array('form' => NULL),
    )
  );
}

/* ----------- The forms ------------ */

161
function node_convert_conversion_form($form_state, $node) {
162
163
164
  $form = array();

  /* Setting the steps */
165
166
  if (!isset($form_state['values']['step'])) {
    $op = 'choose_destination_type';
167
  }
168
169
  elseif ($form_state['values']['step'] == 'choose_destination_type') {
    $op = 'choose_destination_fields';
170
  }
171
  $form['step'] = array(
172
    '#type' => 'value',
173
174
    '#value' => $op,
  );
175
176
177
  $form['node'] = array(
    '#type' => 'value',
    '#value' => $node,
178
179
  );
  /* Form steps */
180
  if ($op == 'choose_destination_type') {
181
    $form['current_type'] = array('#type' => 'markup', '#value' => $node->type); // Remember current node type, used in theme_ function
182
183
184
185
186
187
188
189
190
    $types = node_convert_return_access_node_types('to');  // Get available content types
    if ($types != FALSE) {
      $key = array_search($form['current_type']['#value'], $types);
      if ($key !== FALSE) unset($types[$key]);  // Delete the current content type from the list
      $options = $types;
      // Populate the select with possible content types
      $form['destination_type'] = array('#type' => 'select', '#options' => $options, '#title' => t("To what content type should this node be converted"));
    }
    else {
191
      // Just used as a message, not sure if it's the best implementation
192
193
      $form['destination_type'] = array('#type' => 'markup', '#value' => t("You don't have access to any node types."));
    }
194
  }
195
  elseif ($op == 'choose_destination_fields') {
196
    $source_fields = content_types($node->type);
197
    $source_fields = $source_fields['fields']; // Get the cck fields of the source type
198
    if (count($source_fields) == 0) {
199
      // In case there are no cck fields, just convert the node type
200
      $form['no_fields'] = array('#type' => 'value', '#value' => 'TRUE');
201
    }
202
    else {  // Otherwise
203
204
205
206
      $dest_fields = content_types($form_state['storage']['destination_type']);
      $dest_fields = $dest_fields['fields'];  // Get the destination type fields
      $i = 0;
      foreach ($source_fields as $source_field) {
207
208
209
        $i++;
        $options = array();
        $options['discard'] = 'discard';
210
        // Populate only the destination type fields into the select that are of the same type (cck type and multiple property)
211
212
        foreach ($dest_fields as $dest_value) if ($source_field['type'] == $dest_value['type'] && $source_field['multiple'] == $dest_value['multiple']) {
          $options[$dest_value['field_name']] = $dest_value['field_name'];
213
        }
214
        $form['source_field_'. $i] = array('#type' => 'value', '#value' => $source_field['field_name']); // Remember the source fields to be converted
215
        // The select populated with possible destination cck fields for each source field
216
217
        // If the destination node type has the same field as the source node type, the default value is set to it.
        $form['dest_field_'. $i] = array('#type' => 'select', '#options' => $options, '#default_value' => $source_field['field_name'], '#title' => $source_field['field_name'] ." ". t("should be inserted into"));
218

219
        // Print the current value of the source field
220
        $temp_value = node_convert_get_field_value($node, $source_field);
221
        $form['current_field_value_'. $i] = array('#type' => 'markup', '#value' => '<div>'. t("Current value is:") ." <b>". $temp_value .'</b></div>');
222
      }
223
      $form['number_of_fields'] = array('#type' => 'value', '#value' => $i);
224
    }
225
226
227
    $hook_options = module_invoke_all('node_convert_change', array('dest_node_type' => $form_state['storage']['destination_type']), 'options');
    if (!empty($hook_options)) {
      $form['hook_options'] = $hook_options;
228
      array_unshift($form['hook_options'], array('#value' => '<br /><strong>'. t("Also the following parameters are available:") .'</strong>'));
229
230
      $form['hook_options']['#tree'] = TRUE;
    }
231
232
  }

233
  if ($op != 'choose_destination_fields' && isset($types) && $types != FALSE) {
234
    $form['submit'] = array('#type' => 'submit', '#value' => t("Next"));
235
  }
236
  elseif ($op == 'choose_destination_fields') {
237
238
    $form['submit'] = array('#type' => 'submit', '#value' => t("Convert"));
  }
239
240
241
242

  return $form;
}

243
244
245
246
247
248
function node_convert_conversion_form_validate($form, &$form_state) {
  if ($form_state['values']['step'] == 'choose_destination_fields') {
    module_invoke_all('node_convert_change', array('dest_node_type' => $form_state['storage']['destination_type'], 'form_state' => $form_state), 'options validate');
  }
}

249
250
251
252
253
254
function node_convert_conversion_form_submit($form, &$form_state) {
  // Remember the destination type
  if ($form_state['values']['step'] == 'choose_destination_type') {
    $form_state['rebuild'] = TRUE;
    $form_state['storage']['destination_type'] = $form_state['values']['destination_type'];
  }
255
  elseif ($form_state['values']['step'] == 'choose_destination_fields') {
256
    // Information needed in the convert process: nid, vid, source type, destination type
257
258
259
    $dest_node_type = $form_state['storage']['destination_type'];
    $node = $form_state['values']['node'];
    $nid = $node->nid;
260
261
    $vid = $node->vid;
    $source_node_type = $node->type;
262
263
    $no_fields = $form_state['values']['no_fields'];
    $number_of_fields = $form_state['values']['number_of_fields'];
264

265
266
267
268
    if ($no_fields == FALSE) {  // If there are cck fields that can to be converted
      for ($i = 1; $i <= $number_of_fields; $i++) {
        $source_fields[] = $form_state['values']['source_field_'. $i]; //  Source fields
        $dest_fields[] = $form_state['values']['dest_field_'. $i];  // Destination fields
269
270
      }
    }
271
272
    if (!empty($form['hook_options'])) {
      $hook_options = $form_state['values']['hook_options'];
273
274
    }
    else {
275
276
      $hook_options = NULL;
    }
277
    $result = node_convert_node_convert($nid, $dest_node_type, $source_fields, $dest_fields, $no_fields, $hook_options);
278
279
    // We display errors if any, or the default succesuful message
    node_convert_messages($result, array('nid' => $nid));
280
281
282
    // We clear the storage so redirect works
    $form_state['storage'] = array();
    $form_state['redirect'] = "node/". $nid;
283
284
285
  }
}

286
function theme_node_convert_conversion_form($form) {
287
288
  $output = '';
  if (isset($form['current_type'])) $output  .= '<div>'. t("The current node type is:") ." <b>". drupal_render($form['current_type']) .'</b></div>';
289
290
  // If there are no fields to convert, we notify the user
  if (isset($form['no_fields']['#value']) && $form['no_fields']['#value'] == TRUE) $output .= '<div>'. t("There are no cck fields to convert. Please press Convert.") .'</div>';
291
292
293
  $output .= drupal_render($form);
  return $output;
}
294

295
function node_convert_add_template($form_state) {
296
  $form = array();
297

298
  /* Setting the steps */
299
  if (!isset($form_state['values']['step'])) {
300
    $op = 'choose_destination_type';
301
  }
302
303
  elseif ($form_state['values']['step'] == 'choose_destination_type') {
    $op = 'choose_destination_fields';
304
305
  }
  $form['step'] = array(
306
    '#type' => 'value',
307
308
    '#value' => $op,
  );
309

310
  if ($op == 'choose_destination_type') {
311
312
313
314
    // Get available content types
    $to_types = node_convert_return_access_node_types('to');
    $from_types = node_convert_return_access_node_types('from');
    if ($to_types != FALSE && $from_types != FALSE) {
315
316
317
318
319
      $form['template_name'] = array(
        '#type' => 'textfield',
        '#title' => t("Template name"),
        '#required' => TRUE,
      );
320
321
322
323
324
325
326
327
328
329
      $form['source_type'] = array(
        '#type' => 'select',
        '#title' => t("Source type"),
        '#options' => $from_types,
      );
      $form['dest_type'] = array(
        '#type' => 'select',
        '#title' => t("Destination type"),
        '#options' => $to_types,
      );
330
331
332
333
334
      $form['create_action'] = array(
        '#type' => 'checkbox',
        '#title' => t("Create action?"),
        '#description' => t("If the option is checked, an action named Convert *Template name* will be created."),
      );
335
336
337
338
    }
    else {
      $form['no_types'] = array('#type' => 'markup', '#value' => t("You don't have access to any node types."));
    }
339
  }
340
341
  elseif ($op == 'choose_destination_fields') {
    $source_fields = content_types($form_state['storage']['source_type']);
342
343
344
    $source_fields = $source_fields['fields']; // Get the cck fields of the source type
    if (count($source_fields) == 0) {
      // In case there are no cck fields, just convert the node type
345
      $form['no_fields'] = array('#type' => 'value', '#value' => 'TRUE');
346
    }
347
348
    else {
      $dest_fields = content_types($form_state['storage']['dest_type']);
349
      $dest_fields = $dest_fields['fields'];  // Get the destination type fields
350
351
352
353
354
355
356
357
358
359
360
361
      $i = 0;
      foreach ($source_fields as $source_field) {
        $i++;
        $options = array();
        $options['discard'] = 'discard';
        // Populate only the destination type fields into the select that are of the same type (cck type and multiple property)
        foreach ($dest_fields as $dest_field) if ($source_field['type'] == $dest_field['type'] && $source_field['multiple'] == $dest_field['multiple']) {
          $options[$dest_field['field_name']] = $dest_field['field_name'];
        }
        $form['source_field_'. $i] = array('#type' => 'value', '#value' => $source_field['field_name']); // Remember the source fields to be converted
        // The select populated with possible destination cck fields for each source field
        $form['dest_field_'. $i] = array('#type' => 'select', '#options' => $options, '#title' => $source_field['field_name'] ." ". t("should be inserted into"));
362
      }
363
      $form['number_of_fields'] = array('#type' => 'value', '#value' => $i);
364
    }
365
366
    // All node specific form options needed for types like book, forum, etc. are done here
    $hook_options = module_invoke_all('node_convert_change', array('dest_node_type' => $form_state['storage']['dest_type']), 'options');
367
368
369
370
371
    if (!empty($hook_options)) {
      $form['hook_options'] = $hook_options;
      array_unshift($form['hook_options'], array('#value' => '<strong>'. t("Also the following parameters are available:") .'</strong>'));
      $form['hook_options']['#tree'] = TRUE;
    }
372
  }
373
374

  if ($op == 'choose_destination_type' && $to_types != FALSE && $from_types != FALSE) {
375
376
    $form['submit'] = array('#type' => 'submit', '#value' => t("Next"));
  }
377
378
  elseif ($op == "choose_destination_fields") {
    $form['submit'] = array('#type' => 'submit', '#value' => t("Create"));
379
  }
380

381
382
383
  return $form;
}

384
385
function node_convert_add_template_validate($form, &$form_state) {
  if ($form_state['values']['step'] == 'choose_destination_type') {
386
    if ($form_state['values']['source_type'] == $form_state['values']['dest_type']) {
387
388
389
390
      form_set_error('source_type', t('Please select different node types.'));
      form_set_error('dest_type', t('Please select different node types.'));
    }
  }
391
392
  // All node specific form validations needed for types like book, forum, etc. are done here
  elseif ($form_state['values']['step'] == 'choose_destination_fields') {
393
    module_invoke_all('node_convert_change', array('dest_node_type' => $form_state['values']['info']['dest_type'], 'form_state' => $form_state), 'options validate');
394
395
396
  }
}

397
398
function node_convert_add_template_submit($form, &$form_state) {
  if ($form_state['values']['step'] == 'choose_destination_type') {
399
    $form_state['rebuild'] = TRUE;
400
401
402
403
404
405
406
407
408
409
    $form_state['storage']['template_name'] = $form_state['values']['template_name'];
    $form_state['storage']['source_type'] = $form_state['values']['source_type'];
    $form_state['storage']['dest_type'] = $form_state['values']['dest_type'];
    $form_state['storage']['create_action'] = $form_state['values']['create_action'];
  }
  elseif ($form_state['values']['step'] == 'choose_destination_fields') {
    if ($form_state['values']['no_fields'] == FALSE) {  // If there are cck fields that can to be converted
      for ($i = 1; $i <= $form_state['values']['number_of_fields']; $i++) {
        $source_fields[] = $form_state['values']['source_field_'. $i]; //  Source fields
        $dest_fields[] = $form_state['values']['dest_field_'. $i];  // Destination fields
410
411
      }
    }
412

413
414
    if (!empty($form['hook_options'])) {
      $hook_options = $form_state['values']['hook_options'];
415
416
    }
    else {
417
418
      $hook_options = NULL;
    }
419

420
421
422
423
424
425
426
    $fields = array('source' => $source_fields, 'destination' => $dest_fields);
    $data = array('fields' => $fields, 'hook_options' => $hook_options, 'no_fields' => $form_state['values']['no_fields']);
    $data = serialize($data);
    db_query("INSERT INTO {node_convert_templates} (name, source_type, destination_type, data) VALUES ('%s', '%s', '%s', '%s')", $form_state['storage']['template_name'], $form_state['storage']['source_type'], $form_state['storage']['dest_type'], $data);
    drupal_set_message("Template created succesufuly.");

    if ($form_state['storage']['create_action'] == 1) {
427
      $template_id = db_last_insert_id('node_convert_templates', 'nctid');
428
429
430
431
432
      actions_save('node_convert_convert_action', 'node', array('template' => $template_id), 'Convert '. $form_state['storage']['template_name'], NULL);
    }
    // We clear the storage so redirect works
    $form_state['storage'] = array();
    $form_state['redirect'] = 'admin/build/node_convert_templates';
433
434
435
  }
}

436

437
438
function theme_node_convert_add_template($form) {
  if ($form['step']['#value'] == "choose_destination_type") {
439
440
441
    $output  = '<div>'. t("Choose the source type of the nodes that should be shown, and the destination type to which they will be converted.") .'</div>';
    $output .= drupal_render($form);
  }
442
443
  elseif ($form['step']['#value'] == "choose_destination_fields") {
    if ($form['no_fields']['#value'] == TRUE) $output .= '<div>'. t("There are no cck fields to convert. Please press Create.") .'</div>';
444
445
    $output .= drupal_render($form);
  }
446
447
448
449
450
451
452
453
454
  return $output;
}

/* ------------- Template adding, viewing, deleting. ----------------- */

function node_convert_templates() {
  $output = '';
  $rows = array();
  $headers = array(t("Name"), t("Source type"), t("Dest type"), t("Delete"));
455
  $result = db_query("SELECT * FROM {node_convert_templates} ORDER BY nctid");
456
  while ($row = db_fetch_object($result)) {
457
    $rows[$row->nctid] = array(l($row->name, 'admin/build/node_convert_templates/'. $row->nctid), $row->source_type, $row->destination_type, l(t("Delete"), 'admin/build/node_convert_templates/delete/'. $row->nctid));
458
  }
459
460
461
462
463
464
465
466
467
468
  $output = theme('table', $headers, $rows);

  return $output;
}

function node_convert_template_info($template_id) {
  $output = '';
  $rows = array();
  $headers = array(t("Property"), t("Value"));
  $row = node_convert_load_template($template_id);
469
  $rows[] = array(t("Template id"), $row['nctid']);
470
471
472
473
474
475
476
477
478
479
480
481
482
  $rows[] = array(t("Name"), $row['name']);
  $rows[] = array(t("Source type"), $row['source_type']);
  $rows[] = array(t("Destination type"), $row['destination_type']);
  $data = $row['data'];
  if ($data['no_fields'] == FALSE) {
    $source_fields_string = implode(', ', $data['fields']['source']);
    $dest_fields_string = implode(', ', $data['fields']['destination']);
    $rows[] = array(t("Source fields"), $source_fields_string);
    $rows[] = array(t("Destination fields"), $dest_fields_string);
  }
  if (!empty($data['hook_options'])) $rows[] = array(t("Hook options"), print_r($data['hook_options'], TRUE));
  $output .= theme('table', $headers, $rows);

483
484
  return $output;
}
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509

function node_convert_template_delete_confirm(&$form_state, $template_id) {
  $form['template_id'] = array(
    '#type' => 'value',
    '#value' => $template_id,
  );

  $form['delete_action'] = array(
    '#type' => 'checkbox',
    '#title' => t("Delete action?"),
    '#default_value' => 1,
    '#description' => t("If the option is checked, all actions that contain this template will be erased. Otheriwise, the actions' template will be set to none."),
  );

  return confirm_form($form,
    t('Are you sure you want to delete this template?'),
    isset($_GET['destination']) ? $_GET['destination'] : 'admin/build/node_convert_templates',
    t('This action cannot be undone.'),
    t('Delete'),
    t('Cancel')
  );
}

function node_convert_template_delete_confirm_submit($form, &$form_state) {
  if ($form_state['values']['confirm']) {
510
    db_query("DELETE FROM {node_convert_templates} WHERE nctid = %d", $form_state['values']['template_id']);
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
    if ($form_state['values']['delete_action'] == 1) {
      db_query("DELETE FROM {actions} WHERE callback = 'node_convert_convert_action' AND parameters LIKE '%%template\";s:%%:\"%d%%'", $form_state['values']['template_id']);
    }
    else {
      $none = serialize(array('template' => '0'));
      db_query("UPDATE {actions} SET parameters = '%s' WHERE callback = 'node_convert_convert_action' AND parameters LIKE '%%template\";s:%%:\"%d%%'", $none, $form_state['values']['template_id']);
    }
  }
  $form_state['redirect'] = 'admin/build/node_convert_templates';
}


/* The node_convert action */
function node_convert_convert_action(&$node, &$context = array()) {
  if ($context['template'] === "0") {
    drupal_set_message(t("Dummy template. No action is being performed."), 'warning', FALSE);
    return FALSE;
  }
  $template = node_convert_load_template($context['template']);

  $access = node_convert_check_template_permission_user(array('template' => $template));
  if ($access == FALSE) {
    drupal_set_message(t("You don't have permission to use this conversion template"), 'warning', FALSE);
    return;
  }

  if ($node->type != $template['source_type']) {
    drupal_set_message(t("Node %nid doesn't match the template source type. Discarded. ", array('nid' => $node->nid)), 'warning');
  }
  else {
541
    $result = node_convert_node_convert($node->nid, $template['destination_type'], $template['data']['fields']['source'], $template['data']['fields']['destination'], $template['data']['no_fields'], $template['data']['hook_options']);
542
543
544
545
546
547
548
549
550
551
552
    // We display errors if any, or the default succesuful message
    node_convert_messages($result, array('nid' => $nid));
    // This is node_load is necessary. It loads the new data from the DB, which gets passed down the action chain by reference, where it is saved.
    $node = node_load($node->nid, NULL, TRUE);
  }
}

function node_convert_convert_action_form($context) {
  $result = db_query("SELECT * FROM {node_convert_templates}");
  $templates = array(0 => 'none');
  while ($row = db_fetch_array($result)) {
553
    $access = node_convert_check_template_permission_user(array('template_id' => $row['nctid']));
554
    if ($access == TRUE)
555
      $templates[$row['nctid']] = $row['name'];
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
  }
  if (isset($context['template'])) {
    $default_template = $context['template'];
  }
  $form['template'] = array(
    '#type' => 'select',
    '#title' => t("Template"),
    '#description' => t("Convesion template to use when the action is fired."),
    '#options' => $templates,
    '#default_value' => $default_template,
  );

  return $form;
}

function node_convert_convert_action_submit($form, &$form_state) {
  return array('template' => $form_state['values']['template']);
}

/* ----------------------- API --------------------- */

577
578
579
580
581
582
583
584
585
586
587
588
589
/**
 * Converts a node to another content type.
 *
 * @param $nid
 *   The nid of the node to be converted.
 * @param $dest_node_type
 *   A string containing the destination node type of the node.
 * @param $source_fields
 *   An array containing the source field names.
 * @param $dest_fields
 *   An array containing the destination field names.
 * @param $no_fields_flag
 *   A boolean containing if there are source fields that have to be converted.
590
591
592
593
 * @param $hook_options
 *   An array containing values used by the hook_node_convert_change functions.
 * @return
 *   Returns the $nid.
594
 */
595
function node_convert_node_convert($nid, $dest_node_type, $source_fields, $dest_fields, $no_fields_flag, $hook_options = NULL) {
596
  $node = node_load($nid);
597
598
599
  if ($node == FALSE) {
    return FALSE;
  }
600
601
  $vid = $node->vid;
  $source_node_type = $node->type;
602
  $tables_info = content_types();
603
  db_query("UPDATE {node} SET type = '%s' WHERE nid = %d", $dest_node_type, $nid); // Change the node type in the DB
604
605
606
  if (count($tables_info[$dest_node_type]['tables']) != 0)
    db_query("INSERT INTO {%s} (nid, vid) VALUES (%d, %d)", "content_type_". $dest_node_type, $nid, $vid); // Add the current node to the chosen content type
  if ($no_fields_flag == FALSE) {  // If there are cck fields that can be converted
607
608
609
610
    foreach ($source_fields as $key => $field) {  // Conversion process for each field
      node_convert_field_convert($nid, $field, $dest_fields[$key]);
    }
  }
611
612
613
614
615
616
617
618
619
  // We collate date to send to the hook implementations
  $data = array(
    'node' => $node,
    'dest_node_type' => $dest_node_type,
  );
  if (!empty($hook_options)) $data['hook_options'] = $hook_options;
  // We make sure that all custom node modules do their changes at the appropriate steps
  module_invoke_all('node_convert_change', $data, 'insert');
  module_invoke_all('node_convert_change', $data, 'delete');
620

621
622
  if (count($tables_info[$source_node_type]['tables']) != 0)
    db_query("DELETE FROM {%s} WHERE nid = %d", "content_type_". $source_node_type, $nid); // We delete the source node_type info
623
  db_query("DELETE FROM {cache_content} WHERE cid = '%s'", "content:". $nid .":". $vid); // We clear the cache
624
625
  cache_clear_all('node:'. $nid, 'cache_menu', 'TRUE');
  cache_clear_all('node/'. $nid, 'cache_menu', 'TRUE');
626
  return $nid;
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
}

/**
 * Transfers information from source_field to dest_field.
 *
 * @param $nid
 *   The nid of the node to be converted.
 * @param $source_field
 *   A string containing the name of the source field which contains to be transfered information.
 * @param $dest_field
 *   A string containing the name of the destination field where the information should be strored.
 */
function node_convert_field_convert($nid, $source_field, $dest_field) {
  $node = node_load($nid);
  $vid = $node->vid;
  $field_info_source = content_fields($source_field); // Get source field information
  $db_info_source = content_database_info($field_info_source); // Get DB specific source field information
  // If the source field has a separate table, we will have to delete the node info in it
645
  if (strpos($db_info_source['table'], "content_field_") !== FALSE) $db_source_content_field = TRUE; else $db_source_content_field = FALSE;
646
  if ($dest_field == "discard") {  // If the source field value should be discarded
647
    // Delete node info in the separate field table
648
    if ($db_source_content_field == TRUE) db_query("DELETE FROM {%s} WHERE nid = %d", $db_info_source['table'], $nid);
649
    return;
650
651
652
  }
  $field_info_dest = content_fields($dest_field); // Get destination field information
  $db_info_dest = content_database_info($field_info_dest); // Get DB specific destination field information
653
  if (strpos($db_info_dest['table'], "content_field_") !== FALSE) $db_dest_content_field = TRUE; else $db_dest_content_field = FALSE;
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
  $data = array();
  $dest_column_names = array();
  $source_column_names = array();
  $column_placeholders = array();
  $column_assignments = array();
  $source_values = array();
  foreach ($db_info_source['columns'] as $column => $attributes) {
    $source_column_names[] = $attributes['column']; // Collect the DB columns names of the source field
  }
  if ($field_info_source['multiple'] != 0) {  // If the field has multiple values, we remember each value from the DB
    $query = db_query("SELECT ". implode(", ", $source_column_names) .", delta FROM {%s} WHERE nid = %d AND vid = %d", $db_info_source['table'], $nid, $vid);
    while ($db_row = db_fetch_array($query)) {
      $source_values[] = array_merge(array_values($db_row), array($nid, $vid));
    }
  }
  else { // Otherwise we just remember one row of info
    $source_values = db_fetch_array(db_query("SELECT ". implode(", ", $source_column_names) ." FROM {%s} WHERE nid = %d AND vid = %d", $db_info_source['table'], $nid, $vid));
    $source_values = array_values($source_values);
    $source_values[] = $nid;
    $source_values[] = $vid;
  }
  // After getting the source field values, we delete them in the DB
676
  if ($db_source_content_field == TRUE) db_query("DELETE FROM {%s} WHERE nid = %d", $db_info_source['table'], $nid);
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
  // Prepare the INSERT and UDPATE queries, for transfering the values in the destination fields
  foreach ($db_info_dest['columns'] as $column => $attributes) {
    $dest_column_names[] = $attributes['column'];
    switch ($attributes['type']) {
    case 'int':
    case 'mediumint':
    case 'tinyint':
    case 'bigint':
      $column_placeholders[] = '%d';
      $column_assignments[] = $attributes['column'] .' = %d';
      break;
    case 'float':
      $column_placeholders[] = '%f';
      $column_assignments[] = $attributes['column'] .' = %f';
      break;
    default:
      $column_placeholders[] = "'%s'";
      $column_assignments[] = $attributes['column'] ." = '%s'";
    }
  }
  if ($field_info_source['multiple'] != 0) { // If the field has multiple values, we put each value into the DB
    foreach ($source_values as $values) {
      $db_message = db_query("INSERT INTO {". $db_info_dest['table'] ."} (". implode(", ", $dest_column_names) .", delta, nid, vid) VALUES (". implode(', ', $column_placeholders) .", %d, %d, %d)", $values);
700
    }
701
702
  }
  else {
703
    if ($db_dest_content_field == TRUE) { // If the field is re-used, we insert the data in the field's own table in the DB
704
705
706
707
708
709
      $db_message = db_query("INSERT INTO {". $db_info_dest['table'] ."} (". implode(", ", $dest_column_names) .", nid, vid) VALUES (". implode(', ', $column_placeholders) .", %d, %d)", $source_values);
    }
    else { // Otherwise we just update it in the content_type table
      $db_message = db_query("UPDATE {". $db_info_dest['table'] ."} SET " . implode(", ", $column_assignments) ." WHERE nid = %d AND vid = %d", $source_values);
    }
  }
710
}
711

712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
/**
 * Displays error messages if any occured, otherwise the success message.
 *
 * @param $result
 *   The result value of the node conversion. Possible values
 *   - FALSE  Displays an error message.
 *   - Any other  Displays success message.
 * @param $params
 *   An array containing message parameters. Possible values
 *   - display_success  If TRUE, the success message will be displayed, otherwise no message is displayed.
 *   Default is TRUE.
 */
function node_convert_messages($result, $params = array()) {
  $params += array('display_success' => TRUE);
  if ($result == FALSE) {
    drupal_set_message(t("Conversion failed. Node nid @nid doesn't exist.", array('@nid' => $params['nid'])), 'error');
728
729
  }
  elseif ($params['display_success'] == TRUE) {
730
    drupal_set_message(t("Node @nid has been converted succesufuly.", array('@nid' => $params['nid'])));
731
732
733
  }
}

734
/**
735
 * This is an example implementation for the hook. Preforms actions when converting a node based on it's type.
736
737
738
739
740
 *
 * @param $data
 *   An array containing information about the conversion process. The keys are
 *   - dest_node_type  The destination type of the node
 *   - node  The node object
741
 *   - Any other information passed by $op = 'options' or $op = 'options validate'
742
743
744
745
746
747
748
 * @param $op
 *   A string containg the operation which should be executed. These are the possible values
 *   - insert  Operations which should be run when the node is transferred to the new node type.
 *   Usually for transferring and adding new node information into the database.
 *   - delete  Operations which should be run after the node is transferred to the new node type.
 *   Usually for deleting unneeded information from the database after the transfer.
 *   - options  Configuration elements shown on the conversion form. Should return a FAPI array.
749
 *   - options validate  Validation check on the options elements.
750
751
752
 * @return
 *    Should return a FAPI array only when using the options operation.
 */
753
754
function hook_node_convert_change($data, $op) {
  // All of this is just an example, there real data is being called from hook_init
755
756
757
758
759
760
761
762
763
764
765
766
767
  if ($op == 'insert') {
    if ($data['dest_node_type'] == 'book') {
      $book = array();
      $node = $data['node'];
      $book['link_path'] = 'node/'. $node->nid;
      $book['link_title'] = $node->title;
      $book['plid'] = 0;
      $book['menu_name'] = book_menu_name($node->nid);
      $mlid = menu_link_save($book);
      $book['bid'] = $data['hook_options']['bid'];
      if ($book['bid'] == 'self') $book['bid'] = $node->nid;
      db_query("INSERT INTO {book} (nid, mlid, bid) VALUES (%d, %d, %d)", $node->nid, $book['mlid'], $book['bid']);
    }
768
769
770
771
    if ($data['dest_node_type'] == 'forum') {
      db_query('INSERT INTO {forum} (tid, vid, nid) VALUES (%d, %d, %d)', $data['hook_options']['forum'], $data['node']->vid, $data['node']->nid);
      db_query('INSERT INTO {term_node} (tid, vid, nid) VALUES (%d, %d, %d)', $data['hook_options']['forum'], $data['node']->vid, $data['node']->nid);
    }
772
773
  }
  elseif ($op == 'delete') {
774
775
776
777
    if ($data['node']->type == 'book') {
      menu_link_delete($data['node']->book['mlid']);
      db_query('DELETE FROM {book} WHERE mlid = %d', $data['node']->book['mlid']);
    }
778
779
780
781
    if ($data['node']->type == 'forum') {
      db_query('DELETE FROM {forum} WHERE nid = %d', $data['node']->nid);
      db_query('DELETE FROM {term_node} WHERE nid = %d', $data['node']->nid);
    }
782
783
  }
  elseif ($op == 'options') {
784
785
786
787
788
789
790
791
792
793
794
795
796
    $form = array();
    if ($data['dest_node_type'] == 'book') {
      foreach (book_get_books() as $book) {
        $options[$book['nid']] = $book['title'];
      }
      $options = array('self' => '<'. t('create a new book') .'>') + $options;
      $form['bid'] = array(
        '#type' => 'select',
        '#title' => t('Book'),
        '#options' => $options,
        '#description' => t('Your page will be a part of the selected book.'),
        '#attributes' => array('class' => 'book-title-select'),
      );
797
798
799
800
801
802
803
804
    }
    if ($data['dest_node_type'] == 'forum') {
      $vid = variable_get('forum_nav_vocabulary', '');
      $form['forum'] = taxonomy_form($vid);
      $form['forum']['#weight'] = 7;
      $form['forum']['#required'] = TRUE;
      $form['forum']['#options'][''] = t('- Please choose -');
    }
805
    return $form;
806
807
  }
  elseif ($op == 'options validate') {
808
809
810
811
812
813
814
815
816
    $form_state = $data['form_state'];
    if ($data['dest_node_type'] == 'forum') {
      $containers = variable_get('forum_containers', array());
      $term = $form_state['values']['hook_options']['forum'];
      if (in_array($term, $containers)) {
        $term = taxonomy_get_term($term);
        form_set_error('hook_options][forum', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
      }
    }
817
  }
818
819
}

820
821
822
823
824
825
826
827
828
829
830
831
/**
 * Checks if user can do conversions from this node's type.
 *
 * @param $node
 *   A node object to be checked.
 * @return
 *   TRUE if user can make conversions using this type, FALSE otherwise.
 */
function node_convert_check_access($node) {
  $access = user_access('convert from '. $node->type) && user_access('administer conversion');
  return $access;
}
832

833
834
835
836
837
838
839
840
841
842
843
844
845
/**
 * Returns a list of node types that the user has access to, depending on the direction of conversion.
 *
 * @param $direction
 *   A string containing either 'to' or 'from'.
 * @return
 *   An array of node types or FALSE if the user doesn't have access to any node type.
 */
function node_convert_return_access_node_types($direction) {
  global $user;
  $list = array();
  $types = node_get_types();
  foreach ($types as $type => $parameters) {
846
    if (user_access('convert '. $direction .' '. $type)) $list[$type] = $parameters->name;
847
848
849
850
851
  }
  if (!empty($list))
    return $list;
  else
    return FALSE;
852
}
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
/**
 * Returns a string contaning the value of the $field from the $node object.
 *
 * @param $node
 *   A $node object
 * @param $field
 *   The field who's value to get.
 * @return
 *   A string contaning the value of the $field from the $node object.
 */
function node_convert_get_field_value($node, $field) {
  $value = '';
  if ($field['type'] == "image") {
    $value = $node->{$field['field_name']}[0]['title'] ." ; ". $node->{$field['field_name']}[0]['filepath'];
  }
  elseif ($field['type'] == "link") {
    $value = $node->{$field['field_name']}[0]['url'] ." ; ". $node->{$field['field_name']}[0]['title'];
  }
  elseif ($field['type'] == "email") {
    $value = $node->{$field['field_name']}[0]['email'];
  }
  elseif ($field['type'] == "file_audio" || $field['type'] == "file_video") {
    $value = $node->{$field['field_name']}[0]['filename'] ." ". $node->{$field['field_name']}[0]['filemime'] ." ". $node->{$field['field_name']}[0]['filesize'] ." ". t("bytes");
  }
  elseif ($field['type'] == "nodereference") {
    $value = "nid: ". $node->{$field['field_name']}[0]['nid'];
  }
  elseif ($field['type'] == "userreference") {
    $value = "uid: ". $node->{$field['field_name']}[0]['uid'];
  }
  else $value = $node->{$field['field_name']}[0]['value'];
  if (empty($value)) $value = 'NULL';
  return $value;
}
887

888
889
890
891
892
893
894
895
896
/**
 * Loads a conversion template array using template_id.
 *
 * @param $template_id
 *   The template id to use
 * @return
 *    An array containing the template information or FALSE if there is no such template
 */
function node_convert_load_template($template_id) {
897
  $template = db_fetch_array(db_query("SELECT * FROM {node_convert_templates} WHERE nctid = %d", $template_id));
898
899
  if (is_array($template)) {
    $template['data'] = unserialize($template['data']);
900
  }
901
  return $template;
902
903
}

904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
/**
 * Checks if the logged in user has access to use the conversion template.
 *
 * @param $data
 *   An array containing either of the following keys
 *   - template_id The template id used for conversion
 *   - template  The template array containing data
 * @return
 *    TRUE if user can use the template, FALSE otherwise.
 */
function node_convert_check_template_permission_user($data) {
  if (!empty($data['template'])) {
    $template = $data['template'];
  }
  elseif (!empty($data['template_id'])) {
919
    $template = node_convert_load_template($data['template_id']);
920
921
922
923
924
925
  }
  else
    return FALSE;

  $access = user_access('convert from '. $template['source_type']) && user_access('convert to '. $template['destination_type']);
  return $access;
926
927
}

928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
/**
 * Converts a list of nodes using a given template
 *
 * @param $nodes
 *   An array containing a list of node nid's
 * @param $template_id
 *   The template to use for conversion
 * @return
 *    FALSE if the user doesn't have permission to use the template.
 */
function node_convert_convert_nodes_using_template($nodes, $template_id) {
  $template = node_convert_load_template($template_id);

  $access = node_convert_check_template_permission_user(array('template' => $template));
  if ($access == FALSE) {
943
    drupal_set_message(t("You don't have permission to use this conversion template."), 'warning', FALSE);
944
945
946
947
948
949
950
    return FALSE;
  }

  foreach ($nodes as $nid) {
    $node = node_load($nid);
    // The source type of the given node doesn't match the template one, so we discard it with a message
    if ($node->type != $template['source_type']) {
951
      drupal_set_message(t("Node %nid doesn't match the template source type. Discarded.", array('nid' => $node->nid)), 'warning');
952
953
    }
    else {
954
955
      $result = node_convert_node_convert($node->nid, $template['destination_type'], $template['data']['fields']['source'], $template['data']['fields']['destination'], $template['data']['no_fields'], $template['data']['hook_options']);
      // We display errors if there are any, or the default succesuful message
956
957
958
959
      node_convert_messages($result, array('nid' => $nid));
    }
  }
}