<?php namespace ProcessWire;

/**
 * An Inputfield for handling "textarea" form inputs
 * 
 * ProcessWire 3.x, Copyright 2022 by Ryan Cramer
 * https://processwire.com
 * 
 * @property int $rows Number of rows for textarea (default=5)
 * @property int $contentType Content type, applicable when used with FieldtypeTextarea. See FieldtypeTextarea contentType constants (default=contentTypeUnknown)
 *
 */
class InputfieldTextarea extends InputfieldText {

	/**
	 * Default value for rows attribute
	 * 
	 */
	const defaultRows = 5;

	/**
	 * Get module info
	 * 
	 * @return array
	 * 
	 */
	public static function getModuleInfo() {
		return array(
			'title' => __('Textarea', __FILE__), // Module Title
			'summary' => __('Multiple lines of text', __FILE__), // Module Summary
			'version' => 103,
			'permanent' => true, 
			);
	}

	/**
	 * Init Inputfield
	 * 
	 */
	public function init() {
		parent::init();
		$this->setAttribute('rows', self::defaultRows); 
		$this->setAttribute('maxlength', $this->getDefaultMaxlength()); 
		$this->set('contentType', FieldtypeTextarea::contentTypeUnknown); 
	}

	/**
	 * Get the default maxlength attribute value
	 *
	 * @return mixed
	 *
	 */
	public function getDefaultMaxlength() {
		return $this->hasFieldtype === false ? 1024*32 : 0;
	}

	/**
	 * Get all attributes in an associative array
	 *
	 * @return array
	 *
	 */
	public function getAttributes() {
		$attrs = parent::getAttributes();

		if(isset($attrs['maxlength'])) {
			if($attrs['maxlength'] > 0) {
				// we only allow maxlength attribute for use outside of PW fields
				// otherwise we only use data-maxlength attribute
				$attrs['data-maxlength'] = $attrs['maxlength'];
				if($this->hasFieldtype !== false) unset($attrs['maxlength']);
			} else {
				unset($attrs['maxlength']);
			}
		}
		
		return $attrs;
	}

	/**
	 * Return the length of the given value (not including markup)
	 *
	 * @param string $value
	 * @param bool $countWords Optionally return a word count rather than character count. 
	 * @return int
	 *
	 */
	protected function getValueLength($value, $countWords = false) {
		if($this->isContentTypeHTML()) $value = strip_tags($value);
		$value = $this->wire()->sanitizer->textarea($value, array('stripTags' => false)); 
		return parent::getValueLength($value, $countWords);
	}

	/**
	 * Render Inputfield
	 * 
	 * @return string
	 * 
	 */
	public function ___render() {

		$attrs = $this->getAttributes();
		$value = $attrs['value'];
		unset($attrs['value'], $attrs['size'], $attrs['type']);

		return
			"<textarea " . $this->getAttributesString($attrs) . ">" . 
				htmlspecialchars($value, ENT_QUOTES, "UTF-8") . 
			"</textarea>";
	}

	/**
	 * Prepare the 'value' attribute
	 * 
	 * @param string $value
	 * @return string
	 * @throws WireException
	 * 
	 */
	protected function setAttributeValue($value) {
		
		$maxlength = $this->attr('maxlength');
		$value = (string) $value;
		
		if($maxlength > 0 && $this->hasFieldtype === false) { 
			$value = $this->wire()->sanitizer->textarea($value, array(
				'maxLength' => $maxlength, 
				'maxBytes' => $maxlength*4, 
				'stripTags' => false, 
				'trim' => $this->noTrim ? false : true
			)); 
		} else {
			if(strpos($value, "\r\n") !== false) {
				$value = str_replace("\r\n", "\n", $value); 
			}
		}
		
		if($this->stripTags) $value = strip_tags($value);
		
		return $this->noTrim ? $value : trim($value); 
	}

	/**
	 * Process input
	 * 
	 * @param WireInputData $input
	 * @return self|Inputfield
	 * 
	 */
	public function ___processInput(WireInputData $input) {

		$maxlength = $this->attr('maxlength');
		
		if($this->hasFieldtype !== false && $maxlength > 0) {
			// we want to apply our own maxlength logic that doesn't truncate
			$this->attr('maxlength', 0);
			$result = parent::___processInput($input);
			$this->attr('maxlength', $maxlength); // restore

			$value = $this->attr('value');
			$length = function_exists('mb_strlen') ? mb_strlen($value) : strlen($value);
			if($length > $maxlength) {
				$this->error(sprintf(
					$this->_('Value exceeds maximum recommended length of %1$d characters (length=%2$d).'),
					$maxlength, $length)
				);
			}
		} else {
			$result = parent::___processInput($input);
		}
		
		return $result;	
	}

	/**
	 * Render just the value (not input) in text/markup for presentation purposes
	 *
	 * @return string of text or markup where applicable
	 *
	 */
	public function ___renderValue() {
		if($this->isContentTypeHTML()) {
			$out = 
				"<div class='InputfieldTextareaContentTypeHTML'>" . 
					$this->wire()->sanitizer->purify($this->val()) . 
				"</div>";
		} else {
			$out = nl2br(htmlentities($this->val(), ENT_QUOTES, "UTF-8"));
		}	
		return $out;
	}

	/**
	 * Is current content-type an HTML content-type?
	 * 
	 * @return bool
	 * 
	 */
	public function isContentTypeHTML() {
		$contentTypes = array(
			FieldtypeTextarea::contentTypeHTML, 
			FieldtypeTextarea::contentTypeImageHTML
		);
		return in_array($this->contentType, $contentTypes);
	}

	/**
	 * Configure Inputfield
	 * 
	 * @return InputfieldWrapper
	 * 
	 */
	public function ___getConfigInputfields() {
		
		$inputfields = parent::___getConfigInputfields();
		$removes = array('size', 'pattern');
		
		foreach($removes as $name) {
			$f = $inputfields->getChildByName($name);
			if($f) $inputfields->remove($f);
		}

		/** @var InputfieldInteger $field */
		$field = $this->wire()->modules->get('InputfieldInteger'); 
		$field->setAttribute('name', 'rows'); 
		$field->label = $this->_('Rows');
		$field->setAttribute('value', $this->attr('rows') > 0 ? $this->attr('rows') : self::defaultRows); 
		$field->setAttribute('size', 3); 
		$field->description = $this->_('The number of rows initially shown for this field.'); 
		if($field->attr('value') == self::defaultRows) $field->collapsed = Inputfield::collapsedYes; 
		$inputfields->append($field);

		return $inputfields; 
	}

	/**
	 * Get config fields allowed for context
	 * 
	 * @param Field $field
	 * @return array
	 * 
	 */
	public function ___getConfigAllowContext($field) {
		return array_merge(parent::___getConfigAllowContext($field), array('rows')); 
	}
	
}
