view msite.inc.php @ 1092:95b74632cfe2

Rename votekeys table to userkeys, and all related variables and settings.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 26 Jan 2017 13:38:19 +0200
parents 00632d30bafe
children 0a2117349f46
line wrap: on
line source

<?php
//
// FAPWeb - Simple Web-based Demoparty Management System
// Generic and miscellaneous site support code
// (C) Copyright 2012-2017 Tecnic Software productions (TNSP)
//
require_once "msitegen.inc.php";

// Define modes of party information display system
define("SMODE_DISABLED", 0);
define("SMODE_ROTATE", 1);
define("SMODE_COMPO", 2);


// Define sizes of database fields, see createdb.php
// and also the places where input is validated.
define("SET_LEN_USERNAME", 32);
define("SET_LEN_GROUPS", 64);
define("SET_LEN_ONELINER", 64);
define("SET_LEN_EMAIL", 80);
define("SET_LEN_REGHOST", 128);

define("SET_LEN_NEWS_TITLE", 128);
define("SET_LEN_NEWS_TEXT", 4096);
define("SET_LEN_NEWS_AUTHOR", 64);

define("SET_LEN_COMPO_NAME", 128);
define("SET_LEN_COMPO_DESC", 4096);
define("SET_LEN_COMPO_NOTES", 4096);
define("SET_LEN_COMPO_PATH", 128);

define("SET_LEN_ENTRY_NAME", 64);
define("SET_LEN_ENTRY_AUTHOR", 64);
define("SET_LEN_ENTRY_FILENAME", 128);
define("SET_LEN_ENTRY_INFO", 50*4);
define("SET_LEN_ENTRY_NOTES", 1024);
define("SET_LEN_ENTRY_PREVIEW_FILE", 128);

define("SET_LEN_DISP_SLIDE_TITLE", 64);
define("SET_LEN_DISP_SLIDE_TEXT", 4096);
define("SET_LEN_ROT_LIST_NAME", 128);

define("SET_LEN_USERKEY", 64);


//
// File format classes
//
define("EFILE_NONE", 0);
define("EFILE_IMAGE", 1);
define("EFILE_AUDIO", 2); // Also preview types

define("EFILE_VIDEO", 3);
define("EFILE_TEXT", 4);
define("EFILE_BINARY", 5);
define("EFILE_ARCHIVE", 6);


//
// Entry preview type (value)
//
$previewTypeList = array(
  EFILE_NONE         => array("No previews" , "Default"),
  EFILE_IMAGE        => array("Image file"  , "Image"),
  EFILE_AUDIO        => array("Audio file"  , "Audio"),
);


//
// Entry flags (bitfield)
//
define("EFLAG_DISQUALIFIED" , 1);    // Entry is disqualified
define("EFLAG_PROBLEMS"     , 2);    // Has some problems
define("EFLAG_LOCKED"       , 4);    // Can't be edited by non-admins

$entryFlagsList = array(
  EFLAG_DISQUALIFIED => array("Disqualified", "img/disqualified.png"),
  EFLAG_PROBLEMS     => array("Has problems", "img/problems.png"),
  EFLAG_LOCKED       => array("LOCKED", "img/locked.png"),
);


//
// Results output flags
//
define("RFLAG_NORMAL", 0);
define("RFLAG_DISQUALIFIED", 1);
define("RFLAG_HIDDEN_COMPOS", 2);

//
// Competition types
//
define("COMPO_NORMAL", 0);          // Normal voting compo, points determine placement
define("COMPO_POINTS", 1);          // Assigned points, points determines placement (no voting)
define("COMPO_ASSIGN", 2);          // Assigned places (no voting)


$compoModeData = array(
  COMPO_NORMAL => array("Normal",
    "Normal voting compo.",
  ),
  COMPO_POINTS => array("Points",
    "Assigned points (no voting).",
    "Points",
  ),
  COMPO_ASSIGN => array("Assigned",
    "Assigned places (no points, no voting).",
    "Place",
  ),
);


//
// Different voting modes
//
define("VOTE_FREELY", 0);
define("VOTE_ACTIVATE", 1);
define("VOTE_ASSIGN", 2);

$voteModeData = array(
  VOTE_FREELY => array("Freeform voting",
    "User keys are not tied to attendees, and do not need to be activated. ".
    "Take one printed key slip, give it to attendee."
    ),
  VOTE_ACTIVATE => array("Key activation",
    "User keys are not tied to attendees, but require manual activation. ".
    "Take one printed key slip, find it by the index number in the list below, set to activated. ".
    "Give key slip to attendee."
    ),
  VOTE_ASSIGN => array("Assigned keys",
    "User keys are tied to attendees, activated by assigning the key to attendee. ".
    "Take one printed key slip, find attendee in the list below, enter key ID number, assign, check. ".
    "Give key slip to attendee."
    ),
);


