<?php

/**
 * JEvents Component for Joomla! 3.x
 *
 * @version     $Id: csvToiCal.php 3285 2012-02-21 14:56:25Z geraintedwards $
 * @package     JEvents
 * @copyright   Copyright (C) 2008-2025 GWESystems Ltd, 2006-2008 JEvents Project Group
 * @license     GNU/GPLv2, see http://www.gnu.org/licenses/gpl-2.0.html
 * @link        http://www.jevents.net
 */
// no direct access
defined('_JEXEC') or die('Restricted access');

include_once("csvLine.php");

use Joomla\CMS\Language\Text;
use Joomla\CMS\Factory;
use Joomla\String\StringHelper;

if (!function_exists('str_getcsv'))
{

	function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = null, $eol = null)
	{

		$temp = fopen("php://memory", "rw");
		fwrite($temp, $input);
		fseek($temp, 0);
		$r = fgetcsv($temp, 4096, $delimiter, $enclosure);
		fclose($temp);

		return $r;

	}

}

/**
 * Class used for CSV transformation to iCal format
 */
class CsvToiCal
{

	var $rawText;
	var $file;
	var $columnSeparator;
	var $colsOrder = array();
	var $colsNum = 0; // to check if every line has right number of columns
	var $tmpFileName;
	var $tmpfile;
	var $timezone = "UTC"; // default timezone
	var $data;

	/**
	 * default constructor
	 *
	 * @param file filename to process
	 * @param columnSeparator separator of columns in CSV file - default ,
	 */

	public function __construct($file, $columnSeparator = ",", $data = false)
	{

		$this->file            = $file;
		$this->data            = $data;
		$this->columnSeparator = $columnSeparator;

		$this->parseFileHeader();

		if (!$this->detectHeadersValidity())
		{
			Factory::getApplication()->enqueueMessage(Text::_('JEV_NOT_A_VALID_CSV_UPLOADED'), 'warning');

			return false;
		}

		if (!$this->convertFile())
		{
			Factory::getApplication()->enqueueMessage(Text::_('JEV_IMPORT_CORRUPT_CANCELLED'), 'warning');

			return false;
		}

		Factory::getApplication()->enqueueMessage(Text::_('JEV_IMPORT_CSV_CONVERTED'), 'notice');

	}

	/**
	 * Function parses first line of the CSV input with headers
	 */
	private function parseFileHeader()
	{

		if ($this->data)
		{
			$line = StringHelper::substr($this->data, 0, StringHelper::strpos($this->data, "\n") + 1);
		}
		else
		{
			$fp   = fopen($this->file, 'r');
			$line = fgets($fp, 4096);
			$line = trim($line); // remove white spaces, at the end always \n (or \r\n)
			fclose($fp);
		}

		$headers       = explode($this->columnSeparator, $line);
		$this->colsNum = count($headers);
		for ($i = 0; $i < $this->colsNum; $i++)
		{
			// -------- remove the utf-8 BOM ----
			$headers[$i]                                               = str_replace("\xEF\xBB\xBF", '', $headers[$i]);
			$this->colsOrder[str_replace('"', '', trim($headers[$i]))] = $i;
			// some people let white space at the end of text, so better to trim
			// CSV has often begining and ending " - replace it
		}
	}

	/**
	 * Check, if mandatory cols are present
	 *
	 * @return true if necessary headers present, false if not
	 */
	private function detectHeadersValidity()
	{

		if (isset($this->colsOrder["CATEGORIES"]) &&
			isset($this->colsOrder["SUMMARY"]) &&
			isset($this->colsOrder["DTSTART"]) &&
			isset($this->colsOrder["DTEND"]) &&
			isset($this->colsOrder["TIMEZONE"]))
			return true;
		else
		{
			return false;
		}

	}

	/**
	 * Function converts file from CSV to iCal
	 *
	 * @return true if success, false in case of error
	 */
	private function convertFile($delimiter = "\n")
	{

		$this->createNewTmpICal();  // creates new temporary iCal file

		if ($this->data)
		{
			// unfold content lines according the unfolding procedure of rfc2445
			$this->data = str_replace("\n ", "", $this->data);
			$this->data = str_replace("\n\t", "", $this->data);

			// Convert string into array for easier processing
			$this->data = explode("\n", $this->data);
			for ($i = 0; $i < count($this->data); $i++)
			{
				$buffer = $this->data[$i];
				while ((!$line = $this->parseCsvLine($buffer)) && $i + 1 < count($this->data))
				{
					$i++;
					$buffer .= $this->data[$i];
				}
				if (!$line)
				{
					// something gone wrong, CSV is corrupted, cancel
					return false;
				}
				if ($i == 0)
					continue; // fist line is header, so continue
				fwrite($this->tmpfile, $line->getInICalFormat()); // write to the converted file
			}
			/*
			 * this approach doesn't deal with carraige returns within fields!
$i = 1;
foreach ($this->data as $buffer){
	if ($buffer == "")
		continue; // end of file or empty line
	if (!$line = $this->parseCsvLine($buffer))
	{
		// something gone wrong, CSV is corrupted, cancel
		return false;
	}
	if ($i++ == 1)
		continue; // fist line is header, so continue
	fwrite($this->tmpfile, $line->getInICalFormat()); // write to the converted file
}
			 */
		}
		else
		{
			$fp = fopen($this->file, 'r');
			$i  = 1;
			while (!feof($fp))
			{
				$buffer = fgets($fp);
				if ($buffer == "")
					break; // end of file or empty line
				if (!$line = $this->parseCsvLine($buffer))
				{
					// something gone wrong, CSV is corrupted, cancel
					return false;
				}
				if ($i++ == 1)
					continue; // fist line is header, so continue
				fwrite($this->tmpfile, $line->getInICalFormat()); // write to the converted file
				$buffer = ''; // clear the buffer
			}
		}

		$this->finalizeTmpICal();

		return true;

	}

