# glz_custom_fields v1.2.4
# Unlimited, super special custom fields.
#
# Gerhard Lazu
# http://gerhardlazu.com
#
# Contributors: Randy Levine, Sam Weiss, Luca Botti, Manfre, Vladimir Siljkovic, Julian Reisenberger, Steve Dickinson, Stef Dawson, Jean-Pol Dupont
# Minimum requirements: Textpattern 4.2.0

// Including helper files. If we can't have classes, we will use includes


// -------------------------------------------------------------
// messages that will be available throughout this plugin
function glz_custom_fields_gTxt($get, $atts = array()) {
  $lang = array(
    'no_name'           => 'Ooops! <strong>custom set</strong> must have a name',
    'deleted'           => '<strong>{custom_set_name}</strong> was deleted',
    'reset'             => '<strong>{custom_set_name}</strong> was reset',
    'created'           => '<strong>{custom_set_name}</strong> was created',
    'updated'           => '<strong>{custom_set_name}</strong> was updated',
    'exists'            => 'Ooops! <strong>{custom_set_name}</strong> already exists',
    'doesnt_exist'      => 'Ooops! <strong>{custom_set_name}</strong> is not set',
    'field_problems'    => 'Ooops! <strong>{custom_set_name}</strong> has some problems. <a href="?event=glz_custom_fields">Go fix it</a>.',
    'custom_set'        => 'Text Input', # custom sets in TXP 4.2.0 are by type custom_set by default...
    'text_input'        => 'Text Input',
    'select'            => 'Select',
    'multi-select'      => 'Multi-Select',
    'textarea'          => 'Textarea',
    'checkbox'          => 'Checkbox',
    'radio'             => 'Radio',
    'date-picker'       => 'Date Picker',
    'time-picker'       => 'Time Picker',
    'custom-script'     => 'Custom Script',
    'type_not_supported'=> 'Type not supported',
    'no_do'             => 'Ooops! No action specified for method, abort.',
    'not_specified'     => 'Ooops! {what} is not specified',
    'searchby_not_set'  => '<strong>searcby</strong> cannot be left blank',
    'jquery_missing'    => 'Upgrade TXP to at least 4.0.5 or put <strong>jquery.js</strong> in your /textpattern folder. <a href="http://jquery.com" title="jQuery website">jQuery website</a>',
    'check_path'        => 'Make sure all your paths are correct. Check <strong>config.php</strong> and the Admin tab (mainly Advanced).',
    'no_articles_found' => 'No articles with custom fields have been found.',
    'migration_success' => 'Migrating custom fields was successful',
    'migration_skip'    => '<strong>custom_fields</strong> table already has data in it, migration skipped.',
    'search_section_created'  => '<strong>search</strong> section has been created',
    'custom_sets_all_input'   => 'All custom sets have been set back to input',
    'preferences_updated'     => 'Plugin preferences have been updated',
    'not_found'         => 'Ooops! <strong>{file}</strong> cannot be found, check path',
    'not_callable'      => 'Ooops! <strong>{function}()</strong> cannot be called. Ensure <strong>{file}</strong> can be executed.'
  );

  $out = ( strstr($lang[$get], "Ooops!") ) ? // Ooops! would appear 0 in the string...
      "<span class=\"red\">{$lang[$get]}</span>" :
      $lang[$get];

  return strtr($out, $atts);
}






// -------------------------------------------------------------
// I would do this through a factory class, but some folks are still running PHP4...
function glz_custom_fields_MySQL($do, $name='', $table='', $extra='') {
  if ( !empty($do) ) {
    switch ( $do ) {
      case 'all':
        return glz_all_custom_sets();
        break;

      case 'values':
        return glz_values_custom_field($name, $extra);
        break;

      case 'all_values' :
        return glz_all_existing_custom_values($name, $extra);
        break;

      case 'article_customs':
        return glz_article_custom_fields($name, $extra);
        break;

      case 'next_custom':
        return glz_next_empty_custom();
        break;

      case 'new':
        glz_new_custom_field($name, $table, $extra);
        glz_custom_fields_update_count();
        break;

      case 'update':
        return glz_update_custom_field($name, $table, $extra);
        break;

      case 'reset':
        return glz_reset_custom_field($name, $table, $extra);
        break;

      case 'delete':
        glz_delete_custom_field($name, $table);
        glz_custom_fields_update_count();
        break;

      case 'check_migration':
        return glz_check_migration();
        break;

      case 'mark_migration':
        return glz_mark_migration();
        break;

      case 'custom_set_exists':
        return glz_check_custom_set_exists($name);
        break;

      case 'plugin_preferences':
        return glz_plugin_preferences($name);
        break;

      case 'update_plugin_preferences':
        return glz_update_plugin_preferences($name);
        break;
    }
  }
  else
    trigger_error(glz_custom_fields_gTxt('no_do'));
}