//
// Data about the file types we use
//
$fileTypeData = array(
  "PNG"  => array(
    "class" => EFILE_IMAGE,
    "type" => EFILE_IMAGE,
    "mime" => "image/png",
    "fext" => "png",
  ),
  "JPEG" => array(
    "class" => EFILE_IMAGE,
    "type" => EFILE_IMAGE,
    "mime" => "image/jpeg",
    "fext" => "jpg",
  ),
  "GIF"  => array(
    "class" => EFILE_IMAGE,
    "type" => EFILE_IMAGE,
    "mime" => "image/gif",
    "fext" => "gif",
  ),

  "MP3" => array(
    "class" => EFILE_AUDIO,
    "type" => EFILE_AUDIO,
    "mime" => "audio/mpeg",
    "fext" => "mp3",
    "test" => "MPEG ADTS, layer III",
  ),
  "OggVorbis" => array(
    "class" => EFILE_AUDIO,
    "type" => EFILE_AUDIO,
    "mime" => "audio/ogg; codecs=vorbis",
    "fext" => "ogg",
    "test" => "Ogg data, Vorbis audio",
  ),
  "FLAC" => array(
    "class" => EFILE_AUDIO,
    "type" => EFILE_AUDIO,
    "mime" => "audio/x-flac",
    "fext" => "flac",
  ),
  "WAV" => array(
    "class" => EFILE_AUDIO,
    "type" => EFILE_AUDIO,
    "mime" => "audio/x-wav",
    "fext" => "wav",
  ),

  // Special cases
  "ILBM" => array(
    "class" => EFILE_IMAGE,
    "mime" => "gfx", // Special cases to be converted through gfxconv ..
    "fext" => "lbm",
    "test" => "^IFF data, ILBM",
  ),
  "ANIM" => array(
    "class" => EFILE_IMAGE,
    "mime" => "image/x-anim", // Special cases to be converted through gfxconv ..
    "fext" => "anim",
    "test" => "^IFF data, ILBM",
  ),
  "PCX" => array(
    "class" => EFILE_IMAGE,
    "mime" => "image/x-pcx",
    "fext" => "pcx",
    "test" => "^PCX ver\. 3\.0",
  ),

  "PTMOD" => array(
    "class" => EFILE_AUDIO,
    "mime" => "audio/x-mod",
    "fext" => "mod",
    "test" => "^\d+-channel Protracker module",
  ),
  "FTMOD" => array(
    "class" => EFILE_AUDIO,
    "mime" => "audio/x-mod",
    "fext" => "mod",
    "test" => "^\d+-channel Fasttracker module",
  ),
  "S3M" => array(
    "class" => EFILE_AUDIO,
    "mime" => "audio/x-mod",
    "fext" => "s3m",
    "test" => "^ScreamTracker III Module",
  ),
  "XM" => array(
    "class" => EFILE_AUDIO,
    "mime" => "audio/x-mod",
    "fext" => "xm",
    "test" => "^Fasttracker II module",
  ),
  "IT" => array(
    "class" => EFILE_AUDIO,
    "mime" => "audio/x-mod",
    "fext" => "it",
    "test" => "^Impulse Tracker module",
  ),
  "PTM" => array(
    "class" => EFILE_AUDIO,
    "mime" => "audio/x-mod",
    "fext" => "ptm",
    "test" => "^Poly Tracker PTM Module",
  ),

  "AVI" => array(
    "class" => EFILE_VIDEO,
    "mime" => "video/x-msvideo",
    "fext" => "avi",
  ),
  "WMV" => array(
    "class" => EFILE_VIDEO,
    "mime" => "video/x-ms-asf",
    "fext" => "wmv",
  ),
  "MP4" => array(
    "class" => EFILE_VIDEO,
    "mime" => "video/mp4",
    "fext" => "mp4",
  ),
  "MOV" => array(
    "class" => EFILE_VIDEO,
    "mime" => "video/quicktime",
    "fext" => "mov",
  ),
  "MKV" => array(
    "class" => EFILE_VIDEO,
    "mime" => "video/x-matroska",
    "fext" => "mkv",
  ),

  // Archives
  "LHA" => array(
    "class" => EFILE_ARCHIVE,
    "mime" => "application/x-lha",
    "fext" => "lha",
    "test" => "^LHarc 1\.x",
  ),
  "ZIP" => array(
    "class" => EFILE_ARCHIVE,
    "mime" => "application/zip",
    "fext" => "zip",
  ),
  "7ZIP" => array(
    "class" => EFILE_ARCHIVE,
    "mime" => "application/x-7z-compressed",
    "fext" => "7z",
  ),
  "RAR" => array(
    "class" => EFILE_ARCHIVE,
    "mime" => "application/x-rar",
    "fext" => "rar",
  ),
  "ARJ" => array(
    "class" => EFILE_ARCHIVE,
    "mime" => "application/x-arj",
    "fext" => "arj",
  ),

  // Final fallback
  "MISC" => array(
    "class" => EFILE_BINARY,
    "mime" => "application/octet-stream",
    "fext" => FALSE,
  ),

  "TEXT" => array(
    "class" => EFILE_TEXT,
    "mime" => "text/plain",
    "fext" => FALSE,
  ),
);


function stFileError($userID, $adminMsg, $userMsg)
{
  stLogError($adminMsg);
  stError(($userID === 0) ? $adminMsg : $userMsg);
  return FALSE;
}


//
// Party infromation system data/variables handling
//
function stReloadDisplayVars()
{
  global $displayVars, $displayVarsChanged;

  $displayVars = array();
  $displayVarsChanged = array();

  if (($res = stExecSQL("SELECT * FROM display_vars")) !== FALSE)
  {
    foreach ($res as $row)
      $displayVars[$row["key"]] = stGetSQLSettingData($row);
  }
}


function stSaveDisplayVars()
{
  global $db, $displayVars, $displayVarsChanged;

  foreach (stExecSQL("SELECT * FROM display_vars") as $item)
  if (isset($displayVarsChanged[$item["key"]]))
  {
    $val = $displayVars[$item["key"]];
    stExecSQL(
      "UPDATE display_vars SET ".stGetSettingSQL($item, $val).
      " WHERE key=".$db->quote($item["key"]));
  }
}


