<?php namespace ProcessWire;

/**
 * ProcessWire Edit Link Process
 *
 * Provides the link capability as used by the rich text editor. 
 * 
 * ProcessWire 3.x, Copyright 2016 by Ryan Cramer
 * https://processwire.com
 * 
 * @property string $relOptions
 * @property string $classOptions
 * @property string $targetOptions
 * @property int $urlType
 * @property int $extLinkRel
 * @property string $extLinkTarget
 * @property string $extLinkClass
 *
 */

class ProcessPageEditLink extends Process implements ConfigurableModule {

	public static function getModuleInfo() {
		return array(
			'title' => 'Page Edit Link',          
			'summary' => 'Provides a link capability as used by some Fieldtype modules (like rich text editors).', 
			'version' => 108, 
			'permanent' => true, 
			'permission' => 'page-edit',
			'icon' => 'link', 
			);
	}
	
	const urlTypeAbsolute = 0;
	const urlTypeRelativeBranch = 1; 
	const urlTypeRelativeAll = 2;
	
	/**
	 * @var Page|null
	 *
	 */
	protected $page = null;

	/**
	 * The "choose page" start label
	 * 
	 * @var string
	 * 
	 */
	protected $startLabel = '';

	/**
	 * Language ID
	 * 
	 * @var int
	 * 
	 */
	protected $langID = 0;

	/**
	 * Get default configuration settings
	 * 
	 * @return array
	 * 
	 */
	public static function getDefaultSettings() {
		return array(
			'classOptions' => "",
			'relOptions' => "nofollow",
			'targetOptions' => "_blank",
			'urlType' => self::urlTypeAbsolute,
			'extLinkRel' => '',
			'extLinkTarget' => '',
			'extLinkClass' => '', 
		);
	}

	/**
	 * Construct
	 * 
	 */
	public function __construct() {
		parent::__construct();
		foreach(self::getDefaultSettings() as $key => $value) {
			$this->set($key, $value); 
		}
	}

	/**
	 * Init
	 * 
	 * @throws WireException
	 * 
	 */
	public function init() {
		
		$this->startLabel = $this->_('Choose page'); 
		$this->modules->get("ProcessPageList"); 	
		$id = (int) $this->input->get('id'); 
		$this->langID = (int) $this->input->get('lang');
		if($id) $this->page = $this->pages->get($id); 
		if($this->page && $this->page->id && !$this->user->hasPermission("page-view", $this->page)) {
			throw new WireException("You don't have access to this page");
		}
		if(!$this->page) $this->page = $this->wire('pages')->newNullPage();

		$this->config->js('ProcessPageEditLink', array(
			'selectStartLabel' => $this->startLabel, 
			'langID' => $this->langID, 
			'pageID' => $id,
			'pageUrl' => $this->page->url,
			'pageName' => $this->page->name, 
			'rootParentUrl' => $this->page->rootParent->url,
			'slashUrls' => $this->page->template ? $this->page->template->slashUrls : 1,
			'urlType' => $this->urlType,
			'extLinkRel' => $this->wire('sanitizer')->names($this->extLinkRel), 
			'extLinkTarget' => $this->extLinkTarget, 
			'extLinkClass' => $this->wire('sanitizer')->names($this->extLinkClass), 
			));

		parent::init();
	}