function glz_all_custom_sets() {
  $all_custom_sets = getRows("
    SELECT
      `name` AS custom_set,
      `val` AS name,
      `position`,
      `html` AS type
    FROM
      `".PFX."txp_prefs`
    WHERE
      `event`='custom'
    ORDER BY
      `position`
  ");

  foreach ( $all_custom_sets as $custom_set ) {
    $out[$custom_set['custom_set']] = array(
      'name'      => $custom_set['name'],
      'position'  => $custom_set['position'],
      'type'      => $custom_set['type']
    );
  }

  return $out;
}


function glz_values_custom_field($name, $extra) {
  global $prefs;

  if ( is_array($extra) ) {
    extract($extra);

    if ( !empty($name) ) {
      switch ( $prefs['values_ordering'] ) {
        case "ascending":
          $orderby = "value ASC";
          break;
        case "descending":
          $orderby = "value DESC";
          break;
        default:
          $orderby = "id ASC";
      }


      $arr_values = getThings("
        SELECT
          `value`
        FROM
          `".PFX."custom_fields`
        WHERE
          `name` = '{$name}'
        ORDER BY
          {$orderby}
      ");

      if ( count($arr_values) > 0 ) {
        // decode all special characters e.g. ", & etc. and use them for keys
        foreach ( $arr_values as $key => $value )
          $arr_values_formatted[glz_return_clean_default(htmlspecialchars($value))] = stripslashes($value);

        // if this is a range, format ranges accordingly
        return glz_format_ranges($arr_values_formatted, $custom_set_name);
      }
    }
  }
  else
    trigger_error(glz_custom_fields_gTxt('not_specified', array('{what}' => "extra attributes")));
}


function glz_all_existing_custom_values($name, $extra) {
  if ( is_array($extra) ) {
    extract(lAtts(array(
      'custom_set_name'   => "",
      'status'            => 4
    ),$extra));

    // we might want to check the custom field values for all articles - think initial migration
    $status_condition = ($status == 0) ? "<> ''" : "= '$status'";

    if ( !empty($name) ) {
      $arr_values = getThings("
        SELECT DISTINCT
          `$name`
        FROM
          `".PFX."textpattern`
        WHERE
          `Status` $status_condition
        AND
          `$name` <> ''
        ORDER BY
          `$name`
        ");

      // trim all values
      foreach ( $arr_values as $key => $value )
        $arr_values[$key] = trim($value);

      // DEBUG
      // dmp($arr_values);

      // prepare our array for checking. We need a single string to check for | instances - seems quickest.
      $values_check = join('::', $arr_values);

      // DEBUG
      // dmp($values_check);

      // check if some of the values are multiple ones
      if ( strstr($values_check, '|') ) {
        // initialize $out
        $out = array();
        // put all values in an array
        foreach ( $arr_values as $value ) {
          $arr_values = explode('|', $value);
          $out = array_merge($out, $arr_values);
        }
        // keep only the unique ones
        $out = array_unique($out);
        // keys and values need to be the same
        $out = php4_array_combine($out, $out);
      }

      // check if this is a range
      else if ( strstr($values_check, '-') && strstr($custom_set_name, 'range') )
        // keys won't have the unit ($, £, m<sup>3</sup>, etc.) values will
        $out = glz_format_ranges($arr_values, $custom_set_name);
      else
        // keys and values need to be the same
        $out = php4_array_combine($arr_values, $arr_values);

      // calling stripslashes on all array values
      array_map('glz_array_stripslashes', $out);

      return $out;
    }
  }
  else
    trigger_error(glz_custom_fields_gTxt('not_specified', array('{what}' => "extra attributes")));
}


function glz_article_custom_fields($name, $extra) {
  if ( is_array($extra) ) {
    // see what custom fields we need to query for
    foreach ( $extra as $custom => $custom_set )
      $select[] = glz_custom_number($custom);

    // prepare the select elements
    $select = implode(',', $select);

    $arr_article_customs = getRow("
      SELECT
        $select
      FROM
        `".PFX."textpattern`
      WHERE
        `ID`='$name'
    ");

    return $arr_article_customs;
  }
  else
    trigger_error(glz_custom_fields_gTxt('not_specified', array('{what}' => "extra attributes")));
}


function glz_new_custom_field($name, $table, $extra) {
  if ( is_array($extra) ) {
    extract($extra);
    // DRYing up, we'll be using this variable quiet often
    $custom_set = ( isset($custom_field_number) ) ?
      "custom_{$custom_field_number}_set" :
      $custom_set;

    if ( ($table == PFX."txp_prefs") ) {
      // if this is a new field without a position, use the $custom_field_number
      if (empty($custom_set_position)) $custom_set_position = $custom_field_number;
      $query = "
        INSERT INTO
          `".PFX."txp_prefs` (`prefs_id`, `name`, `val`, `type`, `event`, `html`, `position`)
        VALUES
          ('1', '{$custom_set}', '{$name}', '1', 'custom', '{$custom_set_type}', {$custom_set_position})
      ";
    }
    else if ( $table == PFX."txp_lang" ) {
      $query = "
        INSERT INTO
          `".PFX."txp_lang` (`id`,`lang`,`name`,`event`,`data`,`lastmod`)
        VALUES
          ('','{$lang}','{$custom_set}','prefs','{$name}',now())
      ";
    }
    else if ( $table == PFX."textpattern" ) {
      $column_type = ( $custom_set_type == "textarea" ) ? "TEXT" : "VARCHAR(255)";
      $query = "
        ALTER TABLE
          `".PFX."textpattern`
        ADD
          `custom_{$custom_field_number}` {$column_type} NOT NULL DEFAULT ''
      ";
    }
    else if ( $table == PFX."custom_fields" ) {
      $arr_values = array_unique(array_filter(explode("\r\n", $value), 'glz_arr_empty_values'));

      if ( is_array($arr_values) && !empty($arr_values) ) {
        $size_arr_values = count($arr_values);
        $insert = '';
        foreach ( $arr_values as $key => $value ) {
          // don't insert empty values
          if ( !empty($value) )
            // make sure special characters are escaped before inserting them in the database
            $value = addslashes(addslashes(trim($value)));
            // if this is the last value, query will have to be different
            $insert .= ($key+1 != $size_arr_values ) ?
              "('{$custom_set}','{$value}'), " :
              "('{$custom_set}','{$value}')";
        }
        $query = "
          INSERT INTO
            `".PFX."custom_fields` (`name`,`value`)
          VALUES
            {$insert}
        ";
      }
    }
    if ( isset($query) && !empty($query) )
      safe_query($query);
  }
  else
    trigger_error(glz_custom_fields_gTxt('not_specified', array('{what}' => "extra attributes")));
}


function glz_update_custom_field($name, $table, $extra) {
  if ( is_array($extra) )
    extract($extra);

  if ( ($table == PFX."txp_prefs") ) {
    safe_query("
      UPDATE
        `".PFX."txp_prefs`
      SET
        `val` = '{$custom_set_name}',
        `html` = '{$custom_set_type}',
        `position` = '{$custom_set_position}'
      WHERE
        `name`='{$name}'
    ");
  }
  else if ( ($table == PFX."textpattern") ) {
    $column_type = ( $custom_set_type == "textarea" ) ? "TEXT" : "VARCHAR(255)";
    safe_query("
      ALTER TABLE
        `".PFX."textpattern`
      MODIFY
        `{$custom_field}` {$column_type} NOT NULL DEFAULT ''
    ");
  }
}


function glz_reset_custom_field($name, $table, $extra) {
  if ( is_array($extra) )
    extract($extra);

  if ( $table == PFX."txp_prefs" ) {
    safe_query("
      UPDATE
        `".PFX."txp_prefs`
      SET
        `val` = '',
        `html` = 'text_input'
      WHERE
        `name`='{$name}'
    ");
  }
  else if ( $table == PFX."textpattern" ) {
    safe_query("UPDATE `".PFX."textpattern` SET `{$name}` = ''");
    safe_query("ALTER TABLE `".PFX."textpattern` MODIFY `{$custom_field}` VARCHAR(255) NOT NULL DEFAULT ''");
  }
}


function glz_delete_custom_field($name, $table) {
  // remember, custom fields under 10 MUST NOT be deleted
  if ( glz_custom_digit($name) > 10 ) {
    if ( in_array($table, array(PFX."txp_prefs", PFX."txp_lang", PFX."custom_fields")) ) {
      $query = "
        DELETE FROM
          `{$table}`
        WHERE
          `name`='{$name}'
      ";
    }
    else if ( $table == PFX."textpattern" ) {
      $query = "
        ALTER TABLE
          `".PFX."textpattern`
        DROP
          `{$name}`
      ";
    }
    safe_query($query);
  }
  else {
    if ( $table == PFX."txp_prefs" )
      glz_custom_fields_MySQL("reset", $name, $table);
    else if ( ($table == PFX."custom_fields") ) {
      safe_query("
        DELETE FROM
          `{$table}`
        WHERE
          `name`='{$name}'
      ");
    }
  }
}


// -------------------------------------------------------------
// checks if custom_fields table has any values in it
function glz_check_migration() {
  return getThing("
    SELECT
      COUNT(*)
    FROM
      `".PFX."custom_fields`
  ");
}


// -------------------------------------------------------------
// make a note of glz_custom_fields migration in txp_prefs
function glz_mark_migration() {
  set_pref("migrated", "1", "glz_custom_f");
}


// -------------------------------------------------------------
// check if one of the special custom fields exists
function glz_check_custom_set_exists($name) {
  if ( !empty($name) ) {
    return getThing("
      SELECT
        `name`, `val`
      FROM
        `".PFX."txp_prefs`
      WHERE
        `html` = '{$name}'
      AND
        `name` LIKE 'custom_%'
      ORDER BY
        `name`
    ");
  }
}


// -------------------------------------------------------------
// updates max_custom_fields
function glz_custom_fields_update_count() {
  set_pref('max_custom_fields', safe_count("txp_prefs", "event='custom'"));
}

// -------------------------------------------------------------
// returns all plugin preferences
function glz_plugin_preferences($arr_preferences) {
	$r = safe_rows_start('name, val', 'txp_prefs', "event = 'glz_custom_f'");
	if ($r) {
		while ($a = nextRow($r)) {
			$out[$a['name']] = stripslashes($a['val']);
		}
	}
  return $out;
}

// -------------------------------------------------------------
// updates all plugin preferences
function glz_update_plugin_preferences($arr_preferences) {
  // die(dmp($arr_preferences));
  foreach ($arr_preferences as $preference => $value) {
    // if there is a preference which doesn't exist, create it
    if ( !isset($prefs[$preference]) )
      set_pref($preference, addslashes(addslashes(trim($value))), "glz_custom_f", 10); // 10 so that it won't appear under TXP's prefs tab
  }
}






// this function doesn't seem to be currently used, remove or make it useful
function glz_next_empty_custom() {
  global $all_custom_sets;

  foreach ( $all_custom_sets as $custom => $custom_set ) {
    if ( empty($custom_set['name']) )
      return $custom;
  }
}


// -------------------------------------------------------------
// edit/delete buttons in custom_fields table require a form each
function glz_form_buttons($action, $value, $custom_set, $custom_set_name, $custom_set_type, $custom_set_position, $onsubmit='') {
  $onsubmit = ($onsubmit) ?
    'onsubmit="'.$onsubmit.'"' :
    '';

  return
    '<form method="post" action="index.php" '.$onsubmit.'>
      <input name="custom_set" value="'.$custom_set.'" type="hidden" />
      <input name="custom_set_name" value="'.$custom_set_name.'" type="hidden" />
      <input name="custom_set_type" value="'.$custom_set_type.'" type="hidden" />
      <input name="custom_set_position" value="'.$custom_set_position.'" type="hidden" />
      <input name="event" value="glz_custom_fields" type="hidden" />
      <input name="'.$action.'" value="'.$value.'" type="submit" />
    </form>';
}


// -------------------------------------------------------------
// the types our custom fields can take
function glz_custom_set_types() {
  return array(
    'normal' => array(
      'text_input',
      'checkbox',
      'radio',
      'select',
      'multi-select',
      'textarea'),
    'special' => array(
      'date-picker',
      'time-picker',
      'custom-script')
  );
}


// -------------------------------------------------------------
// outputs only custom fields that have been set, i.e. have a name assigned to them
function glz_check_custom_set($all_custom_sets, $step) {
  $out = array();

  foreach ($all_custom_sets as $key => $custom_field) {
    if (!empty($custom_field['name'])) {
      if ( ($step == "excerpt") && ($custom_field['type'] == "textarea") )
        $out[$key] = $custom_field;
      else if ( ($step == "custom_fields") && ($custom_field['type'] != "textarea") ) {
        $out[$key] = $custom_field;
      }
    }
  }

  return $out;
}


// -------------------------------------------------------------
// removes { } from values which are marked as default
function glz_return_clean_default($value) {
  $pattern = "/^.*\{(.*)\}.*/";

  return preg_replace($pattern, "$1", $value);
}


// -------------------------------------------------------------
// return our default value from all custom_field values
function glz_default_value($all_values) {
  if ( is_array($all_values) ) {
    preg_match("/(\{.*\})/", join(" ", $all_values), $default);
    return ( (!empty($default) && $default[0]) ? $default[0] : '');
  }
}


// -------------------------------------------------------------
// calling the above function in an array context
function glz_clean_default_array_values(&$value) {
  $value = glz_return_clean_default($value);
}


// -------------------------------------------------------------
// custom_set without "_set" e.g. custom_1_set => custom_1
// or custom set formatted for IDs e.g. custom-1
function glz_custom_number($custom_set, $delimiter="_") {
  $custom_field = substr($custom_set, 0, -4);

  if ($delimiter != "_")
    $custom_field = str_replace("_", $delimiter, $custom_field);

  return $custom_field;
}


// -------------------------------------------------------------
// custom_set digit e.g. custom_1_set => 1
function glz_custom_digit($custom_set) {
  $out = explode("_", $custom_set);
  // $out[0] will always be custom
  return $out[1];
}


// -------------------------------------------------------------
// removes empty values from arrays - used for new custom fields
function glz_arr_empty_values($value) {
  if ( !empty($value) )
    return $value;
}


// -------------------------------------------------------------
// returns the custom set from a custom set name e.g. "Rating" gives us custom_1_set
function glz_get_custom_set($value) {
  global $all_custom_sets;

  // go through all custom fields and see if the one we're looking for exists
  foreach ( $all_custom_sets as $custom => $custom_set ) {
    if ( $custom_set['name'] == $value )
      return $custom;
  }
  // if it doesn't, return error message
  trigger_error(glz_custom_fields_gTxt('doesnt_exist', array('{custom_set_name}' => $value)));
}


// -------------------------------------------------------------
// get the article ID, EVEN IF it's newly saved
function glz_get_article_id() {
  return ( !empty($GLOBALS['ID']) ?
    $GLOBALS['ID'] :
    gps('ID') );
}


// -------------------------------------------------------------
// helps with range formatting - just DRY
function glz_format_ranges($arr_values, $custom_set_name) {
  //initialize $out
  $out = '';
  foreach ( $arr_values as $key => $value ) {
    $out[$key] = ( strstr($custom_set_name, 'range') ) ?
      glz_custom_fields_range($value, $custom_set_name) :
      $value;
  }
  return $out;
}


// -------------------------------------------------------------
// acts as a callback for the above function
function glz_custom_fields_range($custom_value, $custom_set_name) {
  // last part of the string will be the range unit (e.g. $, &pound;, m<sup>3</sup> etc.)
  $nomenclature = array_pop(explode(' ', $custom_set_name));

  // see whether range unit should go after
  if ( strstr($nomenclature, '(after)') ) {
    // trim '(after)' from the measuring unit
    $nomenclature = substr($nomenclature, 0, -7);
    $after = 1;
  }

  // check whether it's a range or single value
  $arr_value = explode('-', $custom_value);
  if ( is_array($arr_value) ) {
    // initialize $out
    $out = '';
    foreach ( $arr_value as $value ) {
      // check whether nomenclature goes before or after
      $out[] = ( !isset($after) ) ?
        $nomenclature.number_format($value) :
        number_format($value).$nomenclature;
    }
    return implode('-', $out);
  }
  // our range is a single value
  else {
    // check whether nomenclature goes before or after
    return ( !isset($after) ) ?
      $nomenclature.number_format($value) :
      number_format($value).$nomenclature;
  }
}


// -------------------------------------------------------------
// returns the next available number for custom set
function glz_custom_next($arr_custom_sets) {
  $arr_extra_custom_sets = array();
  foreach ( array_keys($arr_custom_sets) as $extra_custom_set) {
    $arr_extra_custom_sets[] = glz_custom_digit($extra_custom_set);
  }
  // order the array
  sort($arr_extra_custom_sets);

  for ( $i=0; $i < count($arr_extra_custom_sets); $i++ ) {
    if ($arr_extra_custom_sets[$i] > $i+1)
      return $i+1;
  }

  return count($arr_extra_custom_sets)+1;
}


// -------------------------------------------------------------
// checks if the custom field name isn't already taken
function glz_check_custom_set_name($arr_custom_fields, $custom_set_name, $custom_set='') {
  foreach ( $arr_custom_fields as $custom => $arr_custom_set ) {
    if ( ($custom_set_name == $arr_custom_set['name']) && (!empty($custom_set) && $custom_set != $custom) )
      return TRUE;
  }

  return FALSE;
}


// -------------------------------------------------------------
// formats the custom set output based on its type
function glz_format_custom_set_by_type($custom, $custom_id, $custom_set_type, $arr_custom_field_values, $custom_value = "", $default_value = "") {
  if ( is_array($arr_custom_field_values) )
    $arr_custom_field_values = array_map('glz_array_stripslashes', $arr_custom_field_values);

  switch ( $custom_set_type ) {
    // these are the normal custom fields
    case "text_input":
      return array(
        fInput("text", $custom, $custom_value, "edit", "", "", "22", "", $custom_id),
        'glz_custom_field'
      );

    case "select":
      return array(
        glz_selectInput($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value),
        'glz_custom_select_field'
      );

    case "multi-select":
      return array(
        glz_selectInput($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value, 1),
        'glz_custom_multi-select_field'
      );

    case "checkbox":
      return array(
        glz_checkbox($custom, $arr_custom_field_values, $custom_value, $default_value),
        'glz_custom_checkbox_field'
      );

    case "radio":
      return array(
        glz_radio($custom, $custom_id, $arr_custom_field_values, $custom_value, $default_value),
        'glz_custom_radio_field'
      );

    case "textarea":
      return array(
        text_area($custom, 100, 500, $custom_value, $custom_id),
        'glz_text_area_field'
      );

    // here start the special custom fields, might need to refactor the return, starting to repeat itself
    case "date-picker":
      return array(
        fInput("text", $custom, $custom_value, "edit date-picker", "", "", "22", "", $custom_id),
        'glz_custom_date-picker_field clearfix'
      );
    
    case "time-picker":
      return array(
        fInput("text", $custom, $custom_value, "edit time-picker", "", "", "22", "", $custom_id),
        'glz_custom_time-picker_field'
      );
    
    case "custom-script":
      return array(
        glz_custom_script(reset($arr_custom_field_values), $custom, $custom_id, $custom_value),
        'glz_custom_field_script'
      );

    // a type has been passed that is not supported yet
    default:
      return array(
        glz_custom_fields_gTxt('type_not_supported'),
        'glz_custom_field'
      );
  }
}


// -------------------------------------------------------------
// had to duplicate the default selectInput() because trimming \t and \n didn't work + some other mods & multi-select
function glz_selectInput($name = '', $id = '', $arr_values = '', $custom_value = '', $default_value = '', $multi = '') {
  if ( is_array($arr_values) ) {
    global $prefs;
    $out = array();

    // if there is no custom_value coming from the article, let's use our default one
    if ( empty($custom_value) )
      $custom_value = $default_value;

    foreach ($arr_values as $key => $value) {
      $selected = glz_selected_checked('selected', $key, $custom_value, $default_value);
      $out[] = "<option value=\"$key\"{$selected}>$value</option>";
    }

    // we'll need the extra attributes as well as a name that will produce an array
    if ($multi) {
      $multi = ' multiple="multiple" size="'.$prefs['multiselect_size'].'"';
      $name .= "[]";
    }

    return "<select id=\"".glz_idify($id)."\" name=\"$name\" class=\"list\"$multi>".
      ($default_value ? '' : "<option value=\"\"$selected>&nbsp;</option>").
      ( $out ? join('', $out) : '').
      "</select>";
  }
  else
    return glz_custom_fields_gTxt('field_problems', array('{custom_set_name}' => $name));
}


// -------------------------------------------------------------
// had to duplicate the default checkbox() to keep the looping in here and check against existing value/s
function glz_checkbox($name = '', $arr_values = '', $custom_value = '', $default_value = '') {
  if ( is_array($arr_values) ) {
    $out = array();

    // if there is no custom_value coming from the article, let's use our default one
    if ( empty($custom_value) )
      $custom_value = $default_value;

    foreach ( $arr_values as $key => $value ) {
      $checked = glz_selected_checked('checked', $key, $custom_value);

      // Putting an additional span around the input and label combination so the two can be floated together as a pair for left-right, left-right,... arrangement of checkboxes and radio buttons. Thanks Julian!
      $out[] = "<span><input type=\"checkbox\" name=\"{$name}[]\" value=\"$key\" class=\"checkbox\" id=\"".glz_idify($key)."\"{$checked} /><label for=\"".glz_idify($key)."\">$value</label></span><br />";
    }

    return join('', $out);
  }
  else
    return glz_custom_fields_gTxt('field_problems', array('{custom_set_name}' => $name));
}


// -------------------------------------------------------------
// had to duplicate the default radio() to keep the looping in here and check against existing value/s
function glz_radio($name = '', $id = '', $arr_values = '', $custom_value = '', $default_value = '') {
  if ( is_array($arr_values) ) {
    $out = array();

    // if there is no custom_value coming from the article, let's use our default one
    if ( empty($custom_value) )
      $custom_value = $default_value;

    foreach ( $arr_values as $key => $value ) {
      $checked = glz_selected_checked('checked', $key, $custom_value);

      // Putting an additional span around the input and label combination so the two can be floated together as a pair for left-right, left-right,... arrangement of checkboxes and radio buttons. Thanks Julian!
      $out[] = "<span><input type=\"radio\" name=\"$name\" value=\"$key\" class=\"radio\" id=\"{$id}_".glz_idify($key)."\"{$checked} /><label for=\"{$id}_".glz_idify($key)."\">$value</label></span><br />";
    }

    return join('', $out);
  }
  else
    return glz_custom_fields_gTxt('field_problems', array('{custom_set_name}' => $name));
}


// -------------------------------------------------------------
// checking if this custom field has selected or checked values
function glz_selected_checked($nomenclature, $value, $custom_value = '') {
  // we're comparing against a key which is a "clean" value
  $custom_value = htmlspecialchars($custom_value);

  // make an array if $custom_value contains multiple values
  if ( strpos($custom_value, '|') )
    $arr_custom_value = explode('|', $custom_value);

  if ( isset($arr_custom_value) )
    $out = ( in_array($value, $arr_custom_value) ) ? " $nomenclature=\"$nomenclature\"" : "";
  else
    $out = ($value == $custom_value) ? " $nomenclature=\"$nomenclature\"" : "";

  return $out;
}


//-------------------------------------------------------------
// button gets more consistent styling across browsers rather than input type="submit"
// included in this plugin until in makes it into TXP - if that ever happens...
function glz_fButton($type, $name, $contents='Submit', $value, $class='', $id='', $title='', $onClick='', $disabled = false) {
  $o  = '<button type="'.$type.'" name="'.$name.'"';
  $o .= ' value="'.htmlspecialchars($value).'"';
  $o .= ($class)    ? ' class="'.$class.'"' : '';
  $o .= ($id)       ? ' id="'.$id.'"' : '';
  $o .= ($title)    ? ' title="'.$title.'"' : '';
  $o .= ($onClick)  ? ' onclick="'.$onClick.'"' : '';
  $o .= ($disabled) ? ' disabled="disabled"' : '';
  $o .= '>';
  $o .= $contents;
  $o .= '</button>';
  return $o;
}


//-------------------------------------------------------------
// evals a PHP script and displays output right under the custom field label
function glz_custom_script($script, $custom, $custom_id, $custom_value) {
  if ( is_file($script) ) {
    include($script);
    $custom_function = basename($script, ".php");
    if ( is_callable($custom_function) ) {
      return call_user_func_array($custom_function, array($custom, $custom_id, $custom_value));
    }
    else
      return glz_custom_fields_gTxt('not_callable', array('{function}' => $custom_function, '{file}' => $script));
  }
  else
    return glz_custom_fields_gTxt('not_found', array('{file}' => $script));

}


// -------------------------------------------------------------
// PHP4 doesn't come with array_combine... Thank you redbot!
function php4_array_combine($keys, $values) {
  $result = array(); // initializing the array

  foreach ( array_map(null, $keys, $values) as $pair ) {
    $result[$pair[0]] = $pair[1];
  }

  return $result;
}


// -------------------------------------------------------------
// converts all values into id safe ones
function glz_idify($value) {
  $patterns[0] = "/\s/";
  $replacements[0] = "-";
  $patterns[1] = "/[^a-zA-Z0-9\-]/";
  $replacements[1] = "";

  return preg_replace($patterns, $replacements, strtolower($value));
}


// -------------------------------------------------------------
// strips slashes in arrays, used in conjuction with e.g. array_map
function glz_array_stripslashes(&$value) {
  return stripslashes($value);
}


// -------------------------------------------------------------
// returns all sections/categories that are searchable
function glz_all_searchable_sections_categories($type) {
  $type = (in_array($type, array('category', 'section')) ? $type : 'section');
  $condition = "";

  if ( $type == "section" )
    $condition .= "searchable='1'";
  else
    $condition .= "name <> 'root' AND type='article'";

  $result = safe_rows('*', "txp_{$type}", $condition);

  $out = array();
  foreach ($result as $value) {
    $out[$value['name']] = $value['title'];
  }

  return $out;
}

// -------------------------------------------------------------
// will leave only [A-Za-z0-9 ] in the string
function glz_clean_string($string) {
  if ($string)
    return preg_replace('/[^A-Za-z0-9\s\_\-]/', '', $string);
}





// -------------------------------------------------------------
// replaces the default custom fields under write tab
function glz_custom_fields_replace($event, $step, $data, $rs) {
  global $all_custom_sets, $date_picker;
  // get all custom fields & keep only the ones which are set, filter by step
  $arr_custom_fields = glz_check_custom_set($all_custom_sets, $step);

  // DEBUG
  // dmp($arr_custom_fields);

  $out = ' ';

  if ( is_array($arr_custom_fields) && !empty($arr_custom_fields) ) {
    // get all custom fields values for this article
    $arr_article_customs = glz_custom_fields_MySQL("article_customs", glz_get_article_id(), '', $arr_custom_fields);

    // DEBUG
    // dmp($arr_article_customs);

    if ( is_array($arr_article_customs) )
      extract($arr_article_customs);

    // let's see which custom fields are set
    foreach ( $arr_custom_fields as $custom => $custom_set ) {
      // get all possible/default value(s) for this custom set from custom_fields table
      $arr_custom_field_values = glz_custom_fields_MySQL("values", $custom, '', array('custom_set_name' => $custom_set['name']));

      // DEBUG
      // dmp($arr_custom_field_values);

      //custom_set formatted for id e.g. custom_1_set => custom-1 - don't ask...
      $custom_id = glz_custom_number($custom, "-");
      //custom_set without "_set" e.g. custom_1_set => custom_1
      $custom = glz_custom_number($custom);

      // if current article holds no value for this custom field and we have no default value, make it empty
      $custom_value = (!empty($$custom) ? $$custom : '');
      // DEBUG
      // dmp("custom_value: {$custom_value}");

      // check if there is a default value
      // if there is, strip the { }
      $default_value = glz_return_clean_default(glz_default_value($arr_custom_field_values));
      // DEBUG
      // dmp("default_value: {$default_value}");

      // now that we've found our default, we need to clean our custom_field values
      if (is_array($arr_custom_field_values))
        array_walk($arr_custom_field_values, "glz_clean_default_array_values");

      // DEBUG
      // dmp($arr_custom_field_values);

      // the way our custom field value is going to look like
      list($custom_set_value, $custom_class) = glz_format_custom_set_by_type($custom, $custom_id, $custom_set['type'], $arr_custom_field_values, $custom_value, $default_value);

      // DEBUG
      // dmp($custom_set_value);

      $out .= graf(
        "<label for=\"$custom_id\">{$custom_set['name']}</label><br />$custom_set_value", " class=\"$custom_class\""
      );
    }
  }

  // DEBUG
  // dmp($out);

  // if we're writing textarea custom fields, we need to include the excerpt as well
  if ($step == "excerpt")
    $out = $data.$out;

  return $out;
}


// -------------------------------------------------------------
// prep our custom fields for the db (watch out for multi-selects, checkboxes & radios, they might have multiple values)
function glz_custom_fields_before_save() {
  // keep only the custom fields
  foreach ($_POST as $key => $value) {
    //check for custom fields with multiple values e.g. arrays
    if ( strstr($key, 'custom_') && is_array($value) ) {
      $value = implode($value, '|');
      // feed our custom fields back into the $_POST
      $_POST[$key] = $value;
    }
  }
  // DEBUG
  // dmp($_POST);
}


// -------------------------------------------------------------
// adds the css & js we need
function glz_custom_fields_css_js() {
  global $glz_notice, $date_picker, $time_picker, $prefs;

  // here come our custom stylesheetz
  $css = <<<EOF
<style type="text/css" media="all">
/* - - - - - - - - - - - - - - - - - - - - -

### TEXTPATTERN CUSTOM FIELDS ###

Title : glz_custom_fields stylesheet
Author : Gerhard Lazu
URL : http://www.gerhardlazu.com/

Created : 14th May 2007
Last modified: 28th September 2009

- - - - - - - - - - - - - - - - - - - - - */

.clearfix:after {
  content: ".";
  display: block;
  clear: both;
  visibility: hidden;
  line-height: 0;
  height: 0;
}

.clearfix {
  display: inline-block;
}

html[xmlns] .clearfix {
  display: block;
}

* html .clearfix {
  height: 1%;
}

/* CLASSES
-------------------------------------------------------------- */
.green {
  color: #6B3;
}
.red {
  color: #C00;
}
.grey {
  color: #777;
}
.left {
  float: left;
}
.right {
  float: right;
}


/* TABLE
-------------------------------------------------------------- */
.glz_custom_fields {
  margin: 0 auto;
  border: 1px solid #DDD;
  width: 600px;
}

.glz_custom_fields thead tr {
  font-size: 1.1em;
  font-weight: 700;
  background: #DDD;
}

.glz_custom_fields tbody tr.alt {
  background: #EEE;
}

.glz_custom_fields td {
  padding: 2px 10px;
  vertical-align: middle;
}
.glz_custom_fields thead td {
  padding: 2px 8px 3px 8px;
}
.glz_custom_fields td.custom_set_position {
  text-align: right;
  width: 10%;
}
.glz_custom_fields td.custom_set_name {
  width: 30%;
}
.glz_custom_fields td.type {
  width: 30%;
}
.glz_custom_fields td.events {
  width: 30%;
  text-align: right;
}

.glz_custom_fields_prefs {
  width: 500px;
}
.glz_custom_fields_prefs#list tr th {
  font-weight: normal;
  text-align: right;
  vertical-align: top;
  width: 50%;
}
.glz_custom_fields_prefs#list tr.heading td {
  padding-top: 30px;
}
.glz_custom_fields_prefs#list tr.heading td h2 {
  margin-top: 0
}
.glz_custom_fields_prefs tr td select,
.glz_custom_fields_prefs tr td input {
  width: 100%;
}
.glz_custom_fields_prefs tr td input.publish {
  margin-left: 51%;
  width: auto;
}



/* FORMS
-------------------------------------------------------------- */
.glz_custom_fields td.events form {
  display: inline;
}

#add_edit_custom_field {
  width: 600px;
  margin: 2em auto 0 auto;
}
#add_edit_custom_field fieldset {
  padding: 10px;
}
#add_edit_custom_field legend {
  font-size: 1.1em;
  font-weight: 700;
}
#add_edit_custom_field label {
  width: 20%;
  font-weight: 700;
}
#add_edit_custom_field p input,
#add_edit_custom_field p select {
  width: 50%;
}
#add_edit_custom_field p textarea {
  font-size: 0.9em;
  padding: 1px 0 1px 3px;
  width: 50%; height: 100px;
}
#add_edit_custom_field p span {
  padding-top: 3px;
  width: 27%;
}
#add_edit_custom_field p em,
.glz_custom_fields_prefs tr em {
  font-size: 0.9em;
  font-weight: 500;
}
#add_edit_custom_field input.publish {
  margin-left: 117px;
}