	/**
	 * Constructs new temporary file in iCal format, which will be
	 * used in CSV transformation
	 */
	private function createNewTmpICal()
	{

		$config = Factory::getConfig();
		$path   = $config->get('config.tmp_path') ? $config->get('config.tmp_path') : $config->get('tmp_path');
		echo "create temp CSV conversion file in " . $path . "<br/>";
		$this->tmpFileName = tempnam($path, "phpJE");
		//$this->tmpFileName = tempnam("/tmp", "phpJE");
		$this->tmpfile = fopen($this->tmpFileName, "w");
		fwrite($this->tmpfile, "BEGIN:VCALENDAR\n");
		fwrite($this->tmpfile, "VERSION:2.0\n");
		fwrite($this->tmpfile, "PRODID:-//jEvents 2.0 for Joomla//EN\n");
		fwrite($this->tmpfile, "CALSCALE:GREGORIAN\n");
		fwrite($this->tmpfile, "METHOD:PUBLISH\n");

	}

	/**
	 * Function parses Csv line due previously detected column order,
	 * special treatment for mandatory columns
	 *
	 * @return parsed data line or false if error
	 */
	private function parseCsvLine($line)
	{

		$data = str_getcsv($line);
		// different count of data cols than header cols, bad CSV
		if (count($data) != $this->colsNum && count($data) != 1)
		{ // == 1 probably last empty line
			// different number of cols than in header, file is not in correct format
			return false;
		}
		$dataLine = new CsvLine($data[$this->colsOrder["CATEGORIES"]],
			$data[$this->colsOrder["SUMMARY"]],
			$data[$this->colsOrder["DTSTART"]],
			$data[$this->colsOrder["DTEND"]]);
		foreach ($this->colsOrder as $col => $order)
		{
			switch ($col)
			{
				case "TIMEZONE":
					$dataLine->setTimezone($data[$order]);
					break;
				case "LOCATION":
					$dataLine->setLocation($data[$order]);
					break;
				case "DTSTAMP":
					$dataLine->setDtstamp($data[$order]);
					break;
				case "X-EXTRAINFO":
					$dataLine->setExtraInfo($data[$order]);
					break;
				case "X-COLOR":
					$dataLine->setColor($data[$order]);
					break;
				case "CONTACT":
					$dataLine->setContact($data[$order]);
					break;
				case "DESCRIPTION":
					$dataLine->setDescription($data[$order]);
					break;
				case "RRULE":
					$dataLine->setRrule($data[$order]);
					break;
				case "UID":
					$dataLine->setUid($data[$order]);
					break;
				case "CATEGORIES":
				case "SUMMARY":
				case "DTSTART":
				case "DTEND":
					break;
				case "NOENDTIME":
					$dataLine->setNoendtime($data[$order]);
					break;
				case "PUBLISHED":
					$dataLine->setPublished($data[$order]);
					break;
				case "MULTIDAY":
					$dataLine->setMultiday($data[$order]);
					break;
				case "lockevent":
					$dataLine->lockevent = $data[$order];
					break;
				default:
					$dataLine->customField($data[$order], $col);
					break;
			}
		}

		return $dataLine;

	}

	/**
	 * Function finalizes temporary iCal file
	 */
	private function finalizeTmpICal()
	{

		fwrite($this->tmpfile, "END:VCALENDAR\n");
		fclose($this->tmpfile);

	}

	/**
	 * Function for retrive converted temp file information
	 *
	 * @return array with name and path to temp file
	 */
	public function getConvertedTempFile()
	{

		$file = array("name"     => StringHelper::substr($this->tmpFileName, strrpos($this->tmpFileName, DIRECTORY_SEPARATOR) + 1),
		              "tmp_name" => $this->tmpFileName);

		return $file;

	}

	/**
	 * Function for retrieve raw converted data from temp file
	 *
	 * @return raw data converted CSV to iCal
	 */
	public function getRawData()
	{

		return @file_get_contents($this->tmpFileName);

	}

}
