<?php
// $Id: flexinode_admin.module,v 1.5 2007/01/27 14:46:39 ber Exp $

/**
 * @file Administration interface for flexinode.
 **/

/**
 * Implementation of hook_help().
 */
function flexinode_admin_help($section) {
  switch ($section) {
    case 'admin/help#flexinode':
      $output = '<p>'.
        t('The flexinode module allows administrators to create simple new content types.  This is the administration part, which allows you to manage creation of new types of content without having to program a new content module.') .'</p>';
      $output .= '<p>'.
        t('When creating a new flexinode, administrators are presented with a flexinode form to create their new content type.  Once administrators have created their flexinode they can accept the format or choose to theme the content type to change it\'s presentation.  For users, creating content that is a flexinode is just like adding other content. The flexinode content type will show up alongside all normal content.') .'</p>';
      $output .= t('<p>You can</p>
        <ul>
        <li>create a flexinode content type at <a href="%admin-node-types" title="administer content types to add a flexinode"> administer &gt;&gt; content &gt;&gt; content types</a> and select <strong>add content type</strong>.</li>
        <li><a href="%node-add" title="create a new flexinode type">create content &gt;&gt; add type.</a></li>
        <li>administer flexinode at <a href="%admin-settings-flexinode">administer &gt;&gt; settings &gt;&gt; flexinode</a>.</li>
        ', array('%admin-node-types' => url('admin/node/types'), '%node-add' => url('node/add'), '%admin-settings-flexinode' => url('admin/settings/flexinode'))) .'</ul>';
        $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="%flexinode">Flexinode page</a>.', array('%flexinode' => 'http://www.drupal.org/handbook/modules/flexinode/')) .'</p>';
      return $output;
    case 'admin/modules#description':
      return t('Allows administrators to define their own content types.');
    case 'admin/node/types':
      return t('You may manage your own content types here. These will all have a "title" field to start off, and contain additional fields that you specify. Choose the "add content type" tab to add a new type. Make a selection below to edit an existing type or field. To delete a field or entire content type, first open the editing form for that field or type.');
    case 'admin/node/type':
      return t('Once you create a content type here you will be able to add additional fields to it on the "content types" tab.');
  }

  $output = '';

  return $output;
}

/**
 * Implementation of hook_menu().
 */
function flexinode_admin_menu($may_cache) {
  $items = array();

  if ($may_cache) {
    $admin_access = user_access('administer content types');
    foreach (flexinode_content_types() as $ctype) {
      $items[] = array(
        'path' => 'node/add/flexinode-'. $ctype->ctype_id,
        'title' => t($ctype->name),
        'access' => user_access('create '. $ctype->name .' content') or $admin_access
        );
    }

    // admin menu items
    $items[] = array(
      'path' => 'admin/node/types',
      'title' => t('content types'),
      'callback' => 'flexinode_admin_page_admin',
      'access' => $admin_access,
      'type' => MENU_LOCAL_TASK,
      );
    $items[] = array(
      'path' => 'admin/node/type',
      'title' => t('add content type'),
      'callback' => 'flexinode_admin_content_type_form',
      'access' => $admin_access,
      'type' => MENU_LOCAL_TASK,
      );
    $items[] = array(
      'path' => 'admin/node/type/delete',
      'title' => t('delete content type'),
      'callback' => 'flexinode_admin_confirm_delete_content_type',
      'access' => $admin_access,
      'type' => MENU_CALLBACK,
      );
    $items[] = array(
      'path' => 'admin/node/field',
      'title' => t('edit fields'),
      'callback' => 'flexinode_admin_admin_field',
      'access' => $admin_access,
      'type' => MENU_CALLBACK,
      );
    $items[] = array(
      'path' => 'flexinode/update',
      'title' => t('update'),
      'callback' => 'flexinode_admin_page_admin_update',
      'access' => $admin_access,
      'type' => MENU_CALLBACK,
      );
  }

  return $items;
}

/**
 * MENU CALLBACKS
 */
/**
 * Menu callback; presents an overview of all admin-defined content types.
 */
function flexinode_admin_page_admin($ctype_id = NULL) {
  $content_types = flexinode_content_types();

  $output = '';

  foreach ($content_types as $ctype) {
    $ctype = flexinode_load_content_type($ctype->ctype_id);
    $ctype->links[] = l(t('edit'), 'admin/node/type/'. $ctype->ctype_id);
    $ctype->links[] = l(t('settings'), 'admin/settings/content-types/flexinode-'. $ctype->ctype_id);


    $ctype->controls = flexinode_admin_field_select($ctype->ctype_id);

    foreach ($ctype->fields as $field) {
      $ctype->fieldlist[] = $field->label . ' (' . l(t('edit field'), 'admin/node/field/' . $field->field_id) .')';
    }

    $output .= theme('flexinode_type', $ctype);
    $first = FALSE;
  }

  if (strlen($output) == 0) {
    $output = '<p>'. t('No flexinode content types have been defined. You can <a href="%url">add a new content type</a>.', array('%url' => url('admin/node/type'))) .'</p>';
  }

  drupal_add_js('misc/collapse.js');
  return $output;
}

/**
 * Menu callback; presents a form to edit an existing field in a content type.
 */
function flexinode_admin_admin_field($field_id = NULL) {
  $op = $_POST['op'];

  $crumbs = drupal_get_breadcrumb();
  $crumbs[] = l('content types', 'admin/node/types');
  drupal_set_breadcrumb($crumbs);

  switch ($op) {
    case t('Add field'):
      $field_id = $_POST['edit']['field_type'];
      $output = flexinode_admin_field_form($field_id);
      break;
    case t('Confirm'):
      $field = flexinode_load_field($field_id);
      flexinode_admin_delete_field($field);
      drupal_goto('admin/node/types/'. $field->ctype_id);
      break;
    case t('Delete'):
      $field = flexinode_load_field($field_id);
      $output = flexinode_admin_confirm_delete_field($field);
      break;
    case t('More'):
    default:
      $output = flexinode_admin_field_form($field_id);
      break;
  }

  return $output;
}

/**
 * Perform database updates from older versions.
 *
 * This is a temporary solution until the install system becomes part of
 * the Drupal core.
 */
function flexinode_admin_page_admin_update($update_num = 0) {
  switch ($update_num) {
    case 1:
      db_query("ALTER TABLE {flexinode_field} MODIFY default_value MEDIUMTEXT NOT NULL");
      db_query("ALTER TABLE {flexinode_field} ADD show_teaser INT(1) UNSIGNED DEFAULT '0' NOT NULL");
      db_query("ALTER TABLE {flexinode_field} ADD show_table INT(1) UNSIGNED DEFAULT '0' NOT NULL");

      foreach (flexinode_content_types() as $type) {
        $field_id = flexinode_amin_save_field(array('label' => 'Description', 'default_value' => '', 'rows' => 10, 'required' => 0, 'weight' => 0, 'ctype_id' => $type->ctype_id, 'field_type' => 'textarea', 'options' => NULL, 'description' => ''));
        $result = db_query("SELECT body, nid FROM {node} WHERE type = 'flexinode-%d'", $type->ctype_id);
        while ($node = db_fetch_object($result)) {
          db_query("INSERT INTO {flexinode_data} (nid, field_id, textual_data) VALUES (%d, %d, '%s')", $node->nid, $field_id, $node->body);
        }
      }

      $output = 'Update complete.';
      break;

    default:
      $output = '<p>Which update do you wish to perform?</p>';
      $output .= '<dl>';
      $output .= '<dt>'. l('6/16/04', 'flexinode/update/1') .'</dt>';
      $output .= '<dd>Allows multiline default values for text areas. Allows administrator to configure which fields appear in the teaser and the tabular view. Deprecates special "Description" field in favor of using only basic textarea fields.</dd>';
      $output .= '</dl>';
  }
  return $output;
}

/**
 * CORE HOOKS
 */


/**
 * Implementation of hook_access().
 */
function flexinode_admin_access($op, $node) {
  global $user;

  if (!is_object($node)) {
    $type = $node;
    $node = new StdClass();
    $node->type = $type;
  }

  if ($op == 'create') {
    return user_access('create '. node_get_name($node) .' content');
  }

  if ($op == 'update') {
    foreach ($node as $fieldname => $field) {
      if (preg_match('!flexinode_[0-9]+_format!', $fieldname) && !filter_access($field)) {
        return FALSE;
      }
    }
  }

  if ($op == 'update' || $op == 'delete') {
    if (user_access('edit any '. node_get_name($node) .' content')) {
      return TRUE;
    }
    elseif (user_access('edit own '. node_get_name($node) .' content') && ($user->uid == $node->uid)) {
      return TRUE;
    }
  }
}

/**
 * Render a form for the editing of a content type.
 */
function flexinode_admin_content_type_form($ctype_id = NULL) {
  if ($ctype_id) {
    $ctype = flexinode_load_content_type($ctype_id);
  }

  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Content type name'),
    '#default_value' => $ctype->name,
    '#size' => 60,
    '#maxlength' => 128,
    '#required' => TRUE,
    );
  $form['description'] = array(
    '#type' => 'textfield',
    '#title' => t('Description'),
    '#default_value' => $ctype->description,
    '#size' => 60,
    '#maxlength' => 128,
    '#description' => t('A one-line description of the content type.'),
    );
  $form['help'] = array(
    '#type' => 'textarea',
    '#title' => t('Help text'),
    '#default_value' => $ctype->help,
    '#size' => 60,
    '#maxlength' => 5,
    '#description' => t('Instructions to present to the user when adding new content of this type.'),
    );
  $form['ctype_id'] = array('#type' => 'hidden', '#value' => $ctype_id);
  $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));

  if ($ctype_id) {
    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
  }

  return drupal_get_form('flexinode_admin_content_type_form', $form);
}