/* select on write tab for the custom fields */
td#article-col-1 #advanced p select.list {
  width: 100%;
}

td#article-col-1 #advanced p input.radio,
td#article-col-1 #advanced p input.checkbox {
  width: auto;
}
</style>
EOF;
  // and here come our javascriptz
  $js = '';
  $date_picker_js = '';
  if ( $date_picker ) {
    $css .= '<link rel="stylesheet" type="text/css" media="screen" href="'.$prefs['datepicker_url'].'datePicker.css" />'.n;
    foreach (array('date.js', 'datePicker.js') as $file) {
      $js .= '<script type="text/javascript" src="'.$prefs['datepicker_url'].$file.'"></script>'.n;
    }
    $date_picker_js = <<<EOF
try {
  Date.firstDayOfWeek = {$prefs['datepicker_first_day']};
  Date.format = '{$prefs['datepicker_format']}';
  Date.fullYearStart = '19';
  $(".date-picker").datePicker({startDate:'{$prefs['datepicker_start_date']}'});
} catch(err) {
  $('#messagepane').html('<a href="http://{$prefs['siteurl']}/textpattern/?event=plugin_prefs.glz_custom_fields">Fix glz_custom_fields configuration</a>');
}
EOF;
  }
  $time_picker_js = '';
  if ( $time_picker ) {
    $css .= '<link rel="stylesheet" type="text/css" media="screen" href="'.$prefs['timepicker_url'].'timePicker.css" />'.n;
    $js .= '<script type="text/javascript" src="'.$prefs['timepicker_url'].'timePicker.js"></script>'.n;
    $time_picker_js = <<<EOF
try {
  $(".time-picker").timePicker({
    startTime:'{$prefs['timepicker_start_time']}',
    endTime: '{$prefs['timepicker_end_time']}',
    step: {$prefs['timepicker_step']},
    show24Hours: {$prefs['timepicker_show_24']}
  });
} catch(err) {
  $('#messagepane').html('<a href="http://{$prefs['siteurl']}/textpattern/?event=plugin_prefs.glz_custom_fields">Fix glz_custom_fields configuration</a>');
} 
EOF;
  }
  $js .= <<<EOF