function stDisplayUpdated()
{
  stSetDisplayVar("lastUpdate", time());
}


function stSetDisplayVarUpd($name, $value)
{
  if (stGetDisplayVar($name) != $value)
  {
    stSetDisplayVar($name, $value);
    stDisplayUpdated();
    return TRUE;
  }
  else
    return FALSE;
}


function stGetDisplayVar($name)
{
  global $displayVars;
  if (isset($displayVars[$name]))
    return $displayVars[$name];
  else
    die("No display var for '".$name."'.\n");
}


function stSetDisplayVar($name, $value)
{
  global $displayVars, $displayVarsChanged;
  if (isset($displayVars[$name]))
  {
    $displayVars[$name] = $value;
    $displayVarsChanged[$name] = true;
  }
  else
    die("No display var for '".$name."'.\n");
}


//
// Like stExecSQL(), but throws error messages to "userspace".
//
function stExecSQLCond($sql, $msg = FALSE)
{
  global $db;
  if (($res = stDBExecSQL($db, $sql)) !== FALSE)
  {
    if ($msg !== FALSE)
      stSetStatus(200, $msg);
    return $res;
  }
  else
  {
    stSetStatus(902, "Error in SQL execution.");
    return FALSE;
  }
}


function stPrintAttendee($item, $row, $tr, $full, $edit, $eclass = "")
{
  $id = $item["id"];
  $prefix = "at";

  if ($tr)
  {
    echo
      "  <tr class=\"".($row % 2 == 1 ? "rodd" : "reven").$eclass.
      "\" id=\"attendee".$id."\" ".($full ? "onClick=\"activateAttendee(".$id.")\"" : "").">";
  }

  echo
    stGetTDFormTextInput($edit, 20, SET_LEN_USERNAME, "name", $id, $prefix, $item["name"]).
    stGetTDFormTextInput($edit, 20, SET_LEN_GROUPS, "groups", $id, $prefix, $item["groups"]).
    "<td class=\"regtime\">".date("d.m. H:i", $item["regtime"])."</td>".
    stGetTDFormTextInput($edit, 30, SET_LEN_ONELINER, "oneliner", $id, $prefix, $item["oneliner"], "autocomplete=\"off\"");

  if ($full)
  {
    echo
      stGetTDFormTextInput($edit, 15, SET_LEN_EMAIL, "email", $id, $prefix, $item["email"], "autocomplete=\"off\"").
      stGetTDFormTextInput($edit, 15, SET_LEN_REGHOST, "reghost", $id, $prefix, $item["reghost"], "autocomplete=\"off\"");

    if ($edit)
    {
      echo
        "<td>".
        stGetFormButtonElement($prefix."upd".$id, "","", "Upd", "updateAttendee(".$id.")").
        stGetFormButtonElement($prefix."del".$id, "","", "Del", "deleteAttendee(".$id.")").
        "</td>";
    }
    else
      echo "<td></td>";
  }
  
  if ($tr)
    echo "</tr>\n";
}


function stPrintNewsItem($item)
{
  echo
  "<div class=\"newsItem\" id=\"news".$item["id"]."\">\n".
  "  <h2>".chentities($item["title"])."</h2>\n".
  "  <div class=\"newsText\">".dhentities($item["text"])."</div>\n".
  "  <div class=\"newsAuthor\"><span class=\"newsSig\">-- ".chentities($item["author"])."</span>".
  "<span class=\"newsDate\">".date("d M Y / H:i", $item["utime"])."</span></div>\n".
  "</div>\n";
}


function stGetTDFormTextInput($edit, $size, $len, $name, $id, $prefix, $value, $extra = "")
{
  return
    "<td class=\"".$name."\">".
    ($edit ? stGetFormTextInput($size, $len, $name, $id, $prefix, $value, $extra) : chentities($value)).
    "</td>";
}


function stGetEditFormTextInput($mode, $title, $size, $len, $name, $id, $prefix, $value, $extra = "")
{
  return
    "<div class=\"editControl\"><span class=\"editControlTitle\">".chentities($title)."</span>".
    ($mode ? stGetFormTextInput($size, $len, $name, $id, $prefix, $value, $extra) : chentities($value)).
    "</div>";
}


function stGetEditFormTextArea($mode, $title, $rows, $cols, $name, $id, $prefix, $value, $extra = "")
{
  return
    "<div class=\"editControl\"><span class=\"editControlTitle\">".chentities($title)."</span>".
    stGetFormTextArea($rows, $cols, $name, $id, $prefix, $value, ($mode ? "" : " disabled=\"disabled\" ").$extra).
    "</div>";
}


function stConvSwitchMode(&$str, &$mode, $newMode)
{
  if ($newMode != $mode)
  {
    if ($mode != "")
      $str .= "\n</".$mode.">\n";

    $mode = $newMode;

    if ($mode != "")
      $str .= "<".$mode.">\n";
  }
}


function stConvertCommonDesc($desc, $chent)
{
  $str = "";
  $mode = "";

  foreach (explode("\n", $desc) as $line)
  {
    if (preg_match("/^\s*\s*\*(.+)$/", $line, $m))
    {
      stConvSwitchMode($str, $mode, "ol");
      $str .= "<li>".($chent ? chentities($m[1]) : $m[1])."</li>\n";
    }
    else
    if (preg_match("/^\s*-\s*(.+)$/", $line, $m))
    {
      stConvSwitchMode($str, $mode, "ul");
      $str .= "<li>".($chent ? chentities($m[1]) : $m[1])."</li>\n";
    }
    else
    {
      stConvSwitchMode($str, $mode, "p");
      $str .= ($chent ? chentities($line) : $line);
    }
  }

  stConvSwitchMode($str, $mode, "");

  return $str;
}


