content_types.inc 16 KB
Newer Older
Neil Drumm's avatar
Neil Drumm committed
1
2
3
4
5
6
7
8
9
10
11
12
<?php
// $Id$

/**
 * @file
 * Content type editing UI.
 */

/**
 * Displays the content type admin overview page.
 */
function node_overview_types() {
13
14
  $types = node_type_get_types();
  $names = node_type_get_names();
15
  $header = array(t('Name'), array('data' => t('Operations'), 'colspan' => '2'));
Neil Drumm's avatar
Neil Drumm committed
16
17
18
  $rows = array();

  foreach ($names as $key => $name) {
19
20
    $type = $types[$key]; 
    if (node_hook($type->type, 'form')) {
Neil Drumm's avatar
Neil Drumm committed
21
      $type_url_str = str_replace('_', '-', $type->type);
22
      $row = array(theme('node_admin_overview', $name, $type));
Neil Drumm's avatar
Neil Drumm committed
23
      // Set the edit column.
Dries Buytaert's avatar
Dries Buytaert committed
24
      $row[] = array('data' => l(t('edit'), 'admin/build/node-type/' . $type_url_str));
Neil Drumm's avatar
Neil Drumm committed
25
26
27

      // Set the delete column.
      if ($type->custom) {
Dries Buytaert's avatar
Dries Buytaert committed
28
        $row[] = array('data' => l(t('delete'), 'admin/build/node-type/' . $type_url_str . '/delete'));
Neil Drumm's avatar
Neil Drumm committed
29
30
      }
      else {
31
        $row[] = array('data' => '');
Neil Drumm's avatar
Neil Drumm committed
32
33
34
35
36
37
      }
      $rows[] = $row;
    }
  }

  if (empty($rows)) {
38
    $rows[] = array(array('data' => t('No content types available. <a href="@link">Add content type</a>.', array('@link' => url('admin/build/types/add'))), 'colspan' => '5', 'class' => 'message'));
Neil Drumm's avatar
Neil Drumm committed
39
40
41
42
43
  }

  return theme('table', $header, $rows);
}

44
45
46
47
48
49
50
function theme_node_admin_overview($name, $type) {
  $output = check_plain($name);
  $output .= ' <small> (Machine name: ' . check_plain($type->type) . ')</small>';
  $output .= '<div class="description">' . filter_xss_admin($type->description) . '</div>';
  return $output;
}

Neil Drumm's avatar
Neil Drumm committed
51
52
53
/**
 * Generates the node type editing form.
 */