<script type="text/javascript">
<!--//--><![CDATA[//><!--

$(function() {
  // creating a global object to store variables, functions etc.
  var GLZ_CUSTOM_FIELDS;
  if (GLZ_CUSTOM_FIELDS == undefined)
    GLZ_CUSTOM_FIELDS = {};
  GLZ_CUSTOM_FIELDS.special_custom_types = ["date-picker", "time-picker"];
  GLZ_CUSTOM_FIELDS.no_value_custom_types = ["text_input", "textarea"];
  GLZ_CUSTOM_FIELDS.messages = {
    'textarea' : "<em>Each value on a separate line</em><br /><em>One {default} value allowed</em>",
    'script' : "<em>Full path to your script</em>"
  }

  // sweet jQuery table striping
  $(".stripeMe tr").mouseover(function() { $(this).addClass("over"); }).mouseout(function() { $(this).removeClass("over"); });
  $(".stripeMe tr:even").addClass("alt");

  // disable all custom field references in Advanced Prefs
  // prefs_ui doesn't offer support for this, getting the custom fields to display right here is not crucial at this point
  var custom_field_tr = $("tr[id*=prefs-custom_], tr:has(h2:contains('Custom Fields'))").not('.glz_custom_fields_prefs tr:has(h2)');
  if ( custom_field_tr ) {
    $.each (custom_field_tr, function() {
      $(this).hide();
    });
  }

  // toggle custom field value based on its type
  
  toggle_type_link();
  if ( $.inArray($("select#custom_set_type :selected").attr("value"), [].concat(GLZ_CUSTOM_FIELDS.special_custom_types, GLZ_CUSTOM_FIELDS.no_value_custom_types)) != -1 ) {
    custom_field_value_off();
  }
  else if ( $("select#custom_set_type :selected").attr("value") == "custom-script" )
    custom_field_value_path();

  $("select#custom_set_type").change( function() {
    toggle_type_link();
    if ( $.inArray($("select#custom_set_type :selected").attr("value"), [].concat(GLZ_CUSTOM_FIELDS.special_custom_types, GLZ_CUSTOM_FIELDS.no_value_custom_types)) != -1 ) {
      custom_field_value_off();
    }
    else if ( $("select#custom_set_type :selected").attr("value") == "custom-script" )
      custom_field_value_path();
    else {
      custom_field_value_on();
    }
  });

  // enable date-picker custom sets
  {$date_picker_js}
  
  // enable time-picker custom sets
  {$time_picker_js}

  // add a reset link to all radio custom fields
  if ($(".glz_custom_radio_field").length > 0) {
    $(".glz_custom_radio_field").each(function() {
      custom_field_to_reset = $(this).find("input:first").attr("name");
      $(this).find("label:first").after(" <span class=\"small\">[<a href=\"#\" class=\"glz_custom_field_reset\" name=\"" + custom_field_to_reset +"\">Reset</a>]</span>");
    });

    // catch the reset action for the above link
    $(".glz_custom_field_reset").click(function() {
      custom_field_to_reset = $(this).attr("name");
      // reset our radio input
      $("input[name=" + custom_field_to_reset + "]").attr("checked", false);
      // add an empty value with the same ID so that it saves the value as empty in the db
      $(this).after("<input type=\"hidden\" value=\"\" name=\""+ custom_field_to_reset +"\" />");
      return false;
    });
  }

  // ### RE-USABLE FUNCTIONS ###

  function custom_field_value_off() {
    if ($("textarea#value").length) {
      GLZ_CUSTOM_FIELDS.textarea_value = $("textarea#value").html();
      $("textarea#value + span.right").html('');
      $("textarea#value").remove();
    }
    
    if (!$("input#value").length)
      $("label[for=value]").after('<input id="value" name="value" class="left" />');
    $("input#value").attr('value', "no value allowed").attr('disabled', "disabled");
    $("input#value + span.right").html('');
  }

  function custom_field_value_on() {
    if ( $("input#value").length )
      $("input#value").remove();
    if ( !$("textarea#value").length ) {
      $("label[for=value]").after('<textarea id="value" name="value" class="left"></textarea>');
      $("textarea#value + span.right").html(GLZ_CUSTOM_FIELDS.messages['textarea']);
    }
    if ( GLZ_CUSTOM_FIELDS.textarea_value )
      $("textarea#value").html(GLZ_CUSTOM_FIELDS.textarea_value);
  }

  function custom_field_value_path() {
    if ($("textarea#value").length) {
      $("textarea#value + span.right").html('');
      $("textarea#value").remove();
    }
    if (!$("input#value").length)
      $("label[for=value]").after('<input id="value" name="value" class="left" />');
    if ( $.inArray($("input#value").attr('value'), ["", "no value allowed"]) != -1 )
      $("input#value").attr('value', "{$prefs['custom_scripts_path']}")
    $("input#value").attr('disabled', "");
    $("input#value + span.right").html(GLZ_CUSTOM_FIELDS.messages['script']);
  }

  function toggle_type_link() {
    $("select#custom_set_type").parent().find('span').remove();
    if ( $.inArray($("select#custom_set_type :selected").attr("value"), [].concat(GLZ_CUSTOM_FIELDS.special_custom_types, ["multi-select"])) != -1 )
      $("select#custom_set_type").after("<span class=\"right\"><em><a href=\"http://{$prefs['siteurl']}/textpattern?event=plugin_prefs.glz_custom_fields\">Configure glz_custom_fields</a></em></span>");
  }

});