/**
 * flexinode_content_type_form hook_validate callback function.
 */
function flexinode_admin_content_type_form_validate($form_id, $edit) {
  if (!$edit['name']) {
    form_set_error('name', t('You must give this content type a name.'));
  }
}

/**
 * flexinode_content_type_form hook_submit callback function.
 */
function flexinode_admin_content_type_form_submit($form_id, $edit) {
  if($_POST['op'] == t('Delete')) {
    drupal_goto('admin/node/type/delete/'. $edit['ctype_id']);
  }
  if ($edit['ctype_id']) {
    $ctype_id = $edit['ctype_id'];

    $former_ctype = flexinode_load_content_type($ctype_id);

    db_query("UPDATE {flexinode_type} SET name = '%s', description = '%s', help = '%s' WHERE ctype_id = %d", $edit['name'], $edit['description'], $edit['help'], $ctype_id);

    drupal_set_message(t('updated content type "%name".', array('%name' => $edit['name'])));
  }
  else {
    $ctype_id = db_next_id('{flexinode_ctype}');

    db_query("INSERT INTO {flexinode_type} (name, description, help, ctype_id) VALUES ('%s', '%s', '%s', %d)", $edit['name'], $edit['description'], $edit['help'], $ctype_id);

    drupal_set_message(t('created new content type "%name".', array('%name' => $edit['name'])));
  }

  // update the cached "create content" menu
  menu_rebuild();
  drupal_goto('admin/node/types/'. $ctype_id);
}

