<?php
// $Id: flexinode.module,v 1.77.2.6 2007/02/06 08:37:54 ber Exp $

// Copyright 2003-2004 Jonathan Chaffer. See LICENSE for redistribution allowances.

/**
 * Return a list of all installed field types.
 */
function flexinode_field_types() {
  static $types;

  if (!isset($types)) {
    $types = array();
    $path = drupal_get_path('module', 'flexinode');
    $files = file_scan_directory($path, '^field_.*\.inc$', array('.', '..', 'CVS'), 0, FALSE);
    foreach ($files as $filename => $file) {
      include_once($filename);
      $function = 'flexinode_'. $file->name .'_name';
      if(function_exists($function)) {
        $types[] = substr($file->name, 6);
      }
    }
  }
  return $types;
}

// Load all installed field types.
flexinode_field_types();

/**
 * Implementation of hook_help().
 */
function flexinode_help($section) {
  switch ($section) {
    case 'admin/help#flexinode':
      $output = '<p>'. t('The flexinode module allows administrators to create simple new content types.  Administrators find it very useful to create new types of content without having to program a new content module. This module is the core of flexinode. You will need other modules, such as flexinode_admin, to actually manage the content types.') .'</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>';
    case 'admin/modules#description':
      return t('Manages flexinode content and fields.');
  }

  $output = '';

  if (strpos($section, 'node/add') === 0) {
    foreach (flexinode_content_types() as $type => $name) {
      if ($section == 'node/add#flexinode-' . $type) {
        $ctype = flexinode_load_content_type($type);
        $output .= t($ctype->description);
      }
      if ($section == 'node/add/flexinode-' . $type) {
        $ctype = flexinode_load_content_type($type);
        $output .= t($ctype->help);
      }
    }
  }

  return $output;
}

/**
 * Implementation of hook_perm().
 */
function flexinode_perm() {
  $perms = array('administer content types');
  foreach (flexinode_content_types() as $ctype) {
    $perms[] = 'create '. $ctype->name .' content';
    $perms[] = 'edit own '. $ctype->name .' content';
    $perms[] = 'edit any '. $ctype->name .' content';
  }
  return $perms;
}

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

  if ($may_cache) {
    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
        );
    }
  }

  return $items;
}

/**
 * CORE HOOKS
 */

/**
 * Implementation of hook_node_info().
 */
function flexinode_node_info() {
  $types = array();
  foreach (flexinode_content_types() as $type => $name) {
    $types['flexinode-'. $name->ctype_id] =
              array('name' => $name->name ? t($name->name) : t('flexible content'),
                    'base' => 'flexinode');
  }
  return $types;
}

/**
 * Implementation of hook_access().
 */
function flexinode_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;
    }
  }
}

/**
 * Implementation of hook_load().
 */
function flexinode_load($node) {
  $ctype = flexinode_load_content_type(substr($node->type, 10));

  // build the query
  $fields_to_select = array();
  $table_joins = array();

  foreach ($ctype->fields as $field) {
    $fieldname = 'flexinode_'. $field->field_id;

    if ($field_to_select = flexinode_invoke('db_select', $field)) {
      $fields_to_select[] = $field_to_select;
      $table_joins[] = 'LEFT JOIN {flexinode_data} '. $fieldname .' ON n.nid = '. $fieldname .'.nid AND '. $fieldname .'.field_id = ' . $field->field_id;
    }
  }

  if (count($fields_to_select) > 0) {
    // make the query
    $flexinode = db_fetch_object(db_query('SELECT '. implode(', ', $fields_to_select) .' FROM {node} n '. implode(' ', $table_joins) .' WHERE n.nid = %d', $node->nid));

    // unserialize necessary fields
    foreach ($ctype->fields as $field) {
      $fieldname = 'flexinode_'. $field->field_id;
      $field_data = flexinode_invoke('load', $field, $flexinode);
      if ($field_data) {
        $flexinode->$fieldname = $field_data;
      }
    }
  }

  $flexinode->ctype_id = $ctype->ctype_id;

  return $flexinode;
}

/**
 * Implementation of hook_insert().
 */
function flexinode_insert($node) {
  $ctype = flexinode_load_content_type($node->ctype_id);
  foreach ($ctype->fields as $field) {
    flexinode_invoke('insert', $field, $node);
  }
}

/**
 * Implementation of hook_update().
 */