//--><!]]>
</script>
EOF;

  // displays the notices we have gathered throughout the entire plugin
  if ( count($glz_notice) > 0 ) {
    // let's turn our notices into a string
    $glz_notice = join("<br />", array_unique($glz_notice));

    $js .= '<script type="text/javascript">
    <!--//--><![CDATA[//><!--

    $(document).ready(function() {
      // add our notices
      $("#messagepane").html(\''.$glz_notice.'\');
    });
    //--><!]]>
    </script>';
  }

  echo $js.n.t.$css.n.t;
}


// -------------------------------------------------------------
// we are setting up the pre-requisite values for glz_custom_fields
function before_glz_custom_fields() {
  // we will be reusing these globals across the whole plugin
  global $all_custom_sets, $glz_notice, $prefs, $date_picker, $time_picker;

  // glz_notice collects all plugin notices
  $glz_notice = array();

  // let's get all custom field sets from prefs
  $all_custom_sets = glz_custom_fields_MySQL("all");

  // let's see if we have a date-picker custom field (first of the special ones)
  $date_picker = glz_custom_fields_MySQL("custom_set_exists", "date-picker");
  
  // let's see if we have a time-picker custom field
  $time_picker = glz_custom_fields_MySQL("custom_set_exists", "time-picker");
}