/**
 * Generate a confirmation page for the deletion of a custom content type.
 */
function flexinode_admin_confirm_delete_content_type($ctype_id) {
  if($_POST['op'] == t('Confirm')) {
    flexinode_admin_delete_content_type($ctype_id);
    drupal_goto('admin/node/types');
  }

  $ctype = flexinode_load_content_type($ctype_id);
  $form['ctype_id'] = array(
    '#type' => 'hidden',
    '#value' =>  $ctype_id,
    );
  $form['name'] = array(
    '#type' => 'hidden',
    '#value' =>  $ctype->name,
    );
  return confirm_form('flexinode_confirm_delete_content_type', $form, t('Are you sure you want to delete the content type "%name"? All nodes of this type will be lost.', array('%name' => $ctype->name)), 'admin/node/type/'. $ctype_id);
}

/**
 * Delete a custom content type from the database.
 */
function flexinode_admin_delete_content_type($ctype_id) {
  // TODO: Delete files as appropriate.
  db_query('DELETE FROM {flexinode_type} WHERE ctype_id = %d', $ctype_id);
  db_query('DELETE FROM {flexinode_field} WHERE ctype_id = %d', $ctype_id);
  $result = db_query("SELECT nid FROM {node} WHERE type = '%s'", 'flexinode-' . $ctype_id);
  while ($node = db_fetch_object($result)) {
    db_query('DELETE FROM {flexinode_data} WHERE nid = %d', $node->nid);
  }
  db_query("DELETE FROM {node} WHERE type = '%s'", 'flexinode-' . $ctype_id);

  drupal_set_message(t('deleted content type'));

  // update the cached "create content" menu
  menu_rebuild();
}

