1 node.pages.inc node_form($form, &$form_state, Node $node)

Form constructor for the node add/edit form.

See also

node_form_validate()

node_form_submit()

node_form_submit_build_node()

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;
}