1 node.pages.inc | node_form($form, &$form_state, Node $node) |
Form constructor for the node add/edit form.
See also
Related topics
File
- core/
modules/ node/ node.pages.inc, line 111 - Callbacks for adding, editing, and deleting content and managing revisions.
Code
function node_form($form, &$form_state, Node $node) {
$config = config('system.core');
// During initial form build, add the node entity to the form state for use
// during form building and processing. During a rebuild, use what is in the
// form state.
if (!isset($form_state['node'])) {
node_object_prepare($node);
$form_state['node'] = $node;
}
else {
$node = $form_state['node'];
}
$node_type = node_type_get_type($node->type);
$status = $node->nid ? $node->status : $node_type->settings['status_default'];
$node_preview = $node_type->settings['node_preview'];
// Check if we can retrieve a node from the tempstore.
$node_tempstore_id = isset($form_state['node_tempstore_id']) ? $form_state['node_tempstore_id'] : node_build_tempstore_id();
$form_state['node_tempstore_id'] = $node_tempstore_id;
if ($data = node_tempstore_load($node_tempstore_id)) {
$node = $data;
$form_state['entity'] = $node;
$status = $node->status;
// Restore the destination if we previewed the node.
if (isset($node->destination)) {
$form['node_destination'] = array(
'#type' => 'value',
'#value' => $node->destination,
);
}
node_clear_node_tempstore($node_tempstore_id);
}
// Override the default CSS class name, since the user-defined node type name
// in 'TYPE-node-form' potentially clashes with third-party class names.
$form['#attributes']['class'][0] = backdrop_html_class('node-' . $node->type . '-form');
$form['help'] = array(
'#type' => 'help',
'#markup' => filter_xss_admin($node_type->help),
'#access' => !empty($node_type->help),
'#weight' => -500,
);
// Basic node information.
// These elements are just values so they are not even sent to the client.
foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) {
$form[$key] = array(
'#type' => 'value',
'#value' => isset($node->$key) ? $node->$key : NULL,
);
}
// Changed must be sent to the client, for later overwrite error checking.
$form['changed'] = array(
'#type' => 'hidden',
'#default_value' => isset($node->changed) ? $node->changed : NULL,
);
// Invoke hook_form() to get the node-specific bits. Can't use node_invoke(),
// because hook_form() needs to be able to receive $form_state by reference.
// @todo hook_form() implementations are unable to add #validate or #submit
// handlers to the form buttons below. Remove hook_form() entirely.
$function = node_type_get_base($node) . '_form';
if (function_exists($function) && ($extra = $function($node, $form_state))) {
$form = array_merge_recursive($form, $extra);
}
// If the node type has a title, and the node type form defined no special
// weight for it, we default to a weight of -5 for consistency.
if (isset($form['title']) && !isset($form['title']['#weight'])) {
$form['title']['#weight'] = -5;
}
$form['#node'] = $node;
if ($node_type->settings['language'] && module_exists('language')) {
$language_options = array();
foreach (language_list(TRUE) as $langcode => $language) {
$option_label = $language->name;
if (isset($language->native)) {
$option_label .= ' (' . $language->native . ')';
}
$language_options[$langcode] = $option_label;
}
$form['langcode'] = array(
'#type' => 'select',
'#title' => t('Language'),
'#default_value' => (isset($node->langcode) ? $node->langcode : ''),
'#options' => $language_options,
'#empty_value' => LANGUAGE_NONE,
);
}
else {
$form['langcode'] = array(
'#type' => 'value',
// New nodes without multilingual support have undefined language, old
// nodes keep their language if language.module is not available.
'#value' => !isset($form['#node']->nid) ? LANGUAGE_NONE : $node->langcode,
);
}
$form['additional_settings'] = array(
'#type' => 'vertical_tabs',
'#weight' => 99,
);
// Add a log field if the "Create new revision" option is checked, or if the
// current user has the ability to check that option.
$form['revision_information'] = array(
'#type' => 'fieldset',
'#title' => t('Revision information'),
'#collapsible' => TRUE,
// Collapsed by default when "Create new revision" is unchecked
'#collapsed' => !$node->revision,
'#group' => 'additional_settings',
'#attributes' => array(
'class' => array('node-form-revision-information'),
),
'#attached' => array(
'js' => array(backdrop_get_path('module', 'node') . '/js/node.js'),
),
'#weight' => 20,
'#access' => $node->nid && $node_type->settings['revision_enabled'] && ($node->revision || user_access('administer nodes')),
);
$form['revision_information']['revision'] = array(
'#type' => 'checkbox',
'#title' => t('Create new revision'),
'#default_value' => $node->revision,
'#access' => user_access('administer nodes'),
);
// Check the revision log checkbox when the log textarea is filled in.
// This must not happen if "Create new revision" is enabled by default, since
// the state would auto-disable the checkbox otherwise.
if (!$node->revision) {
$form['revision_information']['revision']['#states'] = array(
'checked' => array(
'textarea[name="log"]' => array('empty' => FALSE),
),
);
}
$form['revision_information']['log'] = array(
'#type' => 'textarea',
'#title' => t('Revision log message'),
'#rows' => 4,
'#default_value' => !empty($node->log) ? $node->log : '',
'#description' => t('Briefly describe the changes you have made.'),
);
// Node author information for administrators
$form['author'] = array(
'#type' => 'fieldset',
'#access' => user_access('administer nodes'),
'#title' => t('Authoring information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'additional_settings',
'#attributes' => array(
'class' => array('node-form-author'),
),
'#attached' => array(
'js' => array(
backdrop_get_path('module', 'node') . '/js/node.js',
array(
'type' => 'setting',
'data' => array('anonymous' => $config->get('anonymous')),
),
),
),
'#weight' => -5,
);
$form['author']['name'] = array(
'#type' => 'textfield',
'#title' => t('Authored by'),
'#maxlength' => 60,
'#autocomplete_path' => 'user/autocomplete',
'#default_value' => !empty($node->name) ? $node->name : '',
'#weight' => -1,
'#description' => t('Leave blank for %anonymous.', array('%anonymous' => $config->getTranslated('anonymous'))),
);
$form['author']['date'] = array(
'#type' => 'html_datetime',
'#title' => t('Authored on'),
'#default_value' => array(
'date' => format_date($node->created, 'custom', DATE_FORMAT_DATE),
'time' => format_date($node->created, 'custom', DATE_FORMAT_TIME),
),
'#attributes' => array(
'date' => array(
'min' => '1970-01-01',
'max' => '2037-12-31',
'placeholder' => t('e.g. @date', array(
'@date' => format_date(REQUEST_TIME, 'custom', DATE_FORMAT_DATE)
)),
),
'time' => array(
'placeholder' => t('e.g. @date', array(
'@date' => format_date(REQUEST_TIME, 'custom', DATE_FORMAT_TIME)
)),
),
),
'#description' => t('Leave blank to use the current time.'),
);
// Node options for administrators
$form['options'] = array(
'#type' => 'fieldset',
'#access' => user_access('administer nodes'),
'#title' => $node->nid ? t('Publishing options') : t('Publishing actions'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'additional_settings',
'#attributes' => array(
'class' => array('node-form-options'),
),
'#attached' => array(
'js' => array(backdrop_get_path('module', 'node') . '/js/node.js'),
),
'#weight' => -10,
);
if ($node->scheduled && $node_type->settings['scheduling_enabled']) {
$status = NODE_SCHEDULED;
}
$form['options']['status'] = array(
'#type' => 'radios',
'#title' => $node->nid ? t('Published state') : t('Publish action'),
'#options' => array(
NODE_PUBLISHED => $node->nid ? t('Published') : t('Publish now'),
NODE_NOT_PUBLISHED => $node->nid ? t('Draft') : t('Save as draft'),
NODE_SCHEDULED => $node->nid ? t('Scheduled for later') : t('Schedule for later'),
),
'#default_value' => $status,
);
$form['options']['status'][NODE_PUBLISHED]['#description'] = t('Site visitors will be able to access this content.');
$form['options']['status'][NODE_NOT_PUBLISHED]['#description'] = t('Only the content creator or site administrators will be able to access this content.');
$form['options']['status'][NODE_SCHEDULED]['#description'] = t('The content is saved as draft, and will be automatically published on the date and time selected.');
// Adjustments to the status radios if scheduling is enabled/disabled.
if ($node_type->settings['scheduling_enabled']) {
// Default the scheduled time to the current time + 1 day.
$scheduled_date = $node->scheduled ? $node->scheduled : REQUEST_TIME + 86400;
$form['options']['scheduled'] = array(
'#type' => 'html_datetime',
'#title' => t('Publish on'),
'#default_value' => array(
'date' => format_date($scheduled_date, 'custom', DATE_FORMAT_DATE),
'time' => format_date($scheduled_date, 'custom', DATE_FORMAT_TIME),
),
'#attributes' => array(
'date' => array(
'min' => format_date(REQUEST_TIME, 'custom', DATE_FORMAT_DATE),
'max' => '2037-12-31',
'placeholder' => t('e.g. @date', array(
'@date' => format_date(REQUEST_TIME, 'custom', DATE_FORMAT_DATE)
)),
),
'time' => array(
'placeholder' => t('e.g. @date', array(
'@date' => format_date(REQUEST_TIME, 'custom', DATE_FORMAT_TIME)
)),
),
),
'#states' => array(
'visible' => array(
':input[name="status"]' => array('value' => NODE_SCHEDULED),
),
),
);
}
else {
$form['options']['status'][NODE_SCHEDULED]['#access'] = FALSE;
}
$form['options']['additional'] = array(
// @todo candidate for conversion to '#type' => 'html_tag'.
// See: https://github.com/backdrop/backdrop-issues/issues/5223
'#type' => 'markup',
'#prefix' => '<label id="promote-sticky-label">',
'#markup' => $node->nid ? t('Additional options') : t('Additional actions'),
'#suffix' => '</label>',
'#access' => $node_type->settings['promote_enabled'] || $node_type->settings['sticky_enabled'],
);
$form['options']['promote'] = array(
'#type' => 'checkbox',
'#title' => $node->nid ? t('Promoted') : t('Promote'),
'#default_value' => $node->promote,
'#access' => $node_type->settings['promote_enabled'],
'#attributes' => array(
'aria-labelledby' => array('promote-sticky-label'),
),
);
$form['options']['sticky'] = array(
'#type' => 'checkbox',
'#title' => $node->nid ? t('Sticky at top of lists') : t('Make sticky at top of lists'),
'#default_value' => $node->sticky,
'#access' => $node_type->settings['sticky_enabled'],
'#attributes' => array(
'aria-labelledby' => array('promote-sticky-label'),
),
);
// Prepare cancel link.
if (isset($_GET['destination'])) {
$path = $_GET['destination'];
}
elseif (isset($_SERVER['HTTP_REFERER'])) {
$path = $_SERVER['HTTP_REFERER'];
// When coming from preview, the $path is set to the preview page.
if (strpos($path, 'node/preview')) {
if (isset($node->nid)) {
$path = 'node/' . $node->nid;
}
else {
// @todo store old $_SERVER['HTTP_REFERER'] so we can redirect to that.
$path = '<front>';
}
}
}
elseif (isset($node->nid)) {
$path = 'node/' . $node->nid;
}
else {
$path = '<front>';
}
$options = backdrop_parse_url($path);
$options['attributes']['class'][] = 'form-cancel';
// Add the buttons.
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#access' => (!form_get_errors()),
'#value' => t('Save'),
'#weight' => 5,
'#submit' => array('node_form_submit'),
);
$form['actions']['preview'] = array(
'#type' => 'submit',
'#access' => (!form_get_errors() && $node_preview != BACKDROP_DISABLED),
'#value' => t('Preview'),
'#weight' => 6,
'#submit' => array('node_form_preview'),
);
if (!empty($node->nid) && node_access('delete', $node)) {
// Use a link for the delete button so the form doesn't need to validate.
$form['actions']['delete'] = array(
'#type' => 'link',
'#title' => t('Delete'),
'#href' => "node/{$node->nid}/delete",
'#options' => array(
'query' => isset($_GET['destination']) ? backdrop_get_destination() : array(),
'attributes' => array('class' => array('button', 'button-secondary', 'form-delete'))
),
'#weight' => 15,
);
}
$form['actions']['cancel'] = array(
'#type' => 'link',
'#title' => t('Cancel'),
'#href' => $options['path'],
'#options' => $options,
'#weight' => 20,
);
// This form uses a button-level #submit handler for the form's main submit
// action. node_form_submit() manually invokes all form-level #submit handlers
// of the form. Without explicitly setting #submit, Form API would auto-detect
// node_form_submit() as submit handler, but that is the button-level #submit
// handler for the 'Save' action. To maintain backwards compatibility, a
// #submit handler is auto-suggested for custom node type modules.
$form['#validate'][] = 'node_form_validate';
if (!isset($form['#submit']) && function_exists($node->type . '_node_form_submit')) {
$form['#submit'][] = $node->type . '_node_form_submit';
}
$form += array('#submit' => array());
field_attach_form('node', $node, $form, $form_state, $node->langcode);
return $form;
}