function stGetNumberSuffix($val)
{
  switch ($val)
  {
    case  1: return "st";
    case  2: return "nd";
    case  3: return "rd";
    case  4: case 5: case 6:
    case  7: case 8: case 9: return "th";
    default: return "th";
  }
}


function stGenerateUserKey()
{
  global $db;

  // Default keychars
  $keyChars = "abdefghjkmnpqrstwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";

  if (($tmp = stGetSetting("userKeyChars", NULL)) !== NULL)
    $keyChars = $tmp;

  if (($tmp = stGetSetting("userKeyCase", NULL)) !== NULL)
    $keyChars = strtoupper($keyChars);
  
  while (TRUE)
  {
    // Generate one randomized keycode
    $key = "";
    for ($n = 0; $n < stGetSetting("userKeyLength"); $n++)
      $key .= $keyChars[rand() % strlen($keyChars)];

    // Check if it already exists, to avoid duplicates
    // We need custom query code here, because stFetchSQLColumn()
    // won't work due to it returning FALSE in error cases.
    $sql = stPrepareSQL("SELECT * FROM userkeys WHERE key=%s", $key);
    if (($res = @$db->query($sql)) !== FALSE)
    {
      // Did we get results?
      if ($res->fetchColumn() === FALSE)
      {
        // Nope, return key
        return $key;
      }
    }
    else
    {
      stLogSQLError($sql);
      return FALSE;
    }
  }
}


function stCheckRegistrationAvailable()
{
  global $maxAttendeesHard, $maxAttendeesSoft, $numAttendees;

  $maxAttendeesHard = stGetSetting("maxAttendeesHard");
  $maxAttendeesSoft = stGetSetting("maxAttendeesSoft");
  if (($numAttendees = stFetchSQLColumn("SELECT COUNT(*) FROM attendees")) === FALSE)
    $numAttendees = 0;

  return stChkSetting("allowRegister") && ($maxAttendeesHard <= 0 || $numAttendees < $maxAttendeesHard);
}


function stCheckRequireEmail()
{
  return stGetSetting("requireEMail");
}


function stValidateRequestUserData($admin, $id = FALSE)
{
  $res = TRUE;
  $chk = 0;

  if (stChkRequestItem("name", $name,
      array(CHK_ISGT, VT_STR, 0, "Handle / name not given."),
      array(CHK_LTEQ, VT_STR, SET_LEN_USERNAME, "Handle / name is too long, should be less than ".SET_LEN_USERNAME." characters.")))
    $chk++;
  else
    $res = FALSE;

  if (stChkRequestItem("groups", $groups,
      array(CHK_LTEQ, VT_STR, SET_LEN_GROUPS, "Groups are too long, should be less than ".SET_LEN_GROUPS." characters.")))
    $chk++;
  else
    $res = FALSE;

  if (!stChkRequestItem("oneliner", $oneliner,
      array(CHK_LTEQ, VT_STR, SET_LEN_ONELINER, "Oneliner is too long, should be less than ".SET_LEN_ONELINER." characters.")))
    $res = FALSE;

  $email = stGetRequestItem("email");
  if (!$admin && stCheckRequireEmail() && strlen($email) < 4)
  {
    stError("E-mail address not given, or it is too short.");
    $res = FALSE;
  }

  if (strlen($email) > 0 && preg_match("/^[a-z0-9][a-z0-9\+\-\.\%_]*@[a-z0-9.-]+\.[a-z]{2,4}$/i", $email) != 1)
  {
    stError("E-mail address not in proper format.");
    $res = FALSE;
  }
  else
  if (strlen($email) > SET_LEN_EMAIL)
  {
    stError("E-mail address too long, max ".SET_LEN_EMAIL." characters.");
    $res = FALSE;
  }
  else
  if (strlen($email) > 0)
  {
    if ($id !== false)
      // By another ID, if we are updating an entry
      $sql = stPrepareSQL("SELECT * FROM attendees WHERE id<>%d AND email=%s", $id, $email);
    else
      // Or just exists, if adding
      $sql = stPrepareSQL("SELECT * FROM attendees WHERE email=%s", $email);

    if (($data = stFetchSQL($sql)) !== false)
    {
      stError("Someone with the same e-mail address is already registered.");
      $res = FALSE;
    }
  }

  // Check if another user already exists
  if ($chk >= 2)
  {
    if ($id !== false)
      // By another ID, if we are updating an entry
      $sql = stPrepareSQL("SELECT * FROM attendees WHERE id<>%d AND name=%s AND groups=%s", $id, $name, $groups);
    else
      // Or just exists, if adding
      $sql = stPrepareSQL("SELECT * FROM attendees WHERE name=%s AND groups=%s", $name, $groups);

    if (($data = stFetchSQL($sql)) !== false)
    {
      stError("Someone with the same name and groups is already registered.");
      $res = FALSE;
    }
  }
  return $res;

}


