<?php
define(&#039;THIS_SCRIPT&#039;, &#039;ft_story_upload&#039;);
require_once(&#039;./global.php&#039;);

/*
  FT Story Upload

  - Dosya Upload:
      * JPG / PNG / GIF  (max 5 MB)
      * MP4              (max 15 MB)
  - YouTube Bağlantısı
      * Sadece youtube.com / youtu.be / youtube-nocookie.com domainleri
      * DB: mediatype = 4, filepath = "yt:VIDEOID"

  - Görünürlük:
      1 = Forum Üyeleri
      3 = Sadece Arkadaşlar

  Geliştirici: atmaca
*/

// ------------------------- Helpers -------------------------

function ft_utf8ize($mixed, $fromCharset)
{
	if (is_array($mixed))
	{
		foreach ($mixed as $k => $v)
		{
			$mixed[$k] = ft_utf8ize($v, $fromCharset);
		}
		return $mixed;
	}
	if (is_string($mixed))
	{
		if (!strcasecmp($fromCharset, &#039;utf-8&#039;))
		{
			return $mixed;
		}

		if (function_exists(&#039;mb_convert_encoding&#039;))
		{
			return @mb_convert_encoding($mixed, &#039;UTF-8&#039;, $fromCharset);
		}

		return utf8_encode($mixed);
	}

	return $mixed;
}

function ft_json_exit($arr)
{
	global $stylevar;

	$fromCharset = &#039;UTF-8&#039;;
	if (!empty($stylevar[&#039;charset&#039;]))
	{
		$fromCharset = $stylevar[&#039;charset&#039;];
	}

	$json = json_encode($arr, JSON_UNESCAPED_UNICODE);
	if ($json === false)
	{
		$arr  = ft_utf8ize($arr, $fromCharset);
		$json = json_encode($arr, JSON_UNESCAPED_UNICODE);
	}

	if (function_exists(&#039;ob_get_length&#039;) && ob_get_length())
	{
		@ob_clean();
	}

	header(&#039;Content-Type: application/json; charset=UTF-8&#039;);
	header(&#039;Cache-Control: no-store, no-cache, must-revalidate, max-age=0&#039;);

	if ($json === false)
	{
		echo json_encode(array(
			&#039;ok&#039;    => 0,
			&#039;error&#039; => &#039;JSON üretilemedi.&#039;,
			&#039;code&#039;  => json_last_error(),
		));
		exit;
	}

	echo $json;
	exit;
}

function ft_upload_error_message($code)
{
	$map = array(
		1 => &#039;Dosya çok büyük (php.ini upload_max_filesize).&#039;,
		2 => &#039;Dosya çok büyük (form MAX_FILE_SIZE).&#039;,
		3 => &#039;Dosya kısmen yüklendi.&#039;,
		4 => &#039;Dosya seçilmedi.&#039;,
		6 => &#039;Geçici klasör (tmp) bulunamadı.&#039;,
		7 => &#039;Dosya diske yazılamadı.&#039;,
		8 => &#039;Bir PHP eklentisi yüklemeyi durdurdu.&#039;,
	);

	return isset($map[$code]) ? $map[$code] : (&#039;Yükleme hatası (kod: &#039; . intval($code) . &#039;).&#039;);
}

function ft_fail($code, $ajax)
{
	$msg = $code;

	if (is_string($code))
	{
		if (strpos($code, &#039;upload_hata_&#039;) === 0)
		{
			$err = intval(substr($code, strlen(&#039;upload_hata_&#039;)));
			$msg = ft_upload_error_message($err);
		}
		else
		{
			$map = array(
				&#039;bad_token&#039;                 => &#039;Oturum doğrulaması başarısız. Sayfayı yenileyip tekrar deneyin.&#039;,
				&#039;dosya_gelmedi&#039;             => &#039;Dosya seçilmedi.&#039;,
				&#039;dosya_5mb_ustu&#039;            => &#039;Görsel boyutu 5 MB sınırını aşıyor.&#039;,
				&#039;video_15mb_ustu&#039;           => &#039;Video boyutu 15 MB sınırını aşıyor.&#039;,
				&#039;gecersiz_resim&#039;            => &#039;Geçersiz resim dosyası.&#039;,
				&#039;yalnizca_jpg_png_gif&#039;      => &#039;Sadece JPG / PNG / GIF kabul ediliyor.&#039;,
				&#039;yalnizca_mp4&#039;              => &#039;Sadece MP4 video kabul ediliyor.&#039;,
				&#039;yalnizca_youtube_link&#039;     => &#039;Sadece YouTube bağlantıları kabul ediliyor.&#039;,
				&#039;upload_klasoru_yazilamaz&#039;  => &#039;Sunucuda yükleme klasörü yazılabilir değil.&#039;,
				&#039;dosya_tasinamadi&#039;          => &#039;Dosya sunucuya kaydedilemedi.&#039;,
				&#039;not_logged_in&#039;             => &#039;Bu işlem için oturum açmalısınız.&#039;,
			);

			if (isset($map[$code]))
			{
				$msg = $map[$code];
			}
		}
	}

	if ($ajax)
	{
		ft_json_exit(array(
			&#039;ok&#039;    => 0,
			&#039;error&#039; => $msg,
			&#039;code&#039;  => $code,
		));
	}

	die($msg);
}

/* Visibility normalize helper */
function ft_normalize_visibility($value)
{
	$value = intval($value);

	// Sadece 1 = kayıtlı üyeler, 3 = arkadaşlar
	if ($value === 3)
	{
		return 3;
	}

	return 1;
}

/* Strong random token */
function ft_token_hex($bytes = 16)
{
	$bytes = max(8, intval($bytes));

	if (function_exists(&#039;random_bytes&#039;))
	{
		try {
			return bin2hex(random_bytes($bytes));
		} catch (Exception $e) {}
	}

	if (function_exists(&#039;openssl_random_pseudo_bytes&#039;))
	{
		$b = openssl_random_pseudo_bytes($bytes);
		if ($b !== false)
		{
			return bin2hex($b);
		}
	}

	// Son çare
	return substr(md5(uniqid(&#039;&#039;, true)), 0, $bytes * 2);
}

/* Quick MP4 signature check for octet-stream cases */
function ft_looks_like_mp4($path)
{
	$fh = @fopen($path, &#039;rb&#039;);
	if (!$fh) return false;
	$head = @fread($fh, 128);
	@fclose($fh);

	// MP4 container’da genellikle "ftyp" bulunur
	return ($head !== false && strpos($head, &#039;ftyp&#039;) !== false);
}

/* Parse YouTube URL -> 11-char video id (only allowed domains) */
function ft_parse_youtube_id($url)
{
	$url = trim((string)$url);
	if ($url === &#039;&#039;) return false;

	$p = @parse_url($url);
	if (!$p || empty($p[&#039;host&#039;])) return false;

	$host = strtolower($p[&#039;host&#039;]);
	$host = preg_replace(&#039;/^www\./&#039;, &#039;&#039;, $host);

	$allowed = array(&#039;youtube.com&#039;, &#039;m.youtube.com&#039;, &#039;youtu.be&#039;, &#039;youtube-nocookie.com&#039;);
	if (!in_array($host, $allowed, true)) return false;

	$path = isset($p[&#039;path&#039;]) ? trim($p[&#039;path&#039;], &#039;/&#039;) : &#039;&#039;;
	$id = &#039;&#039;;

	if ($host === &#039;youtu.be&#039;)
	{
		// youtu.be/VIDEOID
		$parts = explode(&#039;/&#039;, $path);
		$id = isset($parts[0]) ? $parts[0] : &#039;&#039;;
	}
	else
	{
		// youtube.com/watch?v=VIDEOID
		if (!empty($p[&#039;query&#039;]))
		{
			parse_str($p[&#039;query&#039;], $q);
			if (!empty($q[&#039;v&#039;])) $id = (string)$q[&#039;v&#039;];
		}

		// youtube.com/shorts/VIDEOID veya /embed/VIDEOID
		if (!$id && preg_match(&#039;~^(shorts|embed)/([A-Za-z0-9_-]{11})~&#039;, $path, $m))
		{
			$id = $m[2];
		}
	}

	if (!preg_match(&#039;~^[A-Za-z0-9_-]{11}$~&#039;, $id)) return false;
	return $id;
}

// ------------------------- Input -------------------------

$vbulletin->input->clean_array_gpc(&#039;p&#039;, array(
	&#039;do&#039;            => TYPE_STR,   // upload | youtube
	&#039;securitytoken&#039; => TYPE_STR,
	&#039;ajax&#039;          => TYPE_UINT,
	&#039;visibility&#039;    => TYPE_UINT,
	&#039;youtube_url&#039;   => TYPE_STR,   // do=youtube için
	&#039;text_body&#039; => TYPE_STR,      //  Metin hikaye için
    &#039;bg_id&#039;     => TYPE_UINT, 	 //   Metin hikaye için
	&#039;confirm_censor&#039; => TYPE_UINT,
));

$do   = $vbulletin->GPC[&#039;do&#039;];
$ajax = ($vbulletin->GPC[&#039;ajax&#039;] ? true : false);

if (!$vbulletin->userinfo[&#039;userid&#039;])
{
	if ($ajax)
	{
		ft_fail(&#039;not_logged_in&#039;, true);
	}
	print_no_permission();
}

$db =& $vbulletin->db;

// Ayarlar (MVP)
$MAX_IMG_BYTES = 5  * 1024 * 1024;  // 5 MB
$MAX_VID_BYTES = 15 * 1024 * 1024;  // 15 MB
$EXPIRE_SECS   = 24 * 60 * 60;      // 24 saat
$UPLOAD_DIR    = DIR . &#039;/ft_story_uploads&#039;;
$UPLOAD_URL    = &#039;ft_story_uploads&#039;;

// ------------------------- Token verify -------------------------

function ft_verify_token_or_fail($ajax)
{
	global $vbulletin;

	$token = $vbulletin->GPC[&#039;securitytoken&#039;];
	$raw   = (!empty($vbulletin->userinfo[&#039;securitytoken_raw&#039;]) ? $vbulletin->userinfo[&#039;securitytoken_raw&#039;] : &#039;&#039;);

	if (!$token || $token === &#039;guest&#039;)
	{
		ft_fail(&#039;bad_token&#039;, $ajax);
	}
	if (!verify_security_token($token, $raw))
	{
		ft_fail(&#039;bad_token&#039;, $ajax);
	}
}

// ------------------------- Main actions -------------------------

if ($_SERVER[&#039;REQUEST_METHOD&#039;] === &#039;POST&#039;)
{
	// dosya upload
	if ($do === &#039;upload&#039;)
	{
		ft_verify_token_or_fail($ajax);

		if (empty($_FILES[&#039;storyfile&#039;]) || !is_uploaded_file($_FILES[&#039;storyfile&#039;][&#039;tmp_name&#039;]))
		{
			ft_fail(&#039;dosya_gelmedi&#039;, $ajax);
		}

		$f = $_FILES[&#039;storyfile&#039;];

		if (!empty($f[&#039;error&#039;]))
		{
			ft_fail(&#039;upload_hata_&#039; . intval($f[&#039;error&#039;]), $ajax);
		}

		$tmp = $f[&#039;tmp_name&#039;];

		// ext + type detect
		$ext = strtolower(pathinfo((string)$f[&#039;name&#039;], PATHINFO_EXTENSION));
		$is_video = ($ext === &#039;mp4&#039;);

		$mediatype = 1;
		$width = 0;
		$height = 0;

		if ($is_video)
		{
			if ($f[&#039;size&#039;] <= 0 || $f[&#039;size&#039;] > $MAX_VID_BYTES)
			{
				ft_fail(&#039;video_15mb_ustu&#039;, $ajax);
			}

			$mime = &#039;&#039;;
			if (function_exists(&#039;finfo_open&#039;))
			{
				$fi = @finfo_open(FILEINFO_MIME_TYPE);
				if ($fi)
				{
					$mime = @finfo_file($fi, $tmp);
					@finfo_close($fi);
				}
			}

			// bazı sunucular octet-stream döndürebilir -> imza kontrolü
			if ($mime && $mime !== &#039;video/mp4&#039;)
			{
				if ($mime === &#039;application/octet-stream&#039; && ft_looks_like_mp4($tmp))
				{
					// kabul
				}
				else
				{
					ft_fail(&#039;yalnizca_mp4&#039;, $ajax);
				}
			}

			$mediatype = 2;
			$ext = &#039;mp4&#039;;
		}
		else
		{
			// IMAGE
			if ($f[&#039;size&#039;] <= 0 || $f[&#039;size&#039;] > $MAX_IMG_BYTES)
			{
				ft_fail(&#039;dosya_5mb_ustu&#039;, $ajax);
			}

			$img = @getimagesize($tmp);
			if (!$img || empty($img[2]))
			{
				ft_fail(&#039;gecersiz_resim&#039;, $ajax);
			}

			// Sadece JPG/PNG/GIF
			$allowed = array(
				IMAGETYPE_JPEG => &#039;jpg&#039;,
				IMAGETYPE_PNG  => &#039;png&#039;,
				IMAGETYPE_GIF  => &#039;gif&#039;,
			);

			$type = intval($img[2]);
			if (!isset($allowed[$type]))
			{
				ft_fail(&#039;yalnizca_jpg_png_gif&#039;, $ajax);
			}

			$ext    = $allowed[$type];
			$width  = intval($img[0]);
			$height = intval($img[1]);
		}

		if (!is_dir($UPLOAD_DIR))
		{
			@mkdir($UPLOAD_DIR, 0755, true);
		}
		if (!is_dir($UPLOAD_DIR) || !is_writable($UPLOAD_DIR))
		{
			ft_fail(&#039;upload_klasoru_yazilamaz&#039;, $ajax);
		}

		$userid     = intval($vbulletin->userinfo[&#039;userid&#039;]);
		$visibility = ft_normalize_visibility($vbulletin->GPC[&#039;visibility&#039;]);

		// Daha güçlü ve daha az tahmin edilebilir isim
		$token = ft_token_hex(16); // 32 hex
		$fname = &#039;s_&#039; . TIMENOW . &#039;_&#039; . $token . &#039;.&#039; . $ext;

		$dest_abs = $UPLOAD_DIR . &#039;/&#039; . $fname;
		$dest_rel = $UPLOAD_URL . &#039;/&#039; . $fname;

		if (!move_uploaded_file($tmp, $dest_abs))
		{
			ft_fail(&#039;dosya_tasinamadi&#039;, $ajax);
		}

		@chmod($dest_abs, 0644);

		$dateline   = TIMENOW;
		$expiretime = TIMENOW + $EXPIRE_SECS;

		$db->query_write("
			INSERT INTO ft_story (userid, dateline, expiretime, state, privacy, visibility)
			VALUES ($userid, $dateline, $expiretime, 1, 0, $visibility)
		");

		$storyid      = intval($db->insert_id());
		$filepath_sql = $db->escape_string($dest_rel);

		$db->query_write("
			INSERT INTO ft_story_media (storyid, mediatype, filepath, thumbpath, filesize, width, height, dateline)
			VALUES ($storyid, " . intval($mediatype) . ", &#039;$filepath_sql&#039;, &#039;&#039;, " . intval($f[&#039;size&#039;]) . ", " . intval($width) . ", " . intval($height) . ", $dateline)
		");

		// kullanıcı adı + avatar
		$u = $db->query_first("
			SELECT u.username, u.avatarid, u.avatarrevision, a.avatarpath
			FROM user u
			LEFT JOIN avatar a ON (a.avatarid = u.avatarid)
			WHERE u.userid = $userid
			LIMIT 1
		");

		$username = (!empty($u[&#039;username&#039;]) ? $u[&#039;username&#039;] : $vbulletin->userinfo[&#039;username&#039;]);
		if (!empty($u[&#039;avatarpath&#039;]))
		{
			$avatar = $u[&#039;avatarpath&#039;];
		}
		else
		{
			$avatar = &#039;image.php?u=&#039; . $userid . &#039;&dateline=&#039; . intval($u[&#039;avatarrevision&#039;]);
		}

		// AJAX ise JSON dön
		if ($ajax)
		{
			ft_json_exit(array(
				&#039;ok&#039;         => 1,
				&#039;storyid&#039;    => $storyid,
				&#039;url&#039;        => $dest_rel,
				&#039;visibility&#039; => $visibility,
				&#039;mediatype&#039;  => $mediatype,

				&#039;userid&#039;     => $userid,
				&#039;username&#039;   => $username,
				&#039;avatar&#039;     => $avatar,

				&#039;story&#039; => array(
					&#039;storyid&#039;    => $storyid,
					&#039;userid&#039;     => $userid,
					&#039;username&#039;   => $username,
					&#039;avatar&#039;     => $avatar,
					&#039;url&#039;        => $dest_rel,
					&#039;image&#039;      => $dest_rel,
					&#039;visibility&#039; => $visibility,
					&#039;mediatype&#039;  => $mediatype,
				),
			));
		}

		// Non-AJAX: basit çıktı
		echo &#039;OK&#039;;
		exit;
	}

	// YouTube link story
	if ($do === &#039;youtube&#039;)
	{
		ft_verify_token_or_fail($ajax);

		$ytid = ft_parse_youtube_id($vbulletin->GPC[&#039;youtube_url&#039;]);
		if (!$ytid)
		{
			ft_fail(&#039;yalnizca_youtube_link&#039;, $ajax);
		}

		$userid     = intval($vbulletin->userinfo[&#039;userid&#039;]);
		$visibility = ft_normalize_visibility($vbulletin->GPC[&#039;visibility&#039;]);

		$dateline   = TIMENOW;
		$expiretime = TIMENOW + $EXPIRE_SECS;

		$db->query_write("
			INSERT INTO ft_story (userid, dateline, expiretime, state, privacy, visibility)
			VALUES ($userid, $dateline, $expiretime, 1, 0, $visibility)
		");

		$storyid = intval($db->insert_id());

		// filepath içine "yt:ID" saklıyoruz (viewer/json tarafı bunu yorumlayacak)
		$filepath_sql = $db->escape_string(&#039;yt:&#039; . $ytid);

		$db->query_write("
			INSERT INTO ft_story_media (storyid, mediatype, filepath, thumbpath, filesize, width, height, dateline)
			VALUES ($storyid, 4, &#039;$filepath_sql&#039;, &#039;&#039;, 0, 0, 0, $dateline)
		");

		$u = $db->query_first("
			SELECT u.username, u.avatarid, u.avatarrevision, a.avatarpath
			FROM user u
			LEFT JOIN avatar a ON (a.avatarid = u.avatarid)
			WHERE u.userid = $userid
			LIMIT 1
		");

		$username = (!empty($u[&#039;username&#039;]) ? $u[&#039;username&#039;] : $vbulletin->userinfo[&#039;username&#039;]);
		if (!empty($u[&#039;avatarpath&#039;]))
		{
			$avatar = $u[&#039;avatarpath&#039;];
		}
		else
		{
			$avatar = &#039;image.php?u=&#039; . $userid . &#039;&dateline=&#039; . intval($u[&#039;avatarrevision&#039;]);
		}

		if ($ajax)
		{
			ft_json_exit(array(
				&#039;ok&#039;         => 1,
				&#039;storyid&#039;    => $storyid,
				&#039;visibility&#039; => $visibility,
				&#039;mediatype&#039;  => 4,
				&#039;youtube_id&#039; => $ytid,

				&#039;userid&#039;     => $userid,
				&#039;username&#039;   => $username,
				&#039;avatar&#039;     => $avatar,

				&#039;story&#039; => array(
					&#039;storyid&#039;    => $storyid,
					&#039;userid&#039;     => $userid,
					&#039;username&#039;   => $username,
					&#039;avatar&#039;     => $avatar,
					&#039;visibility&#039; => $visibility,
					&#039;mediatype&#039;  => 4,
					&#039;youtube_id&#039; => $ytid,
				),
			));
		}

		echo &#039;OK&#039;;
		exit;
	}
// Text story
if ($do === &#039;text&#039;)
{
  ft_verify_token_or_fail($ajax);
  $db->query_write("SET NAMES utf8mb4");

  // Metin + bg
  $text = trim((string)$vbulletin->GPC[&#039;text_body&#039;]);
  $bg   = intval($vbulletin->GPC[&#039;bg_id&#039;]);
  if ($bg < 1 || $bg > 12) $bg = 1;

  if ($text === &#039;&#039;)
  {
    ft_fail(&#039;bos_metin&#039;, $ajax);
  }

  // uzunluk limiti (MVP)
  if (function_exists(&#039;mb_strlen&#039;)) {
    if (mb_strlen($text, &#039;UTF-8&#039;) > 240) ft_fail(&#039;metin_cok_uzun&#039;, $ajax);
  } else {
    if (strlen($text) > 240) ft_fail(&#039;metin_cok_uzun&#039;, $ajax);
  }
// --- Censor check ---
$confirm = intval($vbulletin->GPC[&#039;confirm_censor&#039;]);

if (function_exists(&#039;fetch_censored_text&#039;))
{
  $censored = fetch_censored_text($text);

  // Sansür uygulanacaksa ilk istekte uyar, kaydetme
  if ($censored !== $text && !$confirm)
  {
    ft_json_exit(array(
      &#039;ok&#039; => 0,
      &#039;code&#039; => &#039;censor_warning&#039;,
      &#039;error&#039; => &#039;Metin yasaklı sözcük içeriyor. Devam ederseniz sansür uygulanacak.&#039;
    ));
  }

  // Kullanıcı onayladıysa sansürlü metni kaydet
  if ($censored !== $text && $confirm)
  {
    $text = $censored;
  }
}
  // emoji için bu request&#039;te utf8mb4
  $db->query_write("SET NAMES utf8mb4");

  $userid     = intval($vbulletin->userinfo[&#039;userid&#039;]);
  $visibility = ft_normalize_visibility($vbulletin->GPC[&#039;visibility&#039;]);

  $dateline   = TIMENOW;
  $expiretime = TIMENOW + $EXPIRE_SECS;

  $db->query_write("
    INSERT INTO ft_story (userid, dateline, expiretime, state, privacy, visibility)
    VALUES ($userid, $dateline, $expiretime, 1, 0, $visibility)
  ");
  $storyid = intval($db->insert_id());

  // media kaydı (mediatype=3)
  $db->query_write("
    INSERT INTO ft_story_media (storyid, mediatype, filepath, thumbpath, filesize, width, height, dateline)
    VALUES ($storyid, 3, &#039;&#039;, &#039;&#039;, 0, 0, 0, $dateline)
  ");

  // text kaydı (utf8mb4 tabloda)
  $text_sql = $db->escape_string($text);
  $db->query_write("
    INSERT INTO ft_story_text (storyid, text_body, bg_id, dateline)
    VALUES ($storyid, &#039;$text_sql&#039;, " . intval($bg) . ", $dateline)
  ");

  // avatar/username
  $u = $db->query_first("
    SELECT u.username, u.avatarid, u.avatarrevision, a.avatarpath
    FROM user u
    LEFT JOIN avatar a ON (a.avatarid = u.avatarid)
    WHERE u.userid = $userid
    LIMIT 1
  ");

  $username = (!empty($u[&#039;username&#039;]) ? $u[&#039;username&#039;] : $vbulletin->userinfo[&#039;username&#039;]);
  $avatar = (!empty($u[&#039;avatarpath&#039;]))
    ? $u[&#039;avatarpath&#039;]
    : (&#039;image.php?u=&#039; . $userid . &#039;&dateline=&#039; . intval($u[&#039;avatarrevision&#039;]));

  if ($ajax)
  {
    ft_json_exit(array(
      &#039;ok&#039;         => 1,
      &#039;storyid&#039;    => $storyid,
      &#039;visibility&#039; => $visibility,
      &#039;mediatype&#039;  => 3,
      &#039;text_body&#039;  => $text,
      &#039;bg_id&#039;      => $bg,

      &#039;userid&#039;     => $userid,
      &#039;username&#039;   => $username,
      &#039;avatar&#039;     => $avatar,

      &#039;story&#039; => array(
        &#039;storyid&#039;    => $storyid,
        &#039;userid&#039;     => $userid,
        &#039;username&#039;   => $username,
        &#039;avatar&#039;     => $avatar,
        &#039;visibility&#039; => $visibility,
        &#039;mediatype&#039;  => 3,
        &#039;text_body&#039;  => $text,
        &#039;bg_id&#039;      => $bg,
      ),
    ));
  }

  echo &#039;OK&#039;;
  exit;
}
	// POST ama bilinmeyen action
	ft_fail(&#039;gecersiz_islem&#039;, $ajax);
}

Add a code snippet to your website: www.paste.org