	/**
	 * Primary execute
	 * 
	 * @return string
	 * 
	 */
	public function ___execute() {
	
		if($this->wire('input')->get('href')) {
			$currentValue = $this->wire('sanitizer')->url($this->wire('input')->get('href'), array(
				'stripQuotes' => false,
				'allowIDN' => true,
			));
		} else {
			$currentValue = '';
		}

		/** @var InputfieldForm $form */
		$form = $this->modules->get("InputfieldForm"); 
		$form->attr('id', 'ProcessPageEditLinkForm');
		//$form->description = $this->_("Enter a URL, select a page, or select a file to link:"); // Headline
		
		$this->wire('modules')->get('JqueryWireTabs'); 
	
		/** @var InputfieldWrapper $fieldset */
		$fieldset = $this->wire(new InputfieldWrapper());
		$fieldset->attr('title', $this->_('Link')); 
		$fieldset->addClass('WireTab');
		$form->add($fieldset); 

		/** @var InputfieldPageAutocomplete $field */
		$field = $this->modules->get("InputfieldPageAutocomplete"); 
		$field->label = $this->_('Link to URL'); 
		$field->attr('id+name', 'link_page_url'); 
		$field->icon = 'external-link-square';
		$field->description = $this->_('Enter a URL, email address, anchor, or enter word(s) to find a page.'); 
		$field->labelFieldName = 'url';
		if($this->wire('modules')->isInstalled('PagePaths') && !$this->wire('languages')) {
			$field->searchFields = 'path title'; 
		} else {
			$field->searchFields = 'name title';
		}
		if($this->langID) $field->lang_id = $this->langID;
		$field->maxSelectedItems = 1; 
		$field->useList = false; 
		$field->allowAnyValue = true; 
		$field->disableChars = '/:.#';
		$field->useAndWords = true; 
		$field->findPagesSelector = 
			"has_parent!=" . $this->wire('config')->adminRootPageID . ", " . 
			"id!=" . $this->wire('config')->http404PageID;
		if($currentValue) $field->attr('value', $currentValue);
		$fieldset->add($field);
		
		if(is_array($this->wire('input')->get('anchors'))) {
			$field->columnWidth = 60; 
			/** @var InputfieldSelect $field */
			$field = $this->modules->get('InputfieldSelect'); 
			$field->columnWidth = 40; 
			$field->attr('id+name', 'link_page_anchor');
			$field->label = $this->_('Select Anchor'); 
			$field->description = $this->_('Anchors found in the text you are editing.'); 
			$field->icon = 'flag';
			foreach($this->wire('input')->get('anchors') as $anchor) {
				$anchor = '#' . $this->wire('sanitizer')->text($anchor); 
				if(strlen($anchor)) $field->addOption($anchor); 
				if($currentValue && $currentValue == $anchor) $field->attr('value', $currentValue); 
			}
			$fieldset->add($field); 
		}
	
		/** @var InputfieldInteger $field */
		$field = $this->modules->get('InputfieldInteger'); 
		$field->attr('id+name', 'link_page_id');
		$field->label = $this->_("Select Page");
		$field->set('startLabel', $this->startLabel);
		$field->collapsed = Inputfield::collapsedYes; 
		$field->icon = 'sitemap';
		$fieldset->add($field); 

		if($this->page->numChildren) {
			$field = $this->modules->get('InputfieldInteger'); 
			$field->attr('id+name', 'child_page_id');
			$field->label = $this->_("Select Child Page");
			$field->description = $this->_('This is the same as "Select Page" above, but may quicker to use if linking to children of the current page.'); 
			$field->set('startLabel', $this->startLabel);
			$field->collapsed = Inputfield::collapsedYes; 
			$field->icon = 'sitemap';
			$fieldset->append($field); 
		}

		$fieldset->append($this->getFilesField()); 
		
		$fieldset = $this->wire(new InputfieldWrapper());
		$fieldset->attr('title', $this->_('Attributes'));
		$fieldset->attr('id', 'link_attributes'); 
		$fieldset->addClass('WireTab'); 
		$form->append($fieldset); 
	
		$field = $this->modules->get('InputfieldText'); 
		$field->attr('id+name', 'link_title'); 
		$field->label = $this->_('Title'); 
		$field->description = $this->_('Additional text to describe link.');
		if($this->wire('input')->get('title')) {
			$field->attr('value', $this->wire('sanitizer')->text($this->wire('input')->get('title')));
		}
		$fieldset->add($field);
		
		if($this->targetOptions) {
			/** @var InputfieldSelect $field */
			$field = $this->modules->get('InputfieldSelect');
			$field->attr('id+name', 'link_target');
			$field->label = $this->_('Target'); 
			$field->description = $this->_('Where this link will open.');
			$this->addSelectOptions($field, 'target', $this->targetOptions); 
			if($this->relOptions) $field->columnWidth = 50;
			$fieldset->add($field); 
		}
		
		if($this->relOptions) {
			$field = $this->modules->get('InputfieldSelect');
			$field->attr('id+name', 'link_rel');
			$field->label = $this->_('Rel'); 
			$field->description = $this->_('Relationship of link to document.');
			if($this->targetOptions) $field->columnWidth = 50; 
			$this->addSelectOptions($field, 'rel', $this->relOptions); 
			$fieldset->add($field); 
		}
		
		if($this->classOptions) {
			/** @var InputfieldCheckboxes $field */
			$field = $this->modules->get('InputfieldCheckboxes');
			$field->attr('id+name', 'link_class');
			$field->label = $this->_('Class');
			$field->description = $this->_('Additional classes that can affect the look or behavior of the link.'); 
			$field->optionColumns = 1; 
			$this->addSelectOptions($field, 'class', $this->classOptions); 
			$fieldset->add($field);
		}
		
		if($this->wire('user')->isSuperuser()) $fieldset->notes = 
			sprintf(
				$this->_('You may customize available attributes shown above in the %s module settings.'),
				"[ProcessPageEditLink](" . $this->wire('config')->urls->admin . "module/edit?name=ProcessPageEditLink)"
				);
		
		return $form->render() . "<p class='detail ui-priority-secondary'><code id='link_markup'></code></p>";
	}