function stGetCompoResultsSQL($mode, $compo, $flags)
{
  //
  // Act based on competition type
  //
  switch ($compo["ctype"])
  {
    case COMPO_NORMAL:
      //
      // "Normal" competition, where results are somehow
      // based on points / voting.
      //
      switch ($mode)
      {
        case VOTE_FREELY:
          $sql =
            "SELECT entries.*,SUM(votes.value) AS votesum ".
            "FROM entries ".
            "LEFT JOIN votes ON votes.entry_id=entries.id";
          break;

        case VOTE_ACTIVATE:
          $sql =
            "SELECT entries.*, ".
              "(SELECT SUM(votes.value) FROM votes ".
              "LEFT JOIN userkeys ON votes.key_id=userkeys.id ".
              "WHERE votes.entry_id=entries.id AND userkeys.active<>0) ".
              "AS votesum ".
            "FROM entries";
          break;

        case VOTE_ASSIGN:
          $sql =
            "SELECT entries.*, ".
              "(SELECT SUM(votes.value) FROM votes ".
              "LEFT JOIN userkeys ON votes.key_id=userkeys.id ".
              "LEFT JOIN attendees ON userkeys.id=attendees.key_id ".
              "WHERE votes.entry_id=entries.id AND attendees.key_id<>0) ".
              "AS votesum ".
            "FROM entries";
          break;
      }

      $extra = 
        "GROUP BY entries.id ".
        "ORDER BY votesum DESC";
      break;

    case COMPO_POINTS:
      //
      // Points ..
      //
      $sql = "SELECT entries.*,entries.evalue AS votesum FROM entries";
      $extra = "ORDER BY entries.evalue DESC";
      break;

    case COMPO_ASSIGN:
      //
      // Ascending
      //
      $sql = "SELECT entries.*,entries.evalue AS votesum FROM entries";
      $extra = "ORDER BY entries.evalue ASC";
      break;
  }

  return $sql." ".
    "WHERE entries.compo_id=".$compo["id"]." ".
    (($flags & RFLAG_DISQUALIFIED) ? "" : "AND (entries.flags & ".EFLAG_DISQUALIFIED.")=0 ").
    $extra;
}


function stGetCompoResults($flags)
{
  $userKeyMode = stGetSetting("userKeyMode");
  $out = array();
  $sql = "SELECT * FROM compos ".(($flags & RFLAG_HIDDEN_COMPOS) ? "" : "WHERE visible<>0 ")."ORDER BY name DESC";
  if (($res = stExecSQL($sql)) === false)
    return $out;

  // For each compo that has been set visible
  foreach ($res as $compo)
  {
    // Check if there are any entries for it
    $sql =
      "SELECT COUNT(*) FROM entries ".
      "WHERE compo_id=".$compo["id"].
      (($flags & RFLAG_DISQUALIFIED) ? "" : " AND (entries.flags & ".EFLAG_DISQUALIFIED.")=0");

    if (($nentries = stFetchSQLColumn($sql)) !== FALSE &&
        ($nentries > 0 || ($flags & RFLAG_HIDDEN_COMPOS)))
    {
      // Get voting results by mode
      $sql = stGetCompoResultsSQL($userKeyMode, $compo, $flags);

      $out[$compo["id"]] = $compo;
      $out[$compo["id"]]["results"] = array();
      $prev = FALSE;
      $index = 0;

      foreach (stExecSQL($sql) as $entry)
      {
        if ($entry["votesum"] !== $prev)
          $index++;

        $entry["position"] = $index;
        $out[$compo["id"]]["results"][] = $entry;

        $prev = $entry["votesum"];
      }
    }
  }
  
  return $out;
}


function stGetCompoResultLine($html, $entry, $points, $showAuthor)
{
  $name = stStrChopPad($entry["name"], 40);
  $author = stStrChopPad($entry["author"], 30);

  $out = sprintf("  %s", $html ? chentities($name) : $name);

  // Author?
  if ($showAuthor)
    $out .= sprintf("  by  %s", $html ? chentities($author) : $author);

  // Points?
  if ($points !== FALSE)
    $out .= sprintf(" (%d pts)", $points);

  // Add disqualified flag etc.
  if ($entry["flags"] & EFLAG_DISQUALIFIED)
    $out .= " [DISQ]";

  return $out."\n";
}


function stGetCompoResultsASCIIStr($html, $flags)
{
  $out = "";
  foreach (stGetCompoResults($flags) as $compo)
  {
    // Output compo title / header
    if ($html)
    {
      $out .=
        "<pre>\n".
        "<b> ".chentities($compo["name"])." </b>\n".
        str_repeat("=", strlen($compo["name"]) + 2)."-- - .\n\n";
    }
    else
    {
      $out .=
        " ".$compo["name"]."\n".
        str_repeat("=", strlen($compo["name"]) + 2)."-- - .\n\n";
    }

    // List results for this compo
    $prev = FALSE;
    foreach ($compo["results"] as $entry)
    {
      if ($entry["position"] !== $prev)
        $out .= sprintf("%3d%s.", $entry["position"], stGetNumberSuffix($entry["position"]));
      else
        $out .= "  -''-";

      $out .= stGetCompoResultLine($html, $entry,
        ($compo["ctype"] != COMPO_ASSIGN) ? $entry["votesum"] : FALSE,
        ($compo["ctype"] != COMPO_NORMAL) ? $compo["show_authors"] : TRUE);

      $prev = $entry["position"];
    }
    $out .=
      "\n".($compo["notes"] != "" ? "NOTES: ".chentities($compo["notes"])."\n" : "").
      "\n".($html ? "</pre>\n" : "");
  }

  return $out;
}