/**
 * FLEXINODE FIELD FUNCTIONS
 */
/**
 * Generate a form for the editing of a content type field.
 * @param field_id can be either a field_id, or string containing the field type for new fields
 */
function flexinode_admin_field_form($field_id, $ctype_id = NULL) {
  if(is_numeric($field_id)) {
    $field = flexinode_load_field($field_id);
  }
  else {
    $field->field_type = $field_id;
    $ctype_id = $_POST['edit']['ctype_id'];
  }
  $form['label'] = array(
    '#type' => 'textfield',
    '#title' => t('Field label'),
    '#default_value' => $field->label,
    '#required' => TRUE,
    );
  $form['description'] = array(
    '#type' => 'textfield',
    '#title' => t('Description'),
    '#default_value' => $field->description,
    '#description' => t('A brief description of the field, to be displayed on the content submission form.'),
    );

  $form[] = flexinode_invoke('config', $field);

  $form['required'] = array(
    '#type' => 'checkbox',
    '#title' => t('Required field'),
    '#default_value' => $field->required,
    '#description' => t('Whether the user must fill in the field when creating content.'),
    );
  $form['show_teaser'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show in teaser'),
    '#default_value' => $field->show_teaser,
    '#description' => t('Whether this field should be shown as part of the teaser.'),
    );

  $form['weight'] = array(
    '#type' => 'weight',
    '#title' => t('Weight'),
    '#default_value' => $field->weight,
    '#delta' => 10,
    '#description' => t('Optional. On the content editing form, the heavier fields will sink and the lighter fields will be positioned nearer the top.'),
    );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
    );

  $form['ctype_id'] = array(
    '#type' => 'hidden',
    '#value' => $ctype_id,
    );
  $form['field_type'] = array(
    '#type' => 'hidden',
    '#value' => $field->field_type,
    );

  $form['#action'] = url('admin/node/field/'. $field_id);

  if ($field->field_id) {
    $form['delete'] = array(
      '#type' => 'submit',
      '#value' => t('Delete'),
      );
    $form['field_id'] = array(
      '#type' => 'hidden',
      '#value' => $field->field_id,
      );
    $form['ctype_id'] = array(
      '#type' => 'hidden',
      '#value' => $field->ctype_id,
      );
  }

  return drupal_get_form('flexinode_admin_field_form', $form);
}

/**
 * flexinode_admin_field_form hook_submit callback function.
 */
function flexinode_admin_field_form_submit($form_id, $edit) {
  flexinode_admin_save_field($edit);
  drupal_goto('admin/node/types/'. $edit['ctype_id']);
}

