Blame | Last modification | View Log | Download
<?php namespace ProcessWire;/*** ProcessWire Textarea Fieldtype** Stores a large block of multi-line text.** For documentation about the fields used in this class, please see:* /wire/core/Fieldtype.php** ProcessWire 3.x, Copyright 2018 by Ryan Cramer* https://processwire.com** Properties set to $field that is using this type, acceessed by $field->get('property'):** - contentType (int): Content type of field output using a self::contentType* constant (default=self::contentTypeUnknown)* - htmlOptions (array): Options for content-type Markup/HTML using self::html* constants (default=null or blank array)* - inputfieldClass (string): Inputfield class/module name to use for this field (default=InputfieldTextarea)**/class FieldtypeTextarea extends FieldtypeText {public static function getModuleInfo() {return array('title' => 'Textarea','version' => 107,'summary' => 'Field that stores multiple lines of text','permanent' => true,);}/*** The default Inputfield class associated with this Fieldtype**/const defaultInputfieldClass = 'InputfieldTextarea';/*** Indicates an unknown or plain text content type**/const contentTypeUnknown = 0;/*** Indicates a Markup/HTML content type with basic root path QA**/const contentTypeHTML = 1;/*** Indicates a Markup/HTML content type with all htmlImage* options enabled**/const contentTypeImageHTML = 2;/*** HTML options: <a> tag management to abstract page URLs in href attributes so they can be dynamically updated**/const htmlLinkAbstract = 2;/*** HTML options: <img> tag management to replace blank alt attributes with file description**/const htmlImageReplaceBlankAlt = 4;/*** HTML options: <img> tag management to remove or re-create images that don't exist**/const htmlImageRemoveNoExists = 8;/*** HTML options: <img> tag management to remove images that user doesn't have access to**/const htmlImageRemoveNoAccess = 16;/*** Instance of MarkupQA** @var MarkupQA**/protected $markupQA = null;/*** Instanceof FieldtypeTextareaHelper** @var FieldtypeTextareaHelper**/protected $configHelper = null;public function init() {$this->set('inputfieldClass', self::defaultInputfieldClass);$this->set('contentType', self::contentTypeUnknown);$this->set('htmlOptions', array());parent::init();}public function sanitizeValue(Page $page, Field $field, $value) {return parent::sanitizeValue($page, $field, $value);}public function ___markupValue(Page $page, Field $field, $value = null, $property = '') {if(is_null($value)) $value = $page->getFormatted($field->name);if($field->get('contentType') >= self::contentTypeHTML) {$value = $this->formatValue($page, $field, $value);} else {$value = parent::___markupValue($page, $field, $value, $property);}return $value;}public function ___formatValue(Page $page, Field $field, $value) {$value = parent::___formatValue($page, $field, $value);return $value;}public function ___sleepValue(Page $page, Field $field, $value) {$value = parent::___sleepValue($page, $field, $value);if($field->get('contentType') >= self::contentTypeHTML) $this->htmlReplacements($page, $field,$value, true);return $value;}public function ___wakeupValue(Page $page, Field $field, $value) {// note: we do this here in addition to loadPageField to account for values that came// from external resources (not loaded from DB).$value = parent::___wakeupValue($page, $field, $value);if($field->get('contentType') >= self::contentTypeHTML) {$this->htmlReplacements($page, $field, $value, false);}return $value;}public function ___loadPageField(Page $page, Field $field) {$value = parent::___loadPageField($page, $field);if($field->get('contentType') >= self::contentTypeHTML) {$this->htmlReplacements($page, $field, $value, false);}return $value;}/*** Get the MarkupQA instance** @param Page $page* @param Field $field* @return MarkupQA* @throws WireException If called the first time without page or field arguments**/public function markupQA(Page $page = null, Field $field = null) {if(is_null($this->markupQA)) {$this->markupQA = $this->wire(new MarkupQA($page, $field));} else {if($page) $this->markupQA->setPage($page);if($field) $this->markupQA->setField($field);}return $this->markupQA;}/*** Content Type HTML replacements accounting for href and src attributes** This ensures that sites migrated from one subdirectory to another, or from a subdirectory to* a non-subdir, or non-subdir to a subdir, continue working. This adds runtime context* to 'href' and 'src' attributes in HTML.** This method modifies the $value directly rather than returning it.** In order to make the abstracted attributes identifiable to this function (so they can be reversed)* it replaces the space preceding the attribute name with a tab character. This ensures the HTML* underneath still remains compliant in case it is later extracted directly from the DB for* data conversion or something like that.** This one handles a string value or array of string values (like for multi-language support)** Note: this is called by both loadPageField and wakeupValue, so will be called with the same* arguments twice during load of a value** @param Page $page* @param Field $field* @param string|array $value Value to look for attributes (or array of values)* @param bool $sleep When true, convert links starting with root URL to "/". When false, do the reverse.**/protected function htmlReplacements(Page $page, Field $field, &$value, $sleep = true) {$languages = $this->wire('languages');if(is_array($value)) {// array of values, most likely multi-language data123 columns from loadPageFieldforeach($value as $k => $v) {if(is_string($v)) {$this->_htmlReplacement($page, $field, $v, $sleep);} else {$this->htmlReplacements($page, $field, $v, $sleep); // recursive}$value[$k] = $v;}} else if(is_object($value) && $languages && $value instanceof LanguagesValueInterface && $value instanceof Wire) {// most likely a LanguagesPageFieldValue, but can be any type implementing LanguagesValueInterface/** @var Wire|LanguagesValueInterface $value */$trackChanges = $value->trackChanges();$value->setTrackChanges(false);foreach($languages as $language) {/** @var LanguagesValueInterface $value */$v = $value->getLanguageValue($language->id);$this->_htmlReplacement($page, $field, $v, $sleep);$value->setLanguageValue($language, $v);}if($trackChanges) $value->setTrackChanges($trackChanges);} else if(is_string($value)) {// standard textarea string$this->_htmlReplacement($page, $field, $value, $sleep);}}/*** Helper for htmlReplacements, to process single value** @param Page $page* @param Field $field* @param string $value* @param bool $sleep**/protected function _htmlReplacement(Page $page, Field $field, &$value, $sleep) {if(!strlen($value)) return;$markupQA = $this->markupQA($page, $field);$contentType = $field->get('contentType');$htmlOptions = $field->get('htmlOptions');if(!is_array($htmlOptions)) $htmlOptions = array();if($sleep) {$markupQA->sleepUrls($value);if(in_array(self::htmlLinkAbstract, $htmlOptions)) $markupQA->sleepLinks($value);} else {if(in_array(self::htmlLinkAbstract, $htmlOptions)) $markupQA->wakeupLinks($value);$markupQA->wakeupUrls($value);$useCheckImg = false;if($contentType == self::contentTypeImageHTML) {// keep default options, which means all enabled$opts = array();$useCheckImg = true;} else {// set image options specifically$opts = array('replaceBlankAlt' => in_array(self::htmlImageReplaceBlankAlt, $htmlOptions),'removeNoExists' => in_array(self::htmlImageRemoveNoExists, $htmlOptions),'removeNoAccess' => in_array(self::htmlImageRemoveNoAccess, $htmlOptions));foreach($opts as $val) if($val) $useCheckImg = true;}if($useCheckImg) $markupQA->checkImgTags($value, $opts);}static $lsep = null;if($lsep === null) $lsep = $this->wire('sanitizer')->unentities('
');if(strpos($value, $lsep) !== false) $value = str_replace($lsep, '', $value);}/*** Get the Inputfield module that provides input for Field** @param Page $page* @param Field $field* @return Inputfield**/public function getInputfield(Page $page, Field $field) {$inputfieldClass = $field->get('inputfieldClass');if($inputfieldClass) {$inputfield = $this->modules->getModule($inputfieldClass, array('noSubstitute' => true));} else {$inputfield = $this->modules->get(self::defaultInputfieldClass);}if(!$inputfield) {$inputfield = $this->modules->get(self::defaultInputfieldClass);$this->configHelper()->getInputfieldError($field);}/** @var InputfieldTextarea|InputfieldCKEditor $inputfield */$inputfield->class = $this->className();return $inputfield;}/*** Get database schema used by the Field** @param Field $field* @return array**/public function getDatabaseSchema(Field $field) {$schema = parent::getDatabaseSchema($field);$schema['data'] = 'mediumtext NOT NULL';$schema['keys']['data'] = 'FULLTEXT KEY data (data)';return $schema;}/*** Get an instance of the FieldtypeTextareaHelper config helper** @return FieldtypeTextareaHelper**/public function configHelper() {if(is_null($this->configHelper)) {require_once($this->wire('config')->paths->FieldtypeTextarea . 'FieldtypeTextareaHelper.php');$this->configHelper = new FieldtypeTextareaHelper();}return $this->configHelper;}/*** Get Inputfields to configure the Field** @param Field $field* @return InputfieldWrapper**/public function ___getConfigInputfields(Field $field) {$this->markupQA()->verbose(true);$inputfields = parent::___getConfigInputfields($field);$inputfields = $this->configHelper()->getConfigInputfields($field, $inputfields);return $inputfields;}/*** Export value** @param Page $page* @param Field $field* @param array|int|object|string $value* @param array $options* @return array|string**/public function ___exportValue(Page $page, Field $field, $value, array $options = array()) {$value = parent::___exportValue($page, $field, $value, $options);if(!empty($options['system'])) {if($field->get('contentType') >= self::contentTypeHTML) {$this->htmlReplacements($page, $field, $value, false);}}return $value;}/*** Import value** @param Page $page* @param Field $field* @param array|int|object|string $value* @param array $options* @return array|string**/public function ___importValue(Page $page, Field $field, $value, array $options = array()) {$value = parent::___importValue($page, $field, $value, $options);// update changed IDs represented in asset pathsif(strpos($value, '/assets/files/') !== false) {$originalID = (int) $page->get('_importOriginalID');if($originalID && $page->id && strpos($value, "/$originalID/")) {$value = str_replace("/assets/files/$originalID/", "/assets/files/$page->id/", $value);}}$contentType = $field->get('contentType');if($contentType == self::contentTypeHTML || $contentType == self::contentTypeImageHTML) {$value = $this->importValueHTML($value, $options);}return $value;}/*** Helper to importValue function for HTML-specific content** This primarily updates references to export-site URLs to the current site** @param string $value* @param array $options* @return string**/protected function importValueHTML($value, array $options) {// update changed root URLs in href or src attributes$config = $this->wire('config');$url = $config->urls->root;$host = $config->httpHost;$_url = isset($options['originalRootUrl']) ? $options['originalRootUrl'] : $url; // original URL$_host = isset($options['originalHost']) ? $options['originalHost'] : $host; // original hostif($_url === $url && $_host === $host) return $value;$findReplace = array();$href = 'href="';$src = 'src="';if($_host != $host) {$schemes = array('http://', 'https://');foreach($schemes as $scheme) {$findReplace[$href . $scheme . $_host . '/'] = $href . '/';$findReplace[$href . $scheme . $_host . '/'] = $href . '/';}}if($_url != $url) {$findReplace[$href . $_url] = $href . $url;$findReplace[$src . $_url] = $src . $url;}foreach($findReplace as $find => $replace) {if($find === $replace) continue;if(strpos($value, $find) === false) continue;$value = preg_replace('!(\s)' . $find . '!', '$1' . $replace, $value);}return $value;}/*** Find abstracted HTML/href attribute Textarea links to given $page** @param Page $page Find links to this page* @param string|bool $selector Optionally filter by selector or specify boolean true to assume "include=all".* @param string|Field $field Optionally limit to searching given field name/instance.* @param array $options Options to modify return value:* - `getIDs` (bool): Return array of page IDs rather than Page instances. (default=false)* - `getCount` (bool): Return a total count (int) of found pages rather than Page instances. (default=false)* - `confirm` (bool): Confirm that the links are present by looking at the actual page field data. (default=true)* You can specify false for this option to make it perform faster, but with a potentially less accurate result.* @return PageArray|array|int**/public function findLinks(Page $page, $selector = '', $field = '', array $options = array()) {$searchFields = array();if($selector === true) $selector = "include=all";foreach($this->wire('fields') as $f) {if($field) {if("$f" != "$field") continue;} else {// limit to fields with contentTypeHTML and htmlLinkAbstract$contentType = $f->get('contentType');if(empty($contentType)) continue;if($contentType != self::contentTypeHTML && $contentType != self::contentTypeImageHTML) continue;$htmlOptions = $f->get('htmlOptions');if(!is_array($htmlOptions) || !in_array(self::htmlLinkAbstract, $htmlOptions)) continue;}$searchFields[$f->name] = $f->name;}if(!count($searchFields)) return $this->wire('pages')->newPageArray();return $this->markupQA()->findLinks($page, $searchFields, $selector, $options);}}