content_types.inc 14.8 KB
Newer Older
drumm's avatar
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'));
drumm's avatar
drumm committed
16
17
18
  $rows = array();

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

      // Set the delete column.
      if ($type->custom) {
28
        $row[] = array('data' => l(t('delete'), 'admin/structure/node-type/' . $type_url_str . '/delete'));
drumm's avatar
drumm committed
29
30
      }
      else {
31
        $row[] = array('data' => '');
drumm's avatar
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/structure/types/add'))), 'colspan' => '5', 'class' => 'message'));
drumm's avatar
drumm committed
39
  }
40
41
42
43
44
45
  
  $build['node_table'] = array(
    '#theme' => 'table',
    '#header' => $header,
    '#rows' => $rows
  );
drumm's avatar
drumm committed
46

47
  return $build;
drumm's avatar
drumm committed
48
49
}

50
51
52
53
54
55
56
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;
}

drumm's avatar
drumm committed
57
58
59
/**
 * Generates the node type editing form.
 */
60
function node_type_form(&$form_state, $type = NULL) {
61
  drupal_add_js(drupal_get_path('module', 'node') . '/content_types.js');
drumm's avatar
drumm committed
62
  if (!isset($type->type)) {
63
    // This is a new type. Node module managed types are custom and unlocked.
64
    $type = node_type_set_defaults(array('custom' => 1, 'locked' => 0));
drumm's avatar
drumm committed
65
66
  }

67
68
  // Make the type object available to implementations of hook_form_alter.
  $form['#node_type'] = $type;
69

drumm's avatar
drumm committed
70
71
72
73
74
75
76
77
  $form['identity'] = array(
    '#type' => 'fieldset',
    '#title' => t('Identification'),
  );
  $form['identity']['name'] = array(
    '#title' => t('Name'),
    '#type' => 'textfield',
    '#default_value' => $type->name,
78
    '#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.'),
drumm's avatar
drumm committed
79
    '#required' => TRUE,
80
    '#size' => 30,
81
    '#field_suffix' => ' <small id="node-type-name-suffix">&nbsp;</small>',
drumm's avatar
drumm committed
82
83
84
85
  );

  if (!$type->locked) {
    $form['identity']['type'] = array(
86
      '#title' => t('Machine name'),
drumm's avatar
drumm committed
87
88
89
90
      '#type' => 'textfield',
      '#default_value' => $type->type,
      '#maxlength' => 32,
      '#required' => TRUE,
91
      '#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.'),
drumm's avatar
drumm committed
92
93
94
95
96
97
98
99
    );
  }
  else {
    $form['identity']['type'] = array(
      '#type' => 'value',
      '#value' => $type->type,
    );
    $form['identity']['type_display'] = array(
100
      '#title' => t('Machine name'),
drumm's avatar
drumm committed
101
      '#type' => 'item',
102
      '#markup' => theme('placeholder', $type->type),
drumm's avatar
drumm committed
103
104
105
106
      '#description' => t('The machine-readable name of this content type. This field cannot be modified for system-defined content types.'),
    );
  }

107
108
109
110
111
112
  $form['identity']['description'] = array(
    '#title' => t('Description'),
    '#type' => 'textarea',
    '#default_value' => $type->description,
    );

drumm's avatar
drumm committed
113
114
  $form['submission'] = array(
    '#type' => 'fieldset',
115
    '#title' => t('Submission form settings'),
drumm's avatar
drumm committed
116
117
    '#collapsible' => TRUE,
  );
118
119
120
121
122
123
124
125
126
127
128
129
  $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;
drumm's avatar
drumm committed
130
  }
131
132
133
  $form['submission']['body_label'] = array(
    '#title' => t('Body field label'),
    '#type' => 'textfield',
134
    '#default_value' => isset($type->body_label) ? $type->body_label : '',
135
    '#description' => t('To remove the body field, remove text and leave blank.'),
136
  );
137
138
  $form['submission']['node_preview'] = array(
    '#type' => 'radios',
139
    '#title' => t('Preview before submitting'),
140
141
142
143
144
145
146
    '#default_value' => variable_get('node_preview_' . $type->type, DRUPAL_OPTIONAL),
    '#options' => array(
      DRUPAL_DISABLED => t('Disabled'),
      DRUPAL_OPTIONAL => t('Optional'),
      DRUPAL_REQUIRED => t('Required'),
    ),
    );
147
148
149
  $form['submission']['help']  = array(
    '#type' => 'textarea',
    '#title' => t('Explanation or submission guidelines'),
150
    '#default_value' => $type->help,
151
    '#description' => t('This text will be displayed at the top of the submission form for this content type.')
152
  );