// -------------------------------------------------------------
// bootstrapping routines, run through plugin_lifecycle
function glz_custom_fields_install() {
  global $all_custom_sets, $glz_notice, $prefs;

  // if jQuery is not present, trigger error
  // improvement courtesy of Sam Weiss
  if ( !@fopen($GLOBALS['txpcfg']['txpath'].'/jquery.js', "r") ) {
    trigger_error(glz_custom_fields_gTxt('jquery_missing'));
    trigger_error(glz_custom_fields_gTxt('check_path'));
  }

  // default custom fields are set to custom_set
  // need to change this because it confuses our set_types()
  safe_query("
    UPDATE
      `".PFX."txp_prefs`
    SET
      `html` = 'text_input'
    WHERE
      `event` = 'custom'
    AND
      `html` = 'custom_set'
  ");

  // set plugin preferences
  $arr_plugin_preferences = array(
    'values_ordering'       => "custom",
    'multiselect_size'      => "5",
    'datepicker_url'        => hu."scripts/glz_custom_fields/jquery.datePicker/",
    'datepicker_format'     => "dd/mm/yyyy",
    'datepicker_first_day'  => 1,
    'datepicker_start_date' => "01/01/1990",
    'timepicker_url'        => hu."scripts/glz_custom_fields/jquery.timePicker/",
    'timepicker_start_time' => "00:00",
    'timepicker_end_time'   => "23:30",
    'timepicker_step'       => 30,
    'timepicker_show_24'    => true,
    'custom_scripts_path'   => $prefs['path_to_site']."/scripts/glz_custom_fields/",
  );
  glz_custom_fields_MySQL("update_plugin_preferences", $arr_plugin_preferences);

  // let's update plugin preferences, make sure they won't appear under Admin > Preferences
  safe_query("
    UPDATE
      `".PFX."txp_prefs`
    SET
      `type` = '10'
    WHERE
      `event` = 'glz_custom_f'
  ");

  // if we don't have a search section, let's create it because we'll need it when searching by custom fields
  if( !getRow("SELECT name FROM `".PFX."txp_section` WHERE name='search'") ) {
    safe_query("
      INSERT INTO
        `".PFX."txp_section` (`name`, `page`, `css`, `is_default`, `in_rss`, `on_frontpage`, `searchable`, `title`)
      VALUES
        ('search', 'default', 'default', '0', '0', '0', '0', 'Search')
    ");
    // add a notice that search section has bee created
    $glz_notice[] = glz_custom_fields_gTxt("search_section_created");
  }

  // if we don't have the custom_fields table, let's create it
  if ( !getRows("SHOW TABLES LIKE '".PFX."custom_fields'") ) {
   safe_query("
     CREATE TABLE `".PFX."custom_fields` (
      `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
       `name` varchar(255) NOT NULL default '',
       `value` varchar(255) NOT NULL default '',
       PRIMARY KEY (id),
       INDEX (`name`)
     ) TYPE=MyISAM
   ");
  }
  else {
    // if there isn't and id column, add it
    if ( !getRows("SHOW COLUMNS FROM ".PFX."custom_fields LIKE 'id'") ) {
      safe_query("
        ALTER TABLE `".PFX."custom_fields`
          ADD `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT KEY
      ");
    }

   // if we have definitely migrated using this function, skip everything
   if ( isset($prefs['migrated']) )
     return;
   // abort the migration if there are values in custom_fields table, we don't want to overwrite anything
   else if ( glz_custom_fields_MySQL('check_migration') > 0 ) {
     // DEBUG
     // dmp(glz_custom_fields_MySQL('check_migration'));
     $glz_notice[] = glz_custom_fields_gTxt("migration_skip");
     // make a note of this migration in txp_prefs
     glz_custom_fields_MySQL('mark_migration');
     return;
   }

   // go through all values in custom field columns in textpattern table one by one
   foreach ($all_custom_sets as $custom => $custom_set) {
     // check only for custom fields that have been set
     if ( $custom_set['name'] ) {
       // get all existing custom values for ALL articles
       $all_values = glz_custom_fields_MySQL('all_values', glz_custom_number($custom), '', array('custom_set_name' => $custom_set['name'], 'status' => 0));
       // if we have results, let's create SQL queries that will add them to custom_fields table
       if ( count($all_values) > 0 ) {
         // initialize insert
         $insert = '';
         foreach ( $all_values as $escaped_value => $value ) {
           // don't insert empty values or values that are over 255 characters
           // values over 255 characters hint to a textarea custom field
           if ( !empty($escaped_value) && strlen($escaped_value) < 255 )
             // if this is the last value, query will have to be different
             $insert .= ( end($all_values) != $value ) ?
               "('{$custom}','{$escaped_value}')," :
               "('{$custom}','{$escaped_value}')";
         }
         $query = "
           INSERT INTO
             `".PFX."custom_fields` (`name`,`value`)
           VALUES
             {$insert}
         ";
         if ( isset($query) && !empty($query) ) {
           // create all custom field values in custom_fields table
           safe_query($query);
           // update the type of this custom field to select (might want to make this user-adjustable at some point)
           glz_custom_fields_MySQL("update", $custom, PFX."txp_prefs", array(
             'custom_set_name'   => $custom_set['name'],
             'custom_set_type'   => "select",
             'custom_set_position' => $custom_set['position']
           ));
           $glz_notice[] = glz_custom_fields_gTxt("migration_success");
         }
       }
     }
   }

   // make a note of this migration in txp_prefs
   glz_custom_fields_MySQL('mark_migration');
  }
}





// globals, expensive operations mostly
before_glz_custom_fields();

if (@txpinterface == "admin") {

  // INSTALL ROUTINES
  // checks if all tables exist and everything is setup properly
  add_privs('glz_custom_fields_install', "1");
  register_callback("glz_custom_fields_install", "plugin_lifecycle.glz_custom_fields", "installed");

  // we'll be doing this only on the pages that we care about, not everywhere
  if ( in_array(gps('event'), array("article", "prefs", "glz_custom_fields", "plugin_prefs.glz_custom_fields")) ) {
    // we need some stylesheets & JS
    add_privs('glz_custom_fields_css_js', "1,2,3,4,5,6");
    register_callback('glz_custom_fields_css_js', "admin_side", 'head_end');

    // we need to make sure that all custom field values will be converted to strings first - think checkboxes & multi-selects etc.
    if ( (gps("step") == "edit") || (gps("step") == "create") ) {
      add_privs('glz_custom_fields_before_save', "1,2,3,4,5,6");
      register_callback('glz_custom_fields_before_save', "article", '', 1);
    }
  }

  // Custom Fields tab under Extensions
  add_privs('glz_custom_fields', "1,2");
  register_tab("extensions", 'glz_custom_fields', "Custom Fields");
  register_callback('glz_custom_fields', "glz_custom_fields");

  // plugin preferences
  add_privs('plugin_prefs.glz_custom_fields', "1,2");
  register_callback('glz_custom_fields_preferences', 'plugin_prefs.glz_custom_fields');


  // YES, finally the default custom fields are replaced by the new, pimped ones : )
  add_privs('glz_custom_fields_replace', "1,2,3,4,5,6");
  register_callback('glz_custom_fields_replace', 'article_ui', 'custom_fields');
  // YES, now we have textarea custom fields as well ; )
  add_privs('glz_custom_fields_replace', "1,2,3,4,5,6");
  register_callback('glz_custom_fields_replace', 'article_ui', 'excerpt');
}

// -------------------------------------------------------------
// everything is happening in this function... generates the content for Extensions > Custom Fields
function glz_custom_fields() {
  global $event, $all_custom_sets, $glz_notice, $prefs;

  // we have $_POST, let's see if there is any CRUD
  if ( $_POST ) {
    $incoming = stripPost();
    // DEBUG
    // die(dmp($incoming));
    extract($incoming);

    // create an empty $value if it's not set in the $_POST
    if ( !isset($value) )
      $value = '';

    // we are deleting a new custom field
    if ( gps('delete') ) {
      glz_custom_fields_MySQL("delete", $custom_set, PFX."txp_prefs");
      glz_custom_fields_MySQL("delete", $custom_set, PFX."txp_lang");
      glz_custom_fields_MySQL("delete", $custom_set, PFX."custom_fields");

      glz_custom_fields_MySQL("delete", glz_custom_number($custom_set), PFX."textpattern");

      $glz_notice[] = glz_custom_fields_gTxt("deleted", array('{custom_set_name}' => $custom_set_name));
    }

    // we are resetting one of the mighty 10
    if ( gps('reset') ) {
      glz_custom_fields_MySQL("reset", $custom_set, PFX."txp_prefs");
      glz_custom_fields_MySQL("delete", $custom_set, PFX."custom_fields");

      glz_custom_fields_MySQL("reset", glz_custom_number($custom_set), PFX."textpattern", array(
        'custom_set_type' => $custom_set_type,
        'custom_field' => glz_custom_number($custom_set)
      ));

      $glz_notice[] = glz_custom_fields_gTxt("reset", array('{custom_set_name}' => $custom_set_name));
    }

    // we are adding a new custom field
    if ( gps("custom_field_number") ) {
      $custom_set_name = gps("custom_set_name");

      // if no name was specified, abort
      if ( !$custom_set_name )
        $glz_notice[] = glz_custom_fields_gTxt("no_name");
      else {
        $custom_set_name = glz_clean_string($custom_set_name);

        $name_exists = glz_check_custom_set_name($all_custom_sets, $custom_set_name);

        // if name doesn't exist
        if ( $name_exists == FALSE ) {
          glz_custom_fields_MySQL("new", $custom_set_name, PFX."txp_prefs", array(
            'custom_field_number' => $custom_field_number,
            'custom_set_type'     => $custom_set_type,
            'custom_set_position' => $custom_set_position
          ));
          glz_custom_fields_MySQL("new", $custom_set_name, PFX."txp_lang", array(
            'custom_field_number' => $custom_field_number,
            'lang'                => $GLOBALS['prefs']['language']
          ));
          glz_custom_fields_MySQL("new", $custom_set_name, PFX."textpattern", array(
            'custom_field_number' => $custom_field_number,
            'custom_set_type' => $custom_set_type
          ));
          // there are custom fields for which we do not need to touch custom_fields table
          if ( !in_array($custom_set_type, array("textarea", "text_input")) ) {
            glz_custom_fields_MySQL("new", $custom_set_name, PFX."custom_fields", array(
              'custom_field_number' => $custom_field_number,
              'value'               => $value
            ));
          }

          $glz_notice[] = glz_custom_fields_gTxt("created", array('{custom_set_name}' => $custom_set_name));
        }
        // name exists, abort
        else
          $glz_notice[] = glz_custom_fields_gTxt("exists", array('{custom_set_name}' => $custom_set_name));
      }
    }

    // we are editing an existing custom field
    if ( gps('save') ) {
      if ( !empty($custom_set_name) ) {
        $custom_set_name = glz_clean_string($custom_set_name);
        $name_exists = glz_check_custom_set_name($all_custom_sets, $custom_set_name, $custom_set);
        // if name doesn't exist we'll need to create a new custom_set
        if ( $name_exists == FALSE ) {
          glz_custom_fields_MySQL("update", $custom_set, PFX."txp_prefs", array(
            'custom_set_name'     => $custom_set_name,
            'custom_set_type'     => $custom_set_type,
            'custom_set_position' => $custom_set_position
          ));

          // custom sets need to be changed based on their type
          glz_custom_fields_MySQL("update", $custom_set, PFX."textpattern", array(
            'custom_set_type' => $custom_set_type,
            'custom_field' => glz_custom_number($custom_set)
          ));

          // for textareas we do not need to touch custom_fields table
          if ( $custom_set_type != "textarea" ) {
            glz_custom_fields_MySQL("delete", $custom_set, PFX."custom_fields");
            glz_custom_fields_MySQL("new", $custom_set_name, PFX."custom_fields", array(
              'custom_set'  => $custom_set,
              'value'       => $value
            ));
          }

          $glz_notice[] = glz_custom_fields_gTxt("updated", array('{custom_set_name}' => $custom_set_name));
        }
        // name exists, abort
        else
          $glz_notice[] = glz_custom_fields_gTxt("exists", array('{custom_set_name}' => $custom_set_name));
      }
      else
        $glz_notice[] = glz_custom_fields_gTxt('no_name');
    }

    // need to re-fetch data since things modified
    $all_custom_sets = glz_custom_fields_MySQL("all");

  }

  pagetop("Custom Fields");

  // the table with all custom fields follows
  echo
    n.'<table cellspacing="0" class="glz_custom_fields stripeMe">'.n.
    ' <thead>'.n.
    '   <tr>'.n.
    '     <td>Position</td>'.n.
    '     <td>Name</td>'.n.
    '     <td>Type</td>'.n.
    '     <td>&nbsp;</td>'.n.
    '   </tr>'.n.
    ' </thead>'.n.
    ' <tbody>'.n;

  // looping through all our custom fields to build the table
  $i = 0;
  foreach ( $all_custom_sets as $custom => $custom_set ) {
    // first 10 fields cannot be deleted, just reset
    if ( $i < 10 ) {
      // can't reset a custom field that is not set
      $reset_delete = ( $custom_set['name'] ) ?
        glz_form_buttons("reset", "Reset", $custom, htmlspecialchars($custom_set['name']), $custom_set['type'], '', 'return confirm(\'By proceeding you will RESET ALL data in `textpattern` and `custom_fields` tables for `'.$custom.'`. Are you sure?\');') :
        NULL;
    }
    else {
      $reset_delete = glz_form_buttons("delete", "Delete", $custom, htmlspecialchars($custom_set['name']), $custom_set['type'], '', 'return confirm(\'By proceeding you will DELETE ALL data in `textpattern` and `custom_fields` tables for `'.$custom.'`. Are you sure?\');');
    }

    $edit = glz_form_buttons("edit", "Edit", $custom, htmlspecialchars($custom_set['name']), $custom_set['type'], $custom_set['position']);

    echo
    '   <tr>'.n.
    '     <td class="custom_set_position">'.$custom_set['position'].'</td>'.n.
    '     <td class="custom_set_name">'.$custom_set['name'].'</td>'.n.
    '     <td class="type">'.(($custom_set['name']) ? glz_custom_fields_gTxt($custom_set['type']) : '').'</td>'.n.
    '     <td class="events">'.$reset_delete.sp.$edit.'</td>'.n.
    '   </tr>'.n;

    $i++;
  }

  echo
    ' </tbody>'.n.
    '</table>'.n;

  // the form where custom fields are being added/edited
  $legend = gps('edit') ?
    'Edit '.gps('custom_set') :
    'Add new custom field';

  $custom_field = gps('edit') ?
    '<input name="custom_set" value="'.gps('custom_set').'" type="hidden" />' :
    '<input name="custom_field_number" value="'.glz_custom_next($all_custom_sets).'" type="hidden" />';

  $custom_set = gps('edit') ?
    gps('custom_set') :
    NULL;

  $custom_name = gps('edit') ?
    gps('custom_set_name') :
    NULL;

  $custom_set_position = gps('edit') ?
    gps('custom_set_position') :
    NULL;

  $arr_custom_set_types = glz_custom_set_types();

  $custom_set_types = NULL;
  foreach ( $arr_custom_set_types as $custom_type_group => $custom_types ) {
    $custom_set_types .= '<optgroup label="'.ucfirst($custom_type_group).'">'.n;
    foreach ($custom_types as $custom_type) {
      $selected = ( gps('edit') && gps('custom_set_type') == $custom_type ) ?
        ' selected="selected"' :
        NULL;
      $custom_set_types .= '<option value="'.$custom_type.'"'.$selected.'>'.glz_custom_fields_gTxt($custom_type).'</option>'.n;
    }
    $custom_set_types .= '</optgroup>'.n;
  }
  // fetching the values for this custom field
  if ( gps('edit') ) {
    if ( $custom_set_type == "text_input" )
      $arr_values = glz_custom_fields_MySQL('all_values', glz_custom_number($custom_set), '', array('custom_set_name' => $custom_set_name, 'status' => 4));
    else
      $arr_values = glz_custom_fields_MySQL("values", $custom_set, '', array('custom_set_name' => $custom_set_name));
    
    $values = ( $arr_values ) ?
      implode("\r\n", $arr_values) :
      '';
  }
  else
    $values = '';
  
  $action = gps('edit') ?
    '<input name="save" value="Save" type="submit" class="publish" />' :
    '<input name="add_new" value="Add new" type="submit" class="publish" />';
  // this needs to be different for a script
  $value = ( isset($custom_set_type) && $custom_set_type == "custom-script" ) ?
    '<input name="value" id="value" value="'.$values.'" class="left"/><span class="right"><em>Full path to your script</em></span>' :
    '<textarea name="value" id="value" class="left">'.$values.'</textarea><span class="right"><em>Each value on a separate line</em> <br /><em>One {default} value allowed</em></span>';
  
  // ok, all is set, let's build the form
  echo
    '<form method="post" action="index.php" id="add_edit_custom_field">'.n.
    '<input name="event" value="glz_custom_fields" type="hidden" />'.n.
    $custom_field.n.
    '<fieldset>'.n.
    ' <legend>'.$legend.'</legend>'.n.
    ' <p class="clearfix">
        <label for="custom_set_name" class="left">Name:</label>
        <input name="custom_set_name" value="'.htmlspecialchars($custom_name).'" id="custom_set_name" class="left" />
        <span class="right"><em>Only word characters allowed</em></span>
      </p>'.n.
    ' <p class="clearfix">
        <label for="custom_set_type" class="left">Type:</label>
        <select name="custom_set_type" id="custom_set_type" class="left">
    '.      $custom_set_types.'
        </select>
      </p>'.n.
    ' <p class="clearfix">
        <label for="custom_set_position" class="left">Position:</label>
        <input name="custom_set_position" value="'.htmlspecialchars($custom_set_position).'" id="custom_set_position" class="left" />
        <span class="right"><em>Automatically assigned if blank</em></span>
      </p>'.n.
    ' <p class="clearfix">
        <label for="value" class="left">Value:</label>
    '.  $value.'
      </p>'.n.
    ' '.$action.n.
    '</fieldset>'.n.
    '</form>'.n;
}


// -------------------------------------------------------------
// glz_custom_fields preferences
function glz_custom_fields_preferences() {
  global $event, $glz_notice;

  if ( $_POST && gps('save') ) {
    glz_custom_fields_MySQL("update_plugin_preferences", $_POST['glz_custom_fields_prefs']);
    $glz_notice[] = glz_custom_fields_gTxt("preferences_updated");
    // need to re-fetch from db because this has changed since $prefs has been populated
  }
  $current_preferences = glz_custom_fields_MySQL('plugin_preferences');

  pagetop("glz_custom_fields Preferences");

  // custom_fields
  $arr_values_ordering = array(
    'ascending'   => "Ascending",
    'descending'  => "Descending",
    'custom'      => "As entered"
  );
  $values_ordering = '<select name="glz_custom_fields_prefs[values_ordering]" id="glz_custom_fields_prefs_values_ordering">';
  foreach ( $arr_values_ordering as $value => $title ) {
    $selected = ($current_preferences['values_ordering'] == $value) ? ' selected="selected"' : '';
    $values_ordering .= "<option value=\"$value\"$selected>$title</option>";
  }
  $values_ordering .= "</select>";
  $multiselect_size = '<input type="text" name="glz_custom_fields_prefs[multiselect_size]" id="glz_custom_fields_prefs_multiselect_size" value="'.$current_preferences['multiselect_size'].'" />';
  $custom_scripts_path_error = ( @fopen($current_preferences['custom_scripts_path'], "r") ) ?
    '' :
    '<br /><em class="red">Folder does not exist, please create it.</em>';

  // jquery.datePicker
  $datepicker_url_error = ( @fopen($current_preferences['datepicker_url']."datePicker.js", "r") ) ?
    '' :
    '<br /><em class="red">Folder does not exist, please create it.</em>';
  $arr_date_format = array("dd/mm/yyyy", "mm/dd/yyyy", "yyyy-mm-dd", "dd mm yy");
  $date_format = '<select name="glz_custom_fields_prefs[datepicker_format]" id="glz_custom_fields_prefs_datepicker_format">';
  foreach ( $arr_date_format as $format ) {
    $selected = ($current_preferences['datepicker_format'] == $format) ? ' selected="selected"' : '';
    $date_format .= "<option value=\"$format\"$selected>$format</option>";
  }
  $date_format .= "</select>";

  $arr_days = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
  $first_day = '<select name="glz_custom_fields_prefs[datepicker_first_day]" id="glz_custom_fields_prefs_datepicker_first_day">';
  foreach ( $arr_days as $key => $day ) {
    $selected = ($current_preferences['datepicker_first_day'] == $key) ? ' selected="selected"' : '';
    $first_day .= "<option value=\"$key\"$selected>$day</option>";
  }
  $first_day .= "</select>";

  $start_date = '<input type="text" name="glz_custom_fields_prefs[datepicker_start_date]" id="glz_custom_fields_prefs_datepicker_start_date" value="'.$current_preferences['datepicker_start_date'].'" />';
  
  // jquery.timePicker
  $timepicker_url_error = ( @fopen($current_preferences['timepicker_url']."timePicker.js", "r") ) ?
    '' :
    '<br /><em class="red">Folder does not exist, please create it.</em>';
  $arr_time_format = array('true' => "24 hours", 'false' => "12 hours");
  $show_24 = '<select name="glz_custom_fields_prefs[timepicker_show_24]" id="glz_custom_fields_prefs_timepicker_show_24">';
  foreach ( $arr_time_format as $value => $title ) {
    $selected = ($current_preferences['timepicker_show_24'] == $value) ? ' selected="selected"' : '';
    $show_24 .= "<option value=\"$value\"$selected>$title</option>";
  }
  $show_24 .= "</select>";

  $out = <<<EOF
<form action="index.php" method="post">
<table id="list" class="glz_custom_fields_prefs" cellpadding="0" cellspacing="0" align="center">
  <tbody>
    <tr class="heading">
      <td colspan="2"><h2 class="pref-heading">Custom Fields</h2></td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_values_ordering">Order for custom field values</th>
      <td>{$values_ordering}</td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_multiselect_size">Multi-select field size</th>
      <td>{$multiselect_size}</td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_custom_scripts_path">Custom scripts path</th>
      <td><input type="text" name="glz_custom_fields_prefs[custom_scripts_path]" id="glz_custom_fields_prefs_custom_scripts_path" value="{$current_preferences['custom_scripts_path']}" />{$custom_scripts_path_error}</td>
    </tr>
    
    <tr class="heading">
      <td colspan="2"><h2 class="pref-heading left">Date Picker</h2> <a href="http://www.kelvinluck.com/assets/jquery/datePicker/v2/demo/index.html" title="A flexible unobtrusive calendar component for jQuery" class="right">jQuery datePicker</a></td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_datepicker_url">Date Picker plugin URL</th>
      <td><input type="text" name="glz_custom_fields_prefs[datepicker_url]" id="glz_custom_fields_prefs_datepicker_url" value="{$current_preferences['datepicker_url']}" />{$datepicker_url_error}</td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_datepicker_format">Date format</th>
      <td>{$date_format}</td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_datepicker_first_day">First day of week</th>
      <td>{$first_day}</td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_datepicker_start_date">Start date</th>
      <td>{$start_date}<br /><em class="grey">MUST be the same as "Date format"</em></td>
    </tr>
    
    <tr class="heading">
      <td colspan="2"><h2 class="pref-heading left">Time Picker</h2> <a href="http://labs.perifer.se/timedatepicker/" title="jQuery time picker" class="right">jQuery timePicker</a></td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_timepicker_url">Time Picker plugin URL</th>
      <td><input type="text" name="glz_custom_fields_prefs[timepicker_url]" id="glz_custom_fields_prefs_timepicker_url" value="{$current_preferences['timepicker_url']}" />{$timepicker_url_error}</td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_timepicker_start_time">Start time</th>
      <td><input type="text" name="glz_custom_fields_prefs[timepicker_start_time]" id="glz_custom_fields_prefs_timepicker_start_time" value="{$current_preferences['timepicker_start_time']}" /></td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_timepicker_end_time">End time</th>
      <td><input type="text" name="glz_custom_fields_prefs[timepicker_end_time]" id="glz_custom_fields_prefs_timepicker_end_time" value="{$current_preferences['timepicker_end_time']}" /></td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_timepicker_step">Step</th>
      <td><input type="text" name="glz_custom_fields_prefs[timepicker_step]" id="glz_custom_fields_prefs_timepicker_step" value="{$current_preferences['timepicker_step']}" /></td>
    </tr>
    <tr>
      <th scope="row"><label for="glz_custom_fields_prefs_timepicker_step">Time format</th>
      <td>{$show_24}</td>
    </tr>
    
    <tr>
      <td colspan="2" class="noline">
        <input class="publish" type="submit" name="save" value="Save" />
        <input type="hidden" name="event" value="plugin_prefs.glz_custom_fields" />
      </td>
    </tr>
  </tbody>
</table>
EOF;

  echo $out;
}