/**
 * Builds and returns the field select form.
 */
function flexinode_admin_field_select($ctype_id = NULL) {
  $form = $options = array();

  $form['#action'] = url('admin/node/field/');
  $form['#redirect'] = FALSE;
  $form['field_type'] = array(
    '#type' => 'select',
    '#options' => flexinode_admin_field_select_options(),
    );
  $form['ctype_id'] = array(
    '#type' => 'hidden',
    '#default_value' => $ctype_id,
    );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Add field'),
    );
  return drupal_get_form('flexinode_admin_field_select', $form);
}

/**
 * Build the options for the selectfield.
 **/
function flexinode_admin_field_select_options() {
  foreach (flexinode_field_types() as $field) {
    $options[$field] = t('add %fieldtype', array('%fieldtype' => flexinode_invoke('name', $field)));
  }
  return $options;
}

/**
 * Save a custom field to the database.
 */
function flexinode_admin_save_field($edit) {
  if(is_array($edit['options'])) {
    $options = $edit['options'];
  }
  else {
    $options = array_merge(array(0 => 0), explode('|', $edit['options']));
    unset($options[0]);
  }

  if ($edit['field_id']) {
    $field_id = $edit['field_id'];

    db_query("UPDATE {flexinode_field} SET label = '%s', default_value = '%s', rows = %d, required = %d, show_teaser = %d, show_table = %d, weight = %d, ctype_id = %d, field_type = '%s', options = '%s', description = '%s' WHERE field_id = %d", $edit['label'], $edit['default_value'], $edit['rows'], $edit['required'], $edit['show_teaser'], $edit['show_table'], $edit['weight'], $edit['ctype_id'], $edit['field_type'], serialize($options), $edit['description'], $field_id);

    drupal_set_message(t('updated field "%name".', array('%name' => $edit['label'])));
  }
  else {
    $field_id = db_next_id('{flexinode_field}');

    db_query("INSERT INTO {flexinode_field} (label, default_value, rows, required, show_teaser, show_table, weight, ctype_id, field_type, options, description, field_id) VALUES ('%s', '%s', %d, %d, %d, %d, %d, %d, '%s', '%s', '%s', %d)", $edit['label'], $edit['default_value'], $edit['rows'], $edit['required'], $edit['show_teaser'], $edit['show_table'], $edit['weight'], $edit['ctype_id'], $edit['field_type'], serialize($options), $edit['description'], $field_id);

    drupal_set_message(t('created new field "%name".', array('%name' => $edit['label'])));
  }

  return $field_id;
}

/**
 * Generate a confirmation page prior to deleting a custom field.
 */
function flexinode_admin_confirm_delete_field($field) {
  return confirm_form('delete_field', array(), t('Are you sure you want to delete field "%name"? All data in this field will be lost.', array('%name' => $field->label)), 'admin/node/field/'. $field->field_id);
}

/**
 * Delete a custom field from the database.
 */
function flexinode_admin_delete_field($field) {
  // TODO: Delete files as appropriate.
  db_query('DELETE FROM {flexinode_field} WHERE field_id = %d', $field->field_id);
  db_query('DELETE FROM {flexinode_data} WHERE field_id = %d', $field->field_id);

  drupal_set_message(t('deleted field %name', array('%name' => $field->label)));
}


/**
 * THEME FUNCTIONS
**/
function theme_flexinode_type($type) {
  $output = "<div><fieldset class=\"collapsible\"><legend>". $type->name ."</legend>\n";
  $output .= "  <p class=\"description\">". $type->description ." (". theme('links', $type->links) .")</p>\n";
  $output .= "  <p class=\"fields\">\n";
  $output .= theme('item_list', $type->fieldlist, t('Field list'));
  $output .= $type->controls ."</p>\n";
  $output .= "</fieldset></div>\n";
  return $output;
}