drumm's avatar
drumm committed
153
154
  $form['workflow'] = array(
    '#type' => 'fieldset',
155
    '#title' => t('Publishing options'),
drumm's avatar
drumm committed
156
    '#collapsible' => TRUE,
157
    '#collapsed' => TRUE,
drumm's avatar
drumm committed
158
159
160
  );
  $form['workflow']['node_options'] = array('#type' => 'checkboxes',
    '#title' => t('Default options'),
161
    '#default_value' => variable_get('node_options_' . $type->type, array('status', 'promote')),
drumm's avatar
drumm committed
162
163
164
165
166
167
168
169
    '#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.'),
  );
170
171
172
173
174
175
176
177
178
  $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'),
179
    '#default_value' => variable_get('node_submitted_' . $type->type, TRUE),
Dries's avatar
Dries committed
180
181
    '#description' => t('Enable the <em>submitted by Username on date</em> text.'),
  );
182
183
184
  $form['display']['teaser_length'] = array(
    '#type' => 'select', 
    '#title' => t('Length of trimmed posts'),
185
    '#default_value' => variable_get('teaser_length_' . $type->type, 600),
186
    '#options' => drupal_map_assoc(array(0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000), '_node_characters'),
187
    '#description' => t("The maximum number of characters used in the trimmed version of content.")
188
  );
drumm's avatar
drumm committed
189
190
191
192
193
194
  $form['old_type'] = array(
    '#type' => 'value',
    '#value' => $type->type,
  );
  $form['orig_type'] = array(
    '#type' => 'value',
195
    '#value' => isset($type->orig_type) ? $type->orig_type : '',
drumm's avatar
drumm committed
196
  );
197
  $form['base'] = array(
drumm's avatar
drumm committed
198
    '#type' => 'value',
199
    '#value' => $type->base,
drumm's avatar
drumm committed
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  );
  $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'),
217
    '#weight' => 40,
drumm's avatar
drumm committed
218
219
220
221
222
223
224
  );

  if ($type->custom) {
    if (!empty($type->type)) {
      $form['delete'] = array(
        '#type' => 'submit',
        '#value' => t('Delete content type'),
225
        '#weight' => 45,
drumm's avatar
drumm committed
226
227
228
229
      );
    }
  }

230
  return $form;
drumm's avatar
drumm committed
231
232
233
}

/**
234
235
236
237
238
239
240
241
 * 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().
drumm's avatar
drumm committed
242
 */
243
function node_type_form_validate($form, &$form_state) {
drumm's avatar
drumm committed
244
  $type = new stdClass();
245
246
  $type->type = trim($form_state['values']['type']);
  $type->name = trim($form_state['values']['name']);
drumm's avatar
drumm committed
247
248

  // Work out what the type was before the user submitted this form
249
  $old_type = trim($form_state['values']['old_type']);
drumm's avatar
drumm committed
250

251
  $types = node_type_get_names();
drumm's avatar
drumm committed
252

253
  if (!$form_state['values']['locked']) {
drumm's avatar
drumm committed
254
    if (isset($types[$type->type]) && $type->type != $old_type) {
255
      form_set_error('type', t('The machine-readable name %type is already taken.', array('%type' => $type->type)));
drumm's avatar
drumm committed
256
    }
257
    if (!preg_match('!^[a-z0-9_]+$!', $type->type)) {
258
      form_set_error('type', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
drumm's avatar
drumm committed
259
    }
260
261
262
263
    // '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)));
264
    }
drumm's avatar
drumm committed
265
266
267
268
269
  }

  $names = array_flip($types);

  if (isset($names[$type->name]) && $names[$type->name] != $old_type) {
270
    form_set_error('name', t('The human-readable name %name is already taken.', array('%name' => $type->name)));
drumm's avatar
drumm committed
271
272
273
274
  }
}

/**
275
 * Implement hook_form_submit().
drumm's avatar
drumm committed
276
 */