function flexinode_update($node) {
  $ctype = flexinode_load_content_type($node->ctype_id);
  foreach ($ctype->fields as $field) {
    flexinode_invoke('delete', $field, $node, FALSE);
  }
  db_query('DELETE FROM {flexinode_data} WHERE nid = %d', $node->nid);
  foreach ($ctype->fields as $field) {
    flexinode_invoke('insert', $field, $node);
  }
}

/**
 * Implementation of hook_delete().
 */
function flexinode_delete($node) {
  $ctype = flexinode_load_content_type($node->ctype_id);
  foreach ($ctype->fields as $field) {
    flexinode_invoke('delete', $field, $node, TRUE);
  }
  db_query('DELETE FROM {flexinode_data} WHERE nid = %d', $node->nid);
}

/**
 * Implementation of hook_form().
 */
function flexinode_form(&$node, &$param) {
  // Set form parameters so we can accept file uploads.
  $form['#attributes'] = array('enctype' => 'multipart/form-data');

  if (!isset($node->ctype_id)) {
    $node->ctype_id = substr($node->type, 10);
  }
  $ctype = flexinode_load_content_type($node->ctype_id);

  foreach ($ctype->fields as $field) {
    $items = flexinode_invoke('form', $field, $node);
    if (count($items)) {
      foreach($items as $key => $item) {
        $form[$key] = $item;
      }
    }
  }

  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => t('Title'),
    '#default_value' => $node->title,
    '#size' => 60,
    '#maxlength' => 128,
    '#required' => TRUE,
    );

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

  return $form;
}

/**
 * Implementation of hook_validate().
 */
function flexinode_validate($node) {
  if (isset($node->ctype_id)) {
    $ctype = flexinode_load_content_type($node->ctype_id);
    foreach ($ctype->fields as $field) {
      $fieldname = 'flexinode_'. $field->field_id;
      $validation = flexinode_invoke('validate', $field, $node);
    }
  }
}

/**
 * Implementation of hook_submit().
 */
function flexinode_submit(&$node) {
  if (isset($node->ctype_id)) {
    $ctype = flexinode_load_content_type($node->ctype_id);
    foreach ($ctype->fields as $field) {
      if ($result = flexinode_invoke('execute', $field, $node)) {
        $fieldname = 'flexinode_'. $field->field_id;
        $node->$fieldname = $result;
      }
    }
    // Pre-render the body and teaser fields, so the Drupal search works.
    $node = flexinode_content($node);
  }
}

/**
 * Implementation of hook_view().
 */
function flexinode_view(&$node, $teaser = FALSE, $page = FALSE) {
  $node = flexinode_content($node, $teaser);
}

function flexinode_content($node, $teaser = FALSE) {
  if (isset($node->ctype_id)) {
    $ctype = flexinode_load_content_type($node->ctype_id);

    $node->readmore = FALSE;
    $node->body = '<div class="flexinode-body flexinode-'. $node->ctype_id .'">';
    $node->teaser = '<div class="flexinode-body flexinode-'. $node->ctype_id .'">';

    foreach ($ctype->fields as $field) {
      $fieldname = 'flexinode_'. $field->field_id;
      if (isset($node->$fieldname)) {
        $body_data = flexinode_invoke('format', $field, $node, FALSE);
        $teaser_data = flexinode_invoke('format', $field, $node, TRUE);
        if (!empty($body_data) && $body_data) {
          $node->body .= theme('flexinode_'. $field->field_type, $field->field_id, $field->label, $node->$fieldname, $teaser ? $teaser_data : $body_data);
          if ($field->show_teaser) {
            if ($body_data != $teaser_data) {
              $node->readmore = TRUE;
            }
            $node->teaser .= theme('flexinode_'. $field->field_type, $field->field_id, $field->label, $node->$fieldname, $teaser_data);
          }
          else {
            $node->readmore = TRUE;
          }
        }
      }
    }

    $node->body .= '</div>';
    $node->teaser .= '</div>';

    return $node;
  }
}

/**
 * Implementation of hook_file_download().
 */