	/**
	 * @param InputfieldSelect $field
	 * @param $attrName
	 * @param $optionsText
	 * 
	 */
	protected function addSelectOptions(InputfieldSelect $field, $attrName, $optionsText) {
		
		$isExisting = $this->wire('input')->get('href') != ''; 
		$existingValue = $this->wire('sanitizer')->text($this->wire('input')->get($attrName));
		$existingValue = explode(' ', $existingValue); 
		
		if($field instanceof InputfieldRadios) {
			$field->addOption('', $this->_('None')); 
		}
		
		foreach(explode("\n", $optionsText) as $value) {
			$value = trim($value);
			$isDefault = strpos($value, '+') !== false;
			if($isDefault) $value = trim($value, '+'); 
			$attr = array();
			if(($isDefault && !$isExisting) || in_array($value, $existingValue)) {
				if($field instanceof InputfieldCheckboxes) {
					$attr['checked'] = 'checked';
				} else {
					$attr['selected'] = 'selected';
				}
			}
			$value = trim($value, '+ ');
			$label = '';
			if(strpos($value, '=') !== false) {
				list($value, $label) = explode('=', $value); 
				$value = trim($value);
				$label = trim($label); 
			} else {
				if($value == '_blank') $label = $this->_('open in new window');
				if($value == 'nofollow') $label = $this->_('tell search engines not to follow');
			}
			if($label) {
				$label = "$value ($label)";
			} else {
				$label = $value; 
			}
	
			$field->addOption($value, $label, $attr);
		}
	}

	/**
	 * Return JSON containing files list for ajax use
	 * 
	 * @return string
	 * @throws WireException
	 * 
	 */
	public function ___executeFiles() {
		if(!$this->page->id) throw new WireException("A page id must be specified"); 	
		$files = $this->getFiles();	
		return wireEncodeJSON($files);
	}

	/**
	 * Get array of info about files attached to given Page
	 *
	 * @return array Associative array of "/url/to/file.pdf" => "Field label: basename"
	 *
	 */
	protected function getFiles() {
		$files = array();
		$page = $this->page;
		// As the link generator might be called in a repeater, we need to find the containing page		
		$n = 0;
		while(wireInstanceOf($page, 'RepeaterPage') && ++$n < 10) {
			/** @var RepeaterPage $page */
			$page = $page->getForPage();
		}
		if($page->id) {
			$files = $this->getFilesPage($page);
		}
		asort($files); 
		return $files;
	}

	/**
	 * Get array of info about files attached to given Page, including any repeater items
	 * 
	 * @param Page $page
	 * @param string $prefix
	 * @return array Associative array of "/url/to/file.pdf" => "Field label: basename"
	 * 
	 */
	protected function getFilesPage(Page $page, $prefix = '') {
		$files = array();
		foreach($page->template->fieldgroup as $field) {
			/** @var Fieldtype $type */
			$type = $field->type;
			if($type instanceof FieldtypeFile) {
				$value = $page->get($field->name);
				if($value) foreach($page->get($field->name) as $file) {
					$files[$file->url] = $prefix . $field->getLabel() . ': ' . $file->basename;
				}
			} else if(wireInstanceOf($type, 'FieldtypeRepeater')) { 
				$value = $page->get($field->name);
				if($value) {
					if($value instanceof Page) $value = array($value);
					if(WireArray::iterable($value)) {
						foreach($value as $repeaterPage) {
							$files = array_merge($this->getFilesPage($repeaterPage, $field->getLabel() . ': '), $files);
						}
					}
				}
			} 
		}
		return $files;
	}

	/**
	 * @return InputfieldSelect
	 * 
	 */
	protected function getFilesField() {
		/** @var InputfieldSelect $field */
		$field = $this->modules->get("InputfieldSelect"); 
		$field->label = $this->_("Select File");
		$field->attr('id+name', 'link_page_file'); 
		$files = $this->getFiles();
		$field->addOption('');
		$field->addOptions($files); 
		$field->collapsed = Inputfield::collapsedYes; 
		if($this->page->id) $field->notes = $this->_('Showing files on page:') .  ' **' . $this->page->url . '**';
		$field->description = 
			$this->_('Select the file from this page that you want to link to.') . ' ' . 
			$this->_("To select a file from another page, click 'Select Page' above and choose the page you want to select a file from."); // Instruction on how to select a file from another page
		$field->icon = 'file-text-o';
		return $field;

	}
	
	public function getModuleConfigInputfields(array $data) {
	
		$data = array_merge(self::getDefaultSettings(), $data); 
		$inputfields = $this->wire(new InputfieldWrapper());
		
		$fieldset = $this->wire('modules')->get('InputfieldFieldset'); 
		$fieldset->label = $this->_('Attribute options');
		$fieldset->description = $this->_('Enter one attribute value per line. The user will be able to select these as options when adding links. To make an option selected by default (for new links), precede it with a plus "+".');
		$fieldset->icon = 'sliders';

		$f = $this->wire('modules')->get('InputfieldTextarea');
		$f->attr('name', 'classOptions');
		$f->label = 'class';
		$f->attr('value', $data['classOptions']);
		$f->columnWidth = 34; 
		$fieldset->add($f);

		$f = $this->wire('modules')->get('InputfieldTextarea');
		$f->attr('name', 'relOptions');
		$f->label = 'rel';
		$f->attr('value', $data['relOptions']);
		$f->columnWidth = 33; 
		$fieldset->add($f);

		$f = $this->wire('modules')->get('InputfieldTextarea');
		$f->attr('name', 'targetOptions');
		$f->label = 'target';
		$f->attr('value', $data['targetOptions']);
		$f->columnWidth = 33; 
		$fieldset->add($f);
		$inputfields->add($fieldset); 
		
		$fieldset = $this->wire('modules')->get('InputfieldFieldset');
		$fieldset->label = $this->_('External link attributes'); 
		$fieldset->description = $this->_('Specify the default selected attributed that will be automatically populated when an external link is detected.');
		$fieldset->description .= ' ' . $this->_('If used, the value must be one you have predefined above.'); 
		$fieldset->icon = 'external-link';
		$fieldset->collapsed = Inputfield::collapsedBlank;

		$f = $this->wire('modules')->get('InputfieldText');
		$f->attr('name', 'extLinkClass');
		$f->label = 'class';
		$f->attr('value', $this->wire('sanitizer')->names($data['extLinkClass']));
		$f->required = false;
		$f->columnWidth = 34;
		$fieldset->add($f);

		$f = $this->wire('modules')->get('InputfieldText');
		$f->attr('name', 'extLinkRel');
		$f->notes = $this->_('Example: Specifying **nofollow** would make external links default to be not followed by search engines.');
		$f->label = 'rel';
		$f->required = false; 
		$f->attr('value', $this->wire('sanitizer')->names($data['extLinkRel']));
		$f->columnWidth = 33; 
		$fieldset->add($f);

		$f = $this->wire('modules')->get('InputfieldName');
		$f->attr('name', 'extLinkTarget');
		$f->label = 'target';
		$f->notes = $this->_('Example: Specifying **_blank** would make external links default to open in a new window.'); 
		$f->attr('value', $data['extLinkTarget']);
		$f->required = false; 
		$f->columnWidth = 33; 
		$fieldset->add($f);
		$inputfields->add($fieldset); 
		
		$f = $this->wire('modules')->get('InputfieldRadios'); 
		$f->attr('name', 'urlType'); 
		$f->label = $this->_('URL type for page links'); 
		$f->addOption(self::urlTypeAbsolute, $this->_('Absolute (default)')); 
		$f->addOption(self::urlTypeRelativeBranch, $this->_('Relative URLs in the same branches only') . '*'); 
		$f->addOption(self::urlTypeRelativeAll, $this->_('Relative URLs always') . '*'); 
		$f->attr('value', isset($data['urlType']) ? $data['urlType'] : self::urlTypeAbsolute); 
		$f->notes = $this->_('*Currently experimental'); 
		$f->collapsed = Inputfield::collapsedYes;
		$inputfields->add($f); 
		
		return $inputfields; 
	}
	

}

