<?php namespace ProcessWire;

/**
 * ProcessWire Password Fieldtype
 *
 * Fieldtype for holding a hashed and salted password
 *
 * For documentation about the fields used in this class, please see:  
 * /wire/core/Fieldtype.php
 * 
 * ProcessWire 3.x, Copyright 2023 by Ryan Cramer
 * https://processwire.com
 *
 */

class FieldtypePassword extends Fieldtype {

	public static function getModuleInfo() {
		return array(
			'title' => 'Password',
			'version' => 101,
			'summary' => 'Field that stores a hashed and salted password',
			'permanent' => true, 
		);
	}

	/**
	 * Initialize the Fieldtype
	 *
	 */
	public function init() {
		parent::init();
	}

	/**
	 * Return the associated Inputfield
	 * 
	 * @param Page $page
	 * @param Field $field
	 * @return InputfieldPassword
	 *
	 */
	public function getInputfield(Page $page, Field $field) {
		/** @var InputfieldPassword $inputfield */
		$inputfield = $this->modules->get('InputfieldPassword'); 
		$inputfield->class = $this->className();
		$inputfield->setPage($page); 
		return $inputfield; 
	}

	/**
	 * Return all Fieldtypes derived from FieldtypeText, which we will consider compatible
	 * 
	 * @param Field $field
	 * @return null|array
	 *
	 */
	public function ___getCompatibleFieldtypes(Field $field) {
		return null;
	}

	/**
	 * Sanitize value for runtime
	 * 
	 * @param Page $page
	 * @param Field $field
	 * @param Password|string $value
	 * @return Password
	 *
	 */
	public function sanitizeValue(Page $page, Field $field, $value) {

		if($value instanceof Password) {
			$item = $value; 

		} else { 
			$item = $page->__isset($field->name) ? $page->get($field->name) : $this->getBlankValue($page, $field); 
			$item->pass = trim($value); 
		}

		if($item->isChanged()) $page->trackChange($field->name); 

		return $item; 
	}

	/**
	 * Get a blank password item object
	 * 
	 * @param Page $page
	 * @param Field $field
	 * @return Password
	 *
	 */
	public function getBlankValue(Page $page, Field $field) {
		$item = $this->wire(new Password()); 
		$item->setTrackChanges(true);
		return $item; 
	}

	/**
	 * Given a raw value (value as stored in DB), return the value as it would appear in a Page object
	 *
	 * @param Page $page
	 * @param Field $field
	 * @param array $value
	 * @return Password
	 *
	 */
	public function ___wakeupValue(Page $page, Field $field, $value) {
		$wakeValue = $this->getBlankValue($page, $field); 
		$wakeValue->salt = $value['salt']; // salt must be set first when blowfish
		$wakeValue->hash = $value['data'];
		$wakeValue->setTrackChanges(true); 
		return $wakeValue; 
	}

	/**
	 * Given an 'awake' value, as set by wakeupValue, convert the value back to a basic type for storage in DB. 
	 *              
	 * @param Page $page
	 * @param Field $field
	 * @param string|int|array|object $value
	 * @return array|string|int
	 *
	 */
	public function ___sleepValue(Page $page, Field $field, $value) {

		if(!$value instanceof Password) return $value; 

		$sleepValue = array(
			'salt' => $value->salt, 
			'data' => $value->hash, 
		);

		// salt not needed for blowfish since it is prepended to the hash already
		// if($value->isBlowfish()) $sleepValue['salt'] = '';

		return $sleepValue; 
	}


	/**
	 * Return the database schema in specified format
	 * 
	 * @param Field $field
	 * @return array
	 *
	 */
	public function getDatabaseSchema(Field $field) {
		$schema = parent::getDatabaseSchema($field); 
		$schema['data'] = 'char(40) NOT NULL';
		$schema['salt'] = 'char(32) NOT NULL';
		$engine = $this->wire()->config->dbEngine;
		$schema['xtra']['append'] = "ENGINE=$engine DEFAULT CHARSET=ascii";
		return $schema;
	}


	/**
	 * Return the fields required to configure an instance of FieldtypePassword
	 * 
	 * @param Field $field
	 * @return InputfieldWrapper
	 *
	 */
	public function ___getConfigInputfields(Field $field) {
		$inputfields = parent::___getConfigInputfields($field);
		return $inputfields; 
	}
}