function stGetAttendeeRegistrationSQL()
{
  return stPrepareSQL(
    "INSERT INTO attendees (regtime,name,groups,oneliner,email,reghost) VALUES (%d,%S,%S,%S,%S,%s)",
    time(), "name", "groups", "oneliner", "email", $_SERVER["REMOTE_ADDR"]);
}


//
// Filter a path or filename component string
//
function stFilterPathComponent($str)
{
  $tmp = str_replace(array(" / ", "/", "\$", ), array(" of ", " of ", "S", ), $str);
  return preg_replace("/[^a-zA-Z0-9\(\)\,\._-]/", "_", $tmp);
}


//
// Replace or add filename extension
//
function stReplaceFileExt($filename, $fext = FALSE)
{
  if ($fext === FALSE)
    return $filename;

  $dpos = strrpos($filename, "/");
  if (($spos = strrpos($filename, ".")) !== FALSE &&
    ($dpos === FALSE || $dpos < $spos))
    return substr($filename, 0, $spos).$fext;
  else
    return $filename.$fext;
}


//
// Create a path, or URL from specified components
//
function stMakePath($isURL, $hasFilename, $components)
{
  $res = array();

  // If this is URL, the first component is passed as is
  if ($isURL)
  {
    $res[] = array_shift($components);
    $first = FALSE;
  }
  else
    $first = TRUE;

  // Handle each path component
  if (count($components) > 0)
  {
    for ($i = 0; $i < count($components); $i++)
    foreach (explode("/", $components[$i]) as $item)
    {
      if ($item == "..")
        array_pop($res);
      else
      if ($item != "." && ($item != "" || $first))
        $res[] = stFilterPathComponent($item);
      $first = FALSE;
    }
  }

  return implode("/", $res);
}


function stGetEntryPreviewFile($entry, $compo, $previewPath, $previewURL, $type, $addPath)
{
  global $fileTypeData;
  if (!isset($fileTypeData[$type]))
    die("Invalid file type: ".$type);

  $fileBase = sprintf("%03d.%s", $entry["id"], $fileTypeData[$type]["fext"]);
  $fileName = stMakePath(FALSE, TRUE, array($previewPath, $compo["cpath"], $addPath, $fileBase));

  return array(
    "type"   => $type,
    "mime"   => $fileTypeData[$type]["mime"],
    "file"   => $fileName,
    "exists" => @file_exists($fileName),
    "mtime"  => @filemtime($fileName),
    "url"    => stMakePath(TRUE, TRUE, array($previewURL, $compo["cpath"], $addPath, $fileBase)),
  );
}


function stGetPreviewFileData($compo, $entry, &$pdata)
{
  global $fileTypeData;

  if ($compo === false || $entry === false)
    return FALSE;

  $previewPath = stGetSetting("previewPath");
  $previewURL = stGetSetting("previewURL");
  $pdata = array(
    "type" => $compo["preview_type"],
    "valid" => TRUE,
    "exists" => 0,
    "files" => array(),
  );

  if ($entry["preview_id"] != 0 &&
    ($efile = stFetchSQL("SELECT * FROM files WHERE deleted=0 AND id=".$entry["preview_id"])) !== false)
  {
    $pdata["file"] = $efile;
    if (isset($fileTypeData[$efile["filetype"]]["type"]))
      $pdata["type"] = $fileTypeData[$efile["filetype"]]["type"];
  }
  else
  if ($entry["file_id"] == 0)
    $pdata["valid"] = $efile = false;

  switch ($pdata["type"])
  {
    case EFILE_IMAGE:
      $pdata["files"]["image"] = stGetEntryPreviewFile(
        $entry, $compo, $previewPath, $previewURL,
        stGetSetting("previewImageType"), "");

      $pdata["files"]["thumb"] = stGetEntryPreviewFile(
        $entry, $compo, $previewPath, $previewURL,
        stGetSetting("previewThumbType"),
        stGetSetting("thumbnailSubDir"));
      break;
    
    case EFILE_AUDIO:
      foreach (stGetSetting("sampleTypes") as $type => $fdata)
      {
        $pdata["files"][$type] = stGetEntryPreviewFile(
          $entry, $compo, $previewPath, $previewURL,
          $type, "");
      }
      break;

    default:
      return FALSE;
  }

  foreach ($pdata["files"] as $pkey => $pfile)
  {
    if ($pfile["exists"])
      $pdata["exists"]++;

    if (!$pfile["exists"])//
      $pdata["valid"] = FALSE;
  }

  return TRUE;
}


function stPrintPreviewElements($compo, $entry)
{
  if (!stGetPreviewFileData($compo, $entry, $pdata))
    return FALSE;

  switch ($pdata["type"])
  {
    case EFILE_IMAGE:
      if ($pdata["exists"] >= 2)
      {
        if ($pdata["valid"])
        {
          echo
            "<a href=\"".ihentities($pdata["files"]["image"]["url"]).
            "\" onClick=\"return jsShowPreviewImage('".ihentities($pdata["files"]["image"]["url"])."');\">".
            "<img class=\"imagePreview\" src=\"".ihentities($pdata["files"]["thumb"]["url"]).
            "\" alt=\"Preview\" /></a>";
        }
        else
        {
          echo
            "<img class=\"imagePreview\" src=\"".stGetSetting("previewNoImage")."\" alt=\"Preview\" />";
        }
      }
      else
      {
        echo "<div class=\"noPreview\">No preview</div>";
      }
      break;

    case EFILE_AUDIO:
      if ($pdata["valid"])
      {
        echo "<audio controls preload=\"none\" class=\"audioPreview\">";
        foreach ($pdata["files"] as $pkey => $pfile)
        {
          echo "<source src=\"".ihentities($pfile["url"])."\" type=\"".$pfile["mime"]."\">";
        }
        echo "</audio>";
      }
      else
      {
        echo "<div class=\"noPreview\">No preview</div>";
      }
      break;
  }
}