277
278
function node_type_form_submit($form, &$form_state) {
  $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
drumm's avatar
drumm committed
279

280
  $type = node_type_set_defaults();
drumm's avatar
drumm committed
281

282
283
284
285
  $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;
drumm's avatar
drumm committed
286

287
288
289
290
  $type->description = $form_state['values']['description'];
  $type->help = $form_state['values']['help'];
  $type->title_label = $form_state['values']['title_label'];
  $type->body_label = $form_state['values']['body_label'];
drumm's avatar
drumm committed
291

292
293
294
295
296
  // 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 != '');

297
  $type->base = !empty($form_state['values']['base']) ? $form_state['values']['base'] : 'node_content';
298
  $type->custom = $form_state['values']['custom'];
drumm's avatar
drumm committed
299
  $type->modified = TRUE;
300
  $type->locked = $form_state['values']['locked'];
drumm's avatar
drumm committed
301

302
  if ($op == t('Delete content type')) {
303
    $form_state['redirect'] = 'admin/structure/node-type/' . str_replace('_', '-', $type->old_type) . '/delete';
304
    return;
drumm's avatar
drumm committed
305
306
307
308
  }

  $status = node_type_save($type);

309
  $variables = $form_state['values'];
310

drumm's avatar
drumm committed
311
312
  // Remove everything that's been saved already - whatever's left is assumed
  // to be a persistent variable.
313
  foreach ($variables as $key => $value) {
drumm's avatar
drumm committed
314
    if (isset($type->$key)) {
315
      unset($variables[$key]);
drumm's avatar
drumm committed
316
317
318
    }
  }

319
  unset($variables['form_token'], $variables['op'], $variables['submit'], $variables['delete'], $variables['reset'], $variables['form_id']);
drumm's avatar
drumm committed
320
321

  // Save or reset persistent variable values.
322
  foreach ($variables as $key => $value) {
323
324
    $variable_new = $key . '_' . $type->type;
    $variable_old = $key . '_' . $type->old_type;
325

326
327
    if (is_array($value)) {
      $value = array_keys(array_filter($value));
drumm's avatar
drumm committed
328
    }
329
    variable_set($variable_new, $value);
drumm's avatar
drumm committed
330

331
332
    if ($variable_new != $variable_old) {
      variable_del($variable_old);
drumm's avatar
drumm committed
333
334
335
336
    }
  }

  node_types_rebuild();
337
  $t_args = array('%name' => $type->name);
drumm's avatar
drumm committed
338
339
340
341
342
343

  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));
344
    watchdog('node', 'Added content type %name.', $t_args, WATCHDOG_NOTICE, l(t('view'), 'admin/structure/types'));
drumm's avatar
drumm committed
345
346
  }

347
  $form_state['redirect'] = 'admin/structure/types';
348
  return;
drumm's avatar
drumm committed
349
350
}

351
/**
352
 * Implement hook_node_type().
353
354
 */
function node_node_type($op, $info) {
355
  if ($op != 'delete' && !empty($info->old_type) && $info->old_type != $info->type) {
356
357
358
    $update_count = node_type_update_nodes($info->old_type, $info->type);

    if ($update_count) {
359
      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)));
360
361
362
363
    }
  }
}

drumm's avatar
drumm committed
364
365
366
367
/**
 * Resets all of the relevant fields of a module-defined node type to their
 * default values.
 *
368
 * @param $type
drumm's avatar
drumm committed
369
370
371
372
 *   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.
 */
373
function node_type_reset($type) {
374
  $info_array = module_invoke_all('node_info');
drumm's avatar
drumm committed
375
  if (isset($info_array[$type->orig_type])) {
376
    $info_array[$type->orig_type]['type'] = $type->orig_type;
377
    $info = node_type_set_defaults($info_array[$type->orig_type]);
drumm's avatar
drumm committed
378
379
380
381
382
383
384
385
386
387

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

/**
 * Menu callback; delete a single content type.
 */
388
function node_type_delete_confirm(&$form_state, $type) {
drumm's avatar
drumm committed
389
390
391
  $form['type'] = array('#type' => 'value', '#value' => $type->type);
  $form['name'] = array('#type' => 'value', '#value' => $type->name);

392
  $message = t('Are you sure you want to delete the content type %type?', array('%type' => $type->name));
drumm's avatar
drumm committed
393
394
  $caption = '';

395
  $num_nodes = db_query("SELECT COUNT(*) FROM {node} WHERE type = :type", array(':type' => $type->type))->fetchField();
drumm's avatar
drumm committed
396
  if ($num_nodes) {
397
    $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>';
drumm's avatar
drumm committed
398
399
  }

400
  $caption .= '<p>' . t('This action cannot be undone.') . '</p>';
drumm's avatar
drumm committed
401

402
  return confirm_form($form, $message, 'admin/structure/types', $caption, t('Delete'));
drumm's avatar
drumm committed
403
404
405
}

/**
406
 * Process content type delete confirm submissions.
drumm's avatar
drumm committed
407
 */
408
409
function node_type_delete_confirm_submit($form, &$form_state) {
  node_type_delete($form_state['values']['type']);
drumm's avatar
drumm committed
410

411
412
  variable_del('teaser_length_' . $form_state['values']['type']);
  variable_del('node_preview_' . $form_state['values']['type']);
413
  $t_args = array('%name' => $form_state['values']['name']);
drumm's avatar
drumm committed
414
  drupal_set_message(t('The content type %name has been deleted.', $t_args));
415
  watchdog('menu', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE);
drumm's avatar
drumm committed
416
417
418

  node_types_rebuild();

419
  $form_state['redirect'] = 'admin/structure/types';
420
  return;
drumm's avatar
drumm committed
421
}