<commit_before><?php defined('SYSPATH') or die('No direct script access.');

/**
 * Handles files and file uploads.
 *
 * If a valid upload is set on the field, the upload will be saved
 * automatically to the $path set and the value of the field will
 * be the filename used.
 *
 * @package  Jelly
 */
abstract class Jelly_Core_Field_File extends Jelly_Field
{
	/**
	 * Ensures there is a path for saving set
	 *
	 * @param  array  $options
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Ensure we have path to save to
		if (empty($this->path) OR !is_writable($this->path))
		{
			throw new Kohana_Exception(get_class($this).' must have a `path` property set that points to a writable directory');
		}

		// Make sure the path has a trailing slash
		$this->path = rtrim(str_replace('\\', '/', $this->path), '/').'/';
	}

	/**
	 * Uploads a file if we have a valid upload
	 *
	 * @param   Jelly  $model
	 * @param   mixed  $value
	 * @param   bool   $loaded
	 * @return  string|NULL
	 */
	public function save($model, $value, $loaded)
	{
		// Upload a file?
		if (is_array($value) AND upload::valid($value))
		{
			if (FALSE !== ($filename = upload::save($value, NULL, $this->path)))
			{
				// Chop off the original path
				$value = str_replace(realpath($this->path).DIRECTORY_SEPARATOR, '', $filename);

				// Ensure we have no leading slash
				if (is_string($value))
				{
					$value = trim($value, DIRECTORY_SEPARATOR);
				}
			}
			else
			{
				$value = $this->default;
			}
		}

		return $value;
	}
}<commit_msg>Adding file and image fields. I'll be testing them tomorrow.<commit_after><?php defined('SYSPATH') or die('No direct script access.');

/**
 * Handles file uploads.
 *
 * Since this field is ultimately just a varchar in the database, it 
 * doesn't really make sense to put rules like Upload::valid or Upload::type
 * on the validation object; if you ever want to NULL out the field, the validation
 * will fail!
 * 
 * As such, these 
 *
 * @package  Jelly
 */
abstract class Jelly_Field_File extends Jelly_Field
{
	/**
	 * @var  boolean  Whether or not to delete the old file when a new file is added
	 */
	public $delete_old_file = TRUE;
	
	/**
	 * @var  string  The path to save the file in
	 */
	public $path = NULL;
	
	/**
	 * @var  array  Valid types for the file
	 */
	public $types = array();
	
	/**
	 * Ensures there is a path for saving set
	 *
	 * @param  array  $options
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);
		
		$this->path = $this->_check_path($this->path);
		
		// Add a callback to save the file when validating
		$this->callbacks[] = array($this, '_upload');
	}

	/**
	 * Uploads a file if we have a valid upload
	 *
	 * @param   Jelly  $model
	 * @param   mixed  $value
	 * @param   bool   $loaded
	 * @return  string|NULL
	 */
	public function _upload(Jelly_Validator $array, $field, $model)
	{
		if ($array->errors())
		{
			// Don't bother uploading
			return;
		}

		// Get the image from the array
		$file = $array[$input];

		if ( ! Upload::valid($file) OR ! Upload::not_empty($file))
		{
			// No need to do anything right now
			return;
		}
		
		// Check to see if it's a valid type
		if ($this->types AND ! Upload::type($file, $this->types))
		{
			$array->error($field, 'valid');
		}
		
		// Sanitize the filename
		$file['name'] = preg_replace('/[^a-z0-9-\.]/', '-', strtolower($file['name']));

		// Strip multiple dashes
		$file['name'] = preg_replace('/-{2,}/', '-', $file['name']);
		
		// Upload a file?
		if (FALSE !== ($filename = Upload::save($value, NULL, $this->path)))
		{
			// Chop off the original path
			$value = str_replace($this->path, '', $filename);

			// Ensure we have no leading slash
			if (is_string($value))
			{
				$value = trim($value, '/');
			}
			
			// Garbage collect
			$this->_delete_old_file($model->get($this->name, FALSE), $this->path);
			
			// Set the new filename on the model
			$model->set($this->name, $value);
		}
		else
		{
			$array->error($field, 'failed');
		}
	}
	
	/**
	 * Checks that a given path exists and is writable and that it has a trailing slash.
	 *
	 * (pulled out into a method so that it can be reused easily by image subclass)
	 *
	 * @param  $path
	 * @return string The path - making sure it has a trailing slash
	 */
	protected function _check_path($path)
	{
		// Normalize the path
		$path = realpath(str_replace('\\', '/', $path));
		
		// Ensure we have a trailing slash
		if (!empty($path) AND is_writable($path))
		{
			$path = rtrim($path, '/').'/';
		}
		else
		{
			throw new Kohana_Exception(get_class($this).' must have a `path` property set that points to a writable directory');
		}
		
		return $path;
	}
	
	/**
	 * Deletes the previously used file if necessary.
	 *
	 * @param   string $filename 
	 * @param   string $path 
	 * @return  void
	 */
	protected function _delete_old_file($filename, $path)
	{
		 // Delete the old file if we need to
		if ($this->delete_old_file AND $filename != $this->default)
		{
			$path = $path.$filename;
			
			if (file_exists($path)) 
			{
				unlink($path);
			}
		}
	}
}
{'fraction_non_alphanumeric': 0.14801864801864803, 'fraction_numerical': 0.0005827505827505828, 'mean_word_length': 2.9155893536121673, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7465750', 'n_tokens_mistral': 1914, 'n_tokens_neox': 1658, 'n_words': 547}