//
// Probe file type information
//
function stProbeFileInfo($filename, $silent = FALSE)
{
  global $fileTypeData;

  // Get file magic info
  if (($finfo = finfo_open()) === false)
  {
    stLogError("Internal error. Failed to initialize finfo().");
    return stError("Internal error, failed to probe file.");
  }

  $sdata = @finfo_file($finfo, $filename, FILEINFO_NONE);
  $smime = @finfo_file($finfo, $filename, FILEINFO_MIME_TYPE);
  finfo_close($finfo);

  // Did we get anything?
  if ($sdata === FALSE || $smime === FALSE)
  {
    stLogError("Failed to probe file '".$filename."'.");
    return stError("Failed to probe file.");
  }

  // Match through our supported types ..
  foreach ($fileTypeData as $fid => $fdata)
  {
    $fdata["id"] = $fid;
    if (isset($fdata["test"]) && preg_match("/".$fdata["test"]."/", $sdata))
      return $fdata;
  }

  // Fall back to mime types
  foreach ($fileTypeData as $fid => $fdata)
  {
    $fdata["id"] = $fid;
    if ($fdata["mime"] == $smime)
      return $fdata;
  }

  if ($silent)
    return FALSE;

  stLogError("File '".$filename."' (".$smime.") did not match any defined file types [".$sdata."].");
  return stError("No matching defined file type found for '".$smime."'.");
}


//
// File table entry adding
//
function stAddFileEntry($entry, $type, $origName, $fileSize, $fileExt, $fileType, $uploaderID)
{
  $field = (($type == "preview") ? "preview" : "file")."_id";

  // Create new file entry
  $sql = stPrepareSQL(
    "INSERT INTO files (origname,filetype,filesize,entry_id,uploader_id,uploadtype,utime) ".
    "VALUES (%s,%s,%d,%d,%d,%s,%d)",
    $origName, $fileType, $fileSize, $entry["id"], $uploaderID, $type, time());

  if (($fileID = stExecSQLInsert($sql)) === false)
    return stFileError($uploaderID,
      "Failed to add new ".$type." file for entry #".$entry["id"]." '".$origName."'.",
      "Internal error. Failed to add new file.");

  // Compute storage filename
  $fileName = sprintf("%03d-%s--%s%s_(%d).%s",
    $entry["id"],
    stFilterPathComponent($entry["author"]),
    stFilterPathComponent($entry["name"]),
    ($type == "preview" ? "_preview" : ""),
    $fileID,
    $fileExt);

  // Update entry with generated filename
  $sql = stPrepareSQL("UPDATE files SET filename=%s WHERE id=%d", $fileName, $fileID);
  if (stExecSQL($sql) === false)
    return stFileError($uploaderID,
      "Failed to update newly created files entry #".$fileID." with generated filename '".$fileName."'!",
      "Internal error. Failed to add new file.");

  // Update entry's reference
  $sql = stPrepareSQL("UPDATE entries SET ".$field."=%d WHERE id=%d", $fileID, $entry["id"]);
  if (stExecSQL($sql) === false)
    return stFileError($uploaderID,
      "Failed to update entry #".$entry["id"]." ".$type." ID!",
      "Internal error. Failed to add new file.");

  // Return file entry
  if (($res = stFetchSQL("SELECT * FROM files WHERE id=".$fileID)) === false)
    return stFileError($uploaderID,
      "Failed to fetch newly generated files entry #".$fileID,
      "Internal error. Failed to add new file.");

  return $res;
}