54
function node_type_form(&$form_state, $type = NULL) {
55
  drupal_add_js(drupal_get_path('module', 'node') . '/content_types.js');
Neil Drumm's avatar
Neil Drumm committed
56
  if (!isset($type->type)) {
57
    // This is a new type. Node module managed types are custom and unlocked.
58
    $type = node_type_set_defaults(array('custom' => 1, 'locked' => 0));
Neil Drumm's avatar
Neil Drumm committed
59
60
  }

61
62
  // Make the type object available to implementations of hook_form_alter.
  $form['#node_type'] = $type;
63

Neil Drumm's avatar
Neil Drumm committed
64
65
66
67
68
69
70
71
  $form['identity'] = array(
    '#type' => 'fieldset',
    '#title' => t('Identification'),
  );
  $form['identity']['name'] = array(
    '#title' => t('Name'),
    '#type' => 'textfield',
    '#default_value' => $type->name,
72
    '#description' => t('The human-readable name of this content type. This text will be displayed as part of the list on the <em>add new content</em> page. It is recommended that this name begin with a capital letter and contain only letters, numbers, and <strong>spaces</strong>. This name must be unique.'),
Neil Drumm's avatar
Neil Drumm committed
73
    '#required' => TRUE,
74
    '#size' => 30,
75
    '#field_suffix' => ' <small id="node-type-name-suffix">&nbsp;</small>',
Neil Drumm's avatar
Neil Drumm committed
76
77
78
79
  );

  if (!$type->locked) {
    $form['identity']['type'] = array(
80
      '#title' => t('Machine name'),
Neil Drumm's avatar
Neil Drumm committed
81
82
83
84
      '#type' => 'textfield',
      '#default_value' => $type->type,
      '#maxlength' => 32,
      '#required' => TRUE,
85
      '#description' => t('The machine-readable name of this content type. This text will be used for constructing the URL of the <em>add new content</em> page for this content type. This name must contain only lowercase letters, numbers, and underscores. Underscores will be converted into hyphens when constructing the URL of the <em>add new content</em> page. This name must be unique.'),
Neil Drumm's avatar
Neil Drumm committed
86
87
88
89
90
91
92
93
    );
  }
  else {
    $form['identity']['type'] = array(
      '#type' => 'value',
      '#value' => $type->type,
    );
    $form['identity']['type_display'] = array(
94
      '#title' => t('Machine name'),
Neil Drumm's avatar
Neil Drumm committed
95
      '#type' => 'item',
96
      '#markup' => theme('placeholder', $type->type),
Neil Drumm's avatar
Neil Drumm committed
97
98
99
100
      '#description' => t('The machine-readable name of this content type. This field cannot be modified for system-defined content types.'),
    );
  }

101
102
103
104
  $form['identity']['description'] = array(
    '#title' => t('Description'),
    '#type' => 'textarea',
    '#default_value' => $type->description,
105
    '#description' => t('A brief description of this content type. This text will be displayed as part of the list on the <em>add new content</em> page.'),
106
107
    );

Neil Drumm's avatar
Neil Drumm committed
108
109
  $form['submission'] = array(
    '#type' => 'fieldset',
110
    '#title' => t('Submission form settings'),
Neil Drumm's avatar
Neil Drumm committed
111
112
    '#collapsible' => TRUE,
  );
113
114
115
116
117
118
119
120
121
122
123
124
  $form['submission']['title_label'] = array(
    '#title' => t('Title field label'),
    '#type' => 'textfield',
    '#default_value' => $type->title_label,
    '#required' => TRUE,
  );
  if (!$type->has_title) {
    // Avoid overwriting a content type that intentionally does not have a
    // title field.
    $form['submission']['title_label']['#attributes'] = array('disabled' => 'disabled');
    $form['submission']['title_label']['#description'] = t('This content type does not have a title field.');
    $form['submission']['title_label']['#required'] = FALSE;
Neil Drumm's avatar
Neil Drumm committed
125
  }
126
127
128
  $form['submission']['body_label'] = array(
    '#title' => t('Body field label'),
    '#type' => 'textfield',
129
    '#default_value' => isset($type->body_label) ? $type->body_label : '',
130
131
    '#description' => t('To omit the body field for this content type, remove any text and leave this field blank.'),
  );
Neil Drumm's avatar
Neil Drumm committed
132
133
134
135
  $form['submission']['min_word_count'] = array(
    '#type' => 'select',
    '#title' => t('Minimum number of words'),
    '#default_value' => $type->min_word_count,
136
    '#options' => drupal_map_assoc(array(0, 1, 10, 25, 50, 75, 100, 125, 150, 175, 200)),
Neil Drumm's avatar
Neil Drumm committed
137
138
    '#description' => t('The minimum number of words for the body field to be considered valid for this content type. This can be useful to rule out submissions that do not meet the site\'s standards, such as short test posts.')
  );
139
140
141
142
143
144
145
146
147
148
149
  $form['submission']['node_preview'] = array(
    '#type' => 'radios',
    '#title' => t('Preview post'),
    '#default_value' => variable_get('node_preview_' . $type->type, DRUPAL_OPTIONAL),
    '#options' => array(
      DRUPAL_DISABLED => t('Disabled'),
      DRUPAL_OPTIONAL => t('Optional'),
      DRUPAL_REQUIRED => t('Required'),
    ),
    '#description' => t('Should users preview posts before submitting?'),
    );
150
151
152
  $form['submission']['help']  = array(
    '#type' => 'textarea',
    '#title' => t('Explanation or submission guidelines'),
153
    '#default_value' => $type->help,
154
155
    '#description' => t('This text will be displayed at the top of the submission form for this content type. It is useful for helping or instructing your users.')
  );
Neil Drumm's avatar
Neil Drumm committed
156
157
  $form['workflow'] = array(
    '#type' => 'fieldset',
158
    '#title' => t('Publishing options'),
Neil Drumm's avatar
Neil Drumm committed
159
    '#collapsible' => TRUE,
160
    '#collapsed' => TRUE,
Neil Drumm's avatar
Neil Drumm committed
161
162
163
  );
  $form['workflow']['node_options'] = array('#type' => 'checkboxes',
    '#title' => t('Default options'),
164
    '#default_value' => variable_get('node_options_' . $type->type, array('status', 'promote')),
Neil Drumm's avatar
Neil Drumm committed
165
166
167
168
169
170
171
172
    '#options' => array(
      'status' => t('Published'),
      'promote' => t('Promoted to front page'),
      'sticky' => t('Sticky at top of lists'),
      'revision' => t('Create new revision'),
    ),
    '#description' => t('Users with the <em>administer nodes</em> permission will be able to override these options.'),
  );
173
174
175
176
177
178
179
180
181
  $form['display'] = array(
    '#type' => 'fieldset',
    '#title' => t('Display settings'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['display']['node_submitted'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display post information'),
182
    '#default_value' => variable_get('node_submitted_' . $type->type, TRUE),
Dries Buytaert's avatar
Dries Buytaert committed
183
184
    '#description' => t('Enable the <em>submitted by Username on date</em> text.'),
  );
185
186
187
  $form['display']['teaser_length'] = array(
    '#type' => 'select', 
    '#title' => t('Length of trimmed posts'),
188
    '#default_value' => variable_get('teaser_length_' . $type->type, 600),
189
    '#options' => drupal_map_assoc(array(0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000), '_node_characters'),
190
    '#description' => t("The maximum number of characters used in the trimmed version of content.")
191
  );
Neil Drumm's avatar
Neil Drumm committed
192
193
194
195
196
197
  $form['old_type'] = array(
    '#type' => 'value',
    '#value' => $type->type,
  );
  $form['orig_type'] = array(
    '#type' => 'value',
198
    '#value' => isset($type->orig_type) ? $type->orig_type : '',
Neil Drumm's avatar
Neil Drumm committed
199
  );
200
  $form['base'] = array(
Neil Drumm's avatar
Neil Drumm committed
201
    '#type' => 'value',
202
    '#value' => $type->base,
Neil Drumm's avatar
Neil Drumm committed
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
  );
  $form['custom'] = array(
    '#type' => 'value',
    '#value' => $type->custom,
  );
  $form['modified'] = array(
    '#type' => 'value',
    '#value' => $type->modified,
  );
  $form['locked'] = array(
    '#type' => 'value',
    '#value' => $type->locked,
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save content type'),
220
    '#weight' => 40,
Neil Drumm's avatar
Neil Drumm committed
221
222
223
224
225
226
227
  );

  if ($type->custom) {
    if (!empty($type->type)) {
      $form['delete'] = array(
        '#type' => 'submit',
        '#value' => t('Delete content type'),
228
        '#weight' => 45,
Neil Drumm's avatar
Neil Drumm committed
229
230
231
232
233
234
235
      );
    }
  }
  else {
    $form['reset'] = array(
      '#type' => 'submit',
      '#value' => t('Reset to defaults'),
236
      '#weight' => 50,
Neil Drumm's avatar
Neil Drumm committed
237
238
239
    );
  }

240
  return $form;
Neil Drumm's avatar
Neil Drumm committed
241
242
243
}

/**
244
245
246
247
248
249
250
251
 * Helper function for teaser length choices.
 */
function _node_characters($length) {
  return ($length == 0) ? t('Unlimited') : format_plural($length, '1 character', '@count characters');
}

/**
 * Implementation of hook_form_validate().
Neil Drumm's avatar
Neil Drumm committed
252
 */
253
function node_type_form_validate($form, &$form_state) {
Neil Drumm's avatar
Neil Drumm committed
254
  $type = new stdClass();
255
256
  $type->type = trim($form_state['values']['type']);
  $type->name = trim($form_state['values']['name']);
Neil Drumm's avatar
Neil Drumm committed
257
258

  // Work out what the type was before the user submitted this form
259
  $old_type = trim($form_state['values']['old_type']);
Neil Drumm's avatar
Neil Drumm committed
260

261
  $types = node_type_get_names();
Neil Drumm's avatar
Neil Drumm committed
262

263
  if (!$form_state['values']['locked']) {
Neil Drumm's avatar
Neil Drumm committed
264
    if (isset($types[$type->type]) && $type->type != $old_type) {
265
      form_set_error('type', t('The machine-readable name %type is already taken.', array('%type' => $type->type)));
Neil Drumm's avatar
Neil Drumm committed
266
    }
267
    if (!preg_match('!^[a-z0-9_]+$!', $type->type)) {
268
      form_set_error('type', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
Neil Drumm's avatar
Neil Drumm committed
269
    }
270
271
272
273
    // 'theme' conflicts with theme_node_form().
    // '0' is invalid, since elsewhere we check it using empty().
    if (in_array($type->type, array('0', 'theme'))) {
      form_set_error('type', t("Invalid machine-readable name. Please enter a name other than %invalid.", array('%invalid' => $type->type)));
274
    }
Neil Drumm's avatar
Neil Drumm committed
275
276
277
278
279
  }

  $names = array_flip($types);

  if (isset($names[$type->name]) && $names[$type->name] != $old_type) {
280
    form_set_error('name', t('The human-readable name %name is already taken.', array('%name' => $type->name)));
Neil Drumm's avatar
Neil Drumm committed
281
282
283
284
  }
}

/**
285
 * Implement hook_form_submit().
Neil Drumm's avatar
Neil Drumm committed
286
 */
287
288
function node_type_form_submit($form, &$form_state) {
  $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
Neil Drumm's avatar
Neil Drumm committed
289

290
  $type = node_type_set_defaults();
Neil Drumm's avatar
Neil Drumm committed
291

292
293
294
295
  $type->type = trim($form_state['values']['type']);
  $type->name = trim($form_state['values']['name']);
  $type->orig_type = trim($form_state['values']['orig_type']);
  $type->old_type = isset($form_state['values']['old_type']) ? $form_state['values']['old_type'] : $type->type;
Neil Drumm's avatar
Neil Drumm committed
296

297
298
299
300
301
  $type->description = $form_state['values']['description'];
  $type->help = $form_state['values']['help'];
  $type->min_word_count = $form_state['values']['min_word_count'];
  $type->title_label = $form_state['values']['title_label'];
  $type->body_label = $form_state['values']['body_label'];
Neil Drumm's avatar
Neil Drumm committed
302

303
304
305
306
307
  // title_label is required in core; has_title will always be true, unless a
  // module alters the title field.
  $type->has_title = ($type->title_label != '');
  $type->has_body = ($type->body_label != '');

308
  $type->base = !empty($form_state['values']['base']) ? $form_state['values']['base'] : 'node_content';
309
  $type->custom = $form_state['values']['custom'];
Neil Drumm's avatar
Neil Drumm committed
310
  $type->modified = TRUE;
311
  $type->locked = $form_state['values']['locked'];
Neil Drumm's avatar
Neil Drumm committed
312
313
314
315
316

  if ($op == t('Reset to defaults')) {
    node_type_reset($type);
  }
  elseif ($op == t('Delete content type')) {
Dries Buytaert's avatar
Dries Buytaert committed
317
    $form_state['redirect'] = 'admin/build/node-type/' . str_replace('_', '-', $type->old_type) . '/delete';
318
    return;
Neil Drumm's avatar
Neil Drumm committed
319
320
321
322
  }

  $status = node_type_save($type);

323
  $variables = $form_state['values'];
324

Neil Drumm's avatar
Neil Drumm committed
325
326
  // Remove everything that's been saved already - whatever's left is assumed
  // to be a persistent variable.
327
  foreach ($variables as $key => $value) {
Neil Drumm's avatar
Neil Drumm committed
328
    if (isset($type->$key)) {
329
      unset($variables[$key]);
Neil Drumm's avatar
Neil Drumm committed
330
331
332
    }
  }

333
  unset($variables['form_token'], $variables['op'], $variables['submit'], $variables['delete'], $variables['reset'], $variables['form_id']);
Neil Drumm's avatar
Neil Drumm committed
334
335

  // Save or reset persistent variable values.
336
  foreach ($variables as $key => $value) {
337
338
    $variable_new = $key . '_' . $type->type;
    $variable_old = $key . '_' . $type->old_type;
339

Neil Drumm's avatar
Neil Drumm committed
340
    if ($op == t('Reset to defaults')) {
341
      variable_del($variable_old);
Neil Drumm's avatar
Neil Drumm committed
342
343
344
345
346
    }
    else {
      if (is_array($value)) {
        $value = array_keys(array_filter($value));
      }
347
      variable_set($variable_new, $value);
Neil Drumm's avatar
Neil Drumm committed
348

349
350
      if ($variable_new != $variable_old) {
        variable_del($variable_old);
Neil Drumm's avatar
Neil Drumm committed
351
352
353
354
355
      }
    }
  }

  node_types_rebuild();
356
  $t_args = array('%name' => $type->name);
Neil Drumm's avatar
Neil Drumm committed
357
358
359
360
361
362
363
364
365
366
367

  if ($op == t('Reset to defaults')) {
    drupal_set_message(t('The content type %name has been reset to its default values.', $t_args));
    return;
  }

  if ($status == SAVED_UPDATED) {
    drupal_set_message(t('The content type %name has been updated.', $t_args));
  }
  elseif ($status == SAVED_NEW) {
    drupal_set_message(t('The content type %name has been added.', $t_args));
368
    watchdog('node', 'Added content type %name.', $t_args, WATCHDOG_NOTICE, l(t('view'), 'admin/build/types'));
Neil Drumm's avatar
Neil Drumm committed
369
370
  }

371
  $form_state['redirect'] = 'admin/build/types';
372
  return;
Neil Drumm's avatar
Neil Drumm committed
373
374
}

375
/**
376
 * Implement hook_node_type().
377
378
 */
function node_node_type($op, $info) {
379
  if ($op != 'delete' && !empty($info->old_type) && $info->old_type != $info->type) {
380
381
382
    $update_count = node_type_update_nodes($info->old_type, $info->type);

    if ($update_count) {
383
      drupal_set_message(format_plural($update_count, 'Changed the content type of 1 post from %old-type to %type.', 'Changed the content type of @count posts from %old-type to %type.', array('%old-type' => $info->old_type, '%type' => $info->type)));
384
385
386
387
    }
  }
}

Neil Drumm's avatar
Neil Drumm committed
388
389
390
391
/**
 * Resets all of the relevant fields of a module-defined node type to their
 * default values.
 *
392
 * @param $type
Neil Drumm's avatar
Neil Drumm committed
393
394
395
396
 *   The node type to reset. The node type is passed back by reference with its
 *   resetted values. If there is no module-defined info for this node type,
 *   then nothing happens.
 */
397
function node_type_reset($type) {
398
  $info_array = module_invoke_all('node_info');
Neil Drumm's avatar
Neil Drumm committed
399
  if (isset($info_array[$type->orig_type])) {
400
    $info_array[$type->orig_type]['type'] = $type->orig_type;
401
    $info = node_type_set_defaults($info_array[$type->orig_type]);
Neil Drumm's avatar
Neil Drumm committed
402
403
404
405
406
407
408
409
410
411

    foreach ($info as $field => $value) {
      $type->$field = $value;
    }
  }
}

/**
 * Menu callback; delete a single content type.
 */
412
function node_type_delete_confirm(&$form_state, $type) {
Neil Drumm's avatar
Neil Drumm committed
413
414
415
  $form['type'] = array('#type' => 'value', '#value' => $type->type);
  $form['name'] = array('#type' => 'value', '#value' => $type->name);

416
  $message = t('Are you sure you want to delete the content type %type?', array('%type' => $type->name));
Neil Drumm's avatar
Neil Drumm committed
417
418
  $caption = '';

419
  $num_nodes = db_query("SELECT COUNT(*) FROM {node} WHERE type = :type", array(':type' => $type->type))->fetchField();
Neil Drumm's avatar
Neil Drumm committed
420
  if ($num_nodes) {
421
    $caption .= '<p>' . format_plural($num_nodes, '<strong>Warning:</strong> there is currently 1 %type post on your site. It may not be able to be displayed or edited correctly, once you have removed this content type.', '<strong>Warning:</strong> there are currently @count %type posts on your site. They may not be able to be displayed or edited correctly, once you have removed this content type.', array('%type' => $type->name)) . '</p>';
Neil Drumm's avatar
Neil Drumm committed
422
423
  }

424
  $caption .= '<p>' . t('This action cannot be undone.') . '</p>';
Neil Drumm's avatar
Neil Drumm committed
425

426
  return confirm_form($form, $message, 'admin/build/types', $caption, t('Delete'));
Neil Drumm's avatar
Neil Drumm committed
427
428
429
}

/**
430
 * Process content type delete confirm submissions.
Neil Drumm's avatar
Neil Drumm committed
431
 */
432
433
function node_type_delete_confirm_submit($form, &$form_state) {
  node_type_delete($form_state['values']['type']);
Neil Drumm's avatar
Neil Drumm committed
434

435
436
  variable_del('teaser_length_' . $form_state['values']['type']);
  variable_del('node_preview_' . $form_state['values']['type']);
437
  $t_args = array('%name' => $form_state['values']['name']);
Neil Drumm's avatar
Neil Drumm committed
438
  drupal_set_message(t('The content type %name has been deleted.', $t_args));
439
  watchdog('menu', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE);
Neil Drumm's avatar
Neil Drumm committed
440
441
442

  node_types_rebuild();

443
  $form_state['redirect'] = 'admin/build/types';
444
  return;
Neil Drumm's avatar
Neil Drumm committed
445
}