function flexinode_file_download($file) {
  if (!$file) return false;
  $result = db_fetch_object(db_query("SELECT f.* FROM {flexinode_data} f WHERE f.textual_data = '%s'", $file));
  if (!$result) return false;
  $filedb = unserialize($result->serialized_data);
  if ($filedb->type) {
    return array('Content-type: '. $filedb->type, 'Content-Disposition: attachment; filename="'. $file .'"');
  }
  if ($path = file_create_path($file)) {
    list($width, $height, $type, $attr) = getimagesize($path);
    $types = array(
      IMAGETYPE_GIF => 'image/gif',
      IMAGETYPE_JPEG => 'image/jpeg',
      IMAGETYPE_PNG => 'image/png',
      IMAGETYPE_SWF => 'application/x-shockwave-flash',
      IMAGETYPE_PSD => 'image/psd',
      IMAGETYPE_BMP => 'image/bmp',
      IMAGETYPE_TIFF_II => 'image/tiff',
      IMAGETYPE_TIFF_MM  => 'image/tiff',
      IMAGETYPE_JPC => 'application/octet-stream',
      IMAGETYPE_JP2 => 'image/jp2',
      IMAGETYPE_JPX => 'application/octet-stream',
      IMAGETYPE_JB2 => 'application/octet-stream',
      IMAGETYPE_SWC => 'application/x-shockwave-flash',
      IMAGETYPE_IFF => 'image/iff',
      IMAGETYPE_WBMP => 'image/vnd.wap.wbmp',
      IMAGETYPE_XBM => 'image/xbm'
    );
    if (isset($types[$type])) {
      return array('Content-type: '. $types[$type], 'Content-Disposition: attachment; filename="'. $file .'"');
    }
    else {
      $type = (function_exists('mime_content_type') ? mime_content_type($path) : 'application/x-download');
      return array('Content-type: '. $type, 'Content-Disposition: attachment; filename="'. $file .'"');
    }
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function flexinode_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'rss item':
      if (isset($node->ctype_id)) {
        $keys = array();
        $ctype = flexinode_load_content_type($node->ctype_id);
        foreach ($ctype->fields as $field) {
          $fieldname = 'flexinode_'. $field->field_id;
          $key = array();
          if (isset($node->$fieldname)) {
            $key = flexinode_invoke('rss', $field, $node);
            if(count($key)) {
              $keys[] = $key;
            }
          }
        }
        return $keys;
      }
      break;
  }
}

/**
 * Implementation of hook_cron().
 */
function flexinode_cron() {
  flexinode_invoke_all('cron');
}

/**
 * FLEXINODE CONTENT TYPE FUNCTIONS
 */

/**
 * Return a list of content types.
 *
 * The returned array contains basic information about each type, but the heavy
 * lifting of loading all field descriptions, for example, is not yet done.
 */
function flexinode_content_types() {
  static $types;
  if (!isset($types)) {
    $types = array();
    $result = db_query('SELECT * FROM {flexinode_type}');
    while ($type = db_fetch_object($result)) {
      $types[$type->ctype_id] = $type;
    }
  }
  return $types;
}

/**
 * Return a content type object.
 */
function flexinode_load_content_type($ctype_id) {
  static $content_types;

  if (isset($content_types[$ctype_id])) {
    return $content_types[$ctype_id];
  }

  $ctype = db_fetch_object(db_query('SELECT * FROM {flexinode_type} WHERE ctype_id = %d', $ctype_id));

  $ctype->fields = array();
  $result = db_query('SELECT * FROM {flexinode_field} WHERE ctype_id = %d ORDER BY weight ASC, label ASC', $ctype_id);
  while ($field = db_fetch_object($result)) {
    $field->options = unserialize($field->options);
    $ctype->fields[] = $field;
  }

  $content_types[$ctype_id] = $ctype;
  return $ctype;
}


/**
 * FLEXINODE FIELD FUNCTIONS
 */

/**
 * Invoke a field hook.
 *
 * Each field type has different behavior, so the differences are separated out
 * into include files which themselves behave much like Drupal modules.
 */
function flexinode_invoke($hook, $field, $a1 = NULL, $a2 = NULL, $a3 = NULL) {
  $type = is_string($field) ? $field : $field->field_type;
  $function = 'flexinode_field_'. $type .'_'. $hook;

  if (function_exists($function)) {
    return ($function($field, $a1, $a2, $a3));
  }
}

/**
 * Invoke a field hook for all field types.
 */
function flexinode_invoke_all($hook, $a1 = NULL, $a2 = NULL, $a3 = NULL) {
  $result = array();
  foreach (flexinode_field_types() as $type) {
    $function = 'flexinode_field_'. $type .'_'. $hook;
    if (function_exists($function)) {
      $result = array_merge($result, $function($a1, $a2, $a3));
    }
  }
  return $result;
}

/**
 * Return a field object.
 */
function flexinode_load_field($field_id) {
  $field = db_fetch_object(db_query('SELECT * FROM {flexinode_field} WHERE field_id = %d', $field_id));
  $field->options = unserialize($field->options);
  return $field;
}