//
// File upload handling
//
function stHandleGenericFileUpload($userID)
{
  global $errorSet;

  // Check basics
  if (!stChkRequestItem("type", $uploadType,
        array(CHK_TYPE, VT_STR, "Invalid upload type."),
        array(CHK_ARRAY_VAL, array("entry", "preview"), "Invalid upload type class '%1'."))
      ||
      !stChkRequestItem("entry_id", $entryID,
        array(CHK_TYPE, VT_INT, "Invalid entry ID.")))
    return FALSE;

  // Check entry existence
  if (($entry = stFetchSQL("SELECT * FROM entries WHERE id=".$entryID)) === false)
    return stFileError($userID,
      "Entry ID #".$entryID." does not exist in the entries table?",
      "Entry does not exist??");

  if (($compo = stFetchSQL("SELECT * FROM compos WHERE id=".$entry["compo_id"])) === false)
    return stFileError($userID,
      "Compo ID #".$entry["compo_id"]." in entry ID #".$entryID." does not exist!",
      "Compo does not exist??");

  // Check target path existence / writability
  $dstPath = stMakePath(FALSE, FALSE, array(stGetSetting("entryPath"), $compo["cpath"]));
  if (!file_exists($dstPath))
    return stFileError($userID,
      "Path '".$dstPath."' for compo ID #".$entry["compo_id"]." does not exist.",
      "The directory for entry's compo does not exist!");
  
  $dstPerms = fileperms($dstPath);
  if (($dstPerms & 0x4000) === 0)
    return stFileError($userID,
      "Path for entry's compo '".$dstPath."' is not a directory.",
      "Path for entry's compo is not a directory?");
  
  $needPerms = 0x0100 | 0x0080 | 0x0040;
  if (($dstPerms & $needPerms) !== $needPerms)
    return stFileError($userID,
      "Path for entry's compo '".$dstPath."' does not have sufficient permissions.",
      "Path for entry's compo has no sufficient permissions.");

  // Check permissions for non-admins
  if ($userID != 0)
  {
    // Check if the user even exists, just in case
    if (($user = stFetchSQL("SELECT * FROM attendees WHERE id=".$userID)) === false)
      return stFileError($userID,
        "User ID #".$userID." does not exist??",
        "You do not exist. Go away.");

    if ($entry["owner_id"] != $userID)
      return stFileError($userID,
        "User ID #".$userID." attempted to upload file to an entry that is not owned by him (@ ".$_SERVER["REMOTE_ADDR"].")",
        "Attempted to upload file to entry not owned by user.");
  }

  // Check file status data
  $maxFileSize = stGetSetting($uploadType."MaxSize");

  $index = $uploadType."ToUpload".$entryID;
  $fileSize = $_FILES[$index]["size"];
  $tmpFilename = $_FILES[$index]["tmp_name"];
  $orgFilename = $_FILES[$index]["name"];

  if ($fileSize > $maxFileSize)
    stError("File size ".$fileSize." exceeds FAPWeb's size of ".$maxFileSize." bytes for ".$uploadType." uploads.");

  if ($fileSize < 16)
    stError("File size ".$fileSize." is less than 16 bytes. This can't be right.");

  switch ($_FILES[$index]["error"])
  {
    case UPLOAD_ERR_INI_SIZE:
      stError("File size exceeds PHP's max upload size.");
      break;

    case UPLOAD_ERR_PARTIAL:
      stError("File only partially uploaded.");
      break;

    case UPLOAD_ERR_NO_FILE:
      stError("No file data received!");
      break;

    case UPLOAD_ERR_NO_TMP_DIR:
      stError("Internal error: Temporary file directory not available!");
      break;

    case UPLOAD_ERR_CANT_WRITE:
      stError("Internal error: PHP could not write the file to disk.");
      break;

    case UPLOAD_ERR_OK:
      break;

    default:
      stError("Unknown PHP file error occured.");
      break;
  }

  if ($errorSet)
    return FALSE;


  // Check file properties ..
  if (($fileInfo = stProbeFileInfo($tmpFilename)) === false)
    return FALSE;

  if ($uploadType == "preview" && !isset($fileInfo["type"]))
    return stError("Preview file upload is not one of the supported preview file types.");
  
  // Get original extension
  if (($fileExt = $fileInfo["fext"]) === false)
  {
    $fileExt = "bin";
    if (($epos = strrpos($orgFilename, ".")) !== false)
      $fileExt = substr($orgFilename, $epos + 1);
  }

  // Update current or add new file entry
  if (($fentry = stAddFileEntry($entry, $uploadType, $orgFilename, $fileSize, $fileExt, $fileInfo["id"], $userID)) === false)
    return FALSE;

  // Set permissions before moving the file
  if (chmod($tmpFilename, stGetSetting($uploadType."PathPerms")) === false)
    return stFileError($userID,
      "Could not set permissions for uploaded file '".$tmpFilename."'.",
      "Internal error. Could not set permissions for uploaded file. Contact site admins.");

  // Move file to its destination
  $dstFilename = stMakePath(FALSE, TRUE, array(stGetSetting("entryPath"), $compo["cpath"], $fentry["filename"]));
  if (@move_uploaded_file($tmpFilename, $dstFilename) === false)
    return stFileError($userID,
      "Could not move uploaded file '".$tmpFilename."' to '".$dstFilename."'.",
      "Internal error. Deploying uploaded file failed! Contact site admins.");

  return TRUE;
}


function stNormalizeListSlideOrder($list_id)
{
}


function stAddCompoEntry($compo, $uid)
{
  switch ($compo["ctype"])
  {
    case COMPO_NORMAL:
      $sql = stPrepareSQL(
      "INSERT INTO entries (name,author,compo_id,info,notes,preview_type,utime,owner_id) VALUES (%S,%S,%d,%Q,%Q,%D,%d,%d)",
      "name", "author", $compo["id"], "info", "notes", "preview_type", time(), $uid);
      break;
    
    case COMPO_POINTS:
    case COMPO_ASSIGN:
      $sql = stPrepareSQL(
      "INSERT INTO entries (name,compo_id,notes,evalue,utime,owner_id) VALUES (%S,%d,%Q,%D,%d,%d)",
      "name", $compo["id"], "notes", "evalue", time(), $uid);
      break;
  }

  return stExecSQLCond($sql, "OK, entry added.");
}


function stDeleteCompoEntry($id)
{
  // Delete entry itself
  $sql = stPrepareSQL("DELETE FROM entries WHERE id=%d", $id);
  stExecSQLCond($sql, "OK, entry ".$id." deleted.");

  // Delete votes for the entry
  $sql = stPrepareSQL("DELETE FROM votes WHERE entry_id=%d", $id);
  stExecSQLCond($sql, "OK, entry ".$id." votes deleted.");
  
  // Mark related files to be deleted
  $sql = stPrepareSQL("UPDATE files SET deleted=%d WHERE entry_id=%d", 1, $id);
  stExecSQLCond($sql, "OK, entry ".$id." files marked.");
}


?>