'Page Edit Link', 'summary' => 'Provides a link capability as used by some Fieldtype modules (like rich text editors).', 'version' => 109, 'permanent' => true, 'permission' => 'page-edit', 'icon' => 'link', ); } /** * URL type: Absolute path from root (no relative paths) * */ const urlTypeAbsolute = 0; /** * URL type: Relative path in same branch only * */ const urlTypeRelativeBranch = 1; /** * URL type: Relative path always * */ 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 = ''; } $currentText = $this->wire()->input->get('text'); $currentText = $currentText === null ? '' : $this->wire()->sanitizer->text($currentText); /** @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); if($currentText) { /** @var InputfieldText $field */ $field = $this->modules->get("InputfieldText"); $field->label = $this->_('Link text'); $field->icon = 'pencil-square'; $field->attr('id+name', 'link_text'); $field->val($currentText); $fieldset->add($field); } /** @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() . "

"; } /** * @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->_('Full/absolute path from root (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; } }