/** * Given a string $str and values $vars, replace tags in the string with the values * * The $vars may also be an object, in which case values will be pulled as properties of the object. * * By default, tags are specified in the format: {first_name} where first_name is the name of the * variable to pull from $vars, '{' is the opening tag character, and '}' is the closing tag char. * * The tag parser can also handle subfields and OR tags, if $vars is an object that supports that. * For instance {products.title} is a subfield, and {first_name|title|name} is an OR tag. * * @param string $str The string to operate on (where the {tags} might be found) * @param WireData|object|array Object or associative array to pull replacement values from. * @param array $options Array of optional changes to default behavior, including: * - tagOpen: The required opening tag character(s), default is '{' * - tagClose: The optional closing tag character(s), default is '}' * - recursive: If replacement value contains tags, populate those too? Default=false. * - removeNullTags: If a tag resolves to a NULL, remove it? If false, tag will remain. Default=true. * - entityEncode: Entity encode the values pulled from $vars? Default=false. * - entityDecode: Entity decode the values pulled from $vars? Default=false. * @return string String with tags populated. * */ function wirePopulateStringTags($str, $vars, array $options = array()) { $defaults = array('tagOpen' => '{', 'tagClose' => '}', 'recursive' => false, 'removeNullTags' => true, 'entityEncode' => false, 'entityDecode' => false); $options = array_merge($defaults, $options); // check if this string even needs anything populated if (strpos($str, $options['tagOpen']) === false) { return $str; } if (strlen($options['tagClose']) && strpos($str, $options['tagClose']) === false) { return $str; } // find all tags $tagOpen = preg_quote($options['tagOpen']); $tagClose = preg_quote($options['tagClose']); $numFound = preg_match_all('/' . $tagOpen . '([-_.|a-zA-Z0-9]+)' . $tagClose . '/', $str, $matches); if (!$numFound) { return $str; } $replacements = array(); // create a list of replacements by finding replacement values in $vars foreach ($matches[1] as $key => $fieldName) { $tag = $matches[0][$key]; if (isset($replacements[$tag])) { continue; } // if already found, don't continue $fieldValue = null; if (is_object($vars)) { if ($vars instanceof Page) { $fieldValue = $vars->getMarkup($fieldName); } else { if ($vars instanceof WireData) { $fieldValue = $vars->get($fieldName); } else { $fieldValue = $vars->{$fieldName}; } } } else { if (is_array($vars)) { $fieldValue = isset($vars[$fieldName]) ? $vars[$fieldName] : null; } } if ($options['entityEncode']) { $fieldValue = htmlentities($fieldValue, ENT_QUOTES, 'UTF-8', false); } if ($options['entityDecode']) { $fieldValue = html_entity_decode($fieldValue, ENT_QUOTES, 'UTF-8'); } $replacements[$tag] = $fieldValue; } // replace the tags foreach ($replacements as $tag => $value) { // populate tags recursively, if asked to do so if ($options['recursive'] && strpos($value, $options['tagOpen'])) { $opt = array_merge($options, array('recursive' => false)); // don't go recursive beyond 1 level $value = wirePopulateStringTags($value, $vars, $opt); } // replace tags with replacement values $str = str_replace($tag, $value, $str); } return $str; }
/** * Return the markup value for a given field name or {tag} string * * 1. If given a field name (or name.subname or name1|name2|name3) it will return the * markup value as defined by the fieldtype. * * 2. If given a string with field names referenced in {tags}, it will populate those * tags and return the populated string. * * @param string $key Field name or markup string with field {name} tags in it * @return string * */ public function ___getMarkup($key) { $value = ''; if (strpos($key, '{') !== false && strpos($key, '}')) { // populate a string with {tags} // note that the wirePopulateStringTags() function calls back on this method // to retrieve the markup values for each of the found field names return wirePopulateStringTags($key, $this); } if (strpos($key, '|') !== false) { $key = $this->getFieldFirstValue($key, true); if (!$key) { return ''; } } if ($this->wire('sanitizer')->name($key) != $key) { // not a possible field name return ''; } $parts = strpos($key, '.') ? explode('.', $key) : array($key); $value = $this; do { $name = array_shift($parts); $field = null; if ($this->template && $this->template->fieldgroup) { $field = $this->template->fieldgroup->getField($name); } if (!$field && $this->wire($name)) { // disallow API vars $value = ''; break; } if ($value instanceof Page) { $value = $value->getFormatted($name); } else { if ($value instanceof Wire) { $value = $value->get($name); } else { $value = $value->{$name}; } } if ($field && count($parts) < 2) { // this is a field that will provide its own formatted value $subname = count($parts) == 1 ? array_shift($parts) : ''; if (!$this->wire($subname)) { $value = $field->type->markupValue($this, $field, $value, $subname); } } } while (is_object($value) && count($parts)); if (is_object($value)) { if ($value instanceof Page) { $value = $value->getFormatted('title|name'); } if ($value instanceof PageArray) { $value = $value->getMarkup(); } } if (!is_string($value)) { $value = (string) $value; } return $value; }
/** * Return the markup value for a given field name or {tag} string * * 1. If given a field name (or name.subname or name1|name2|name3) it will return the * markup value as defined by the fieldtype. * * 2. If given a string with field names referenced in {tags}, it will populate those * tags and return the populated string. * * @param string $key Field name or markup string with field {name} tags in it * @return string * */ public function ___getMarkup($key) { $value = ''; if (strpos($key, '{') !== false && strpos($key, '}')) { // populate a string with {tags} // note that the wirePopulateStringTags() function calls back on this method // to retrieve the markup values for each of the found field names $value = wirePopulateStringTags($key, $this); } else { if (strpos($key, '|') !== false) { $key = $this->getFieldFirstValue($key, true); if (!$key) { return ''; } } if ($this->wire('sanitizer')->name($key) != $key) { // not a possible field name return ''; } $name = $key; $subname = ''; if (strpos($name, '.')) { list($name, $subname) = explode('.', $key); } $field = $this->fieldgroup->getField($name); if ($field) { // corresponds to a known field in this page's fieldgroup $value = $this->getFormatted($name); $value = $field->type->markupValue($this, $field, $value, $subname); } else { if ($this->wire($name) || $subname && $this->wire($subname)) { // we don't allow API variables in markup values } else { // native or unknown field $value = $this->getFormatted($key); } } if (is_object($value)) { if ($value instanceof Page) { $value = $value->getFormatted('title|name'); } if ($value instanceof PageArray) { $value = $value->getMarkup(); } } } if (!is_string($value)) { $value = (string) $value; } return $value; }
/** * Execute a function for each item, or build a string or array from each item * * @param callable|function|string|array|null $func Accepts any of the following: * 1. Callable function that each item will be passed to as first argument. If this * function returns a string, it will be appended to that of the other items and * the result returned by this each() method. * 2. Markup or text string with variable {tags} within it where each {tag} resolves * to a property in each item. This each() method will return the concatenated result. * 3. A property name (string) common to items in this WireArray. The result will be * returned as an array. * 4. An array of property names common to items in this WireArray. The result will be * returned as an array of associative arrays indexed by property name. * * @return array|null|string|WireArray Returns one of the following (related to numbers above). * 1a. $this WireArray of given a function that has no return values. * 1b. Returns a string containing the concatenated results of all function calls, if * your function returns strings. * 2. Returns the processed and concatenated result (string) of all items in your * template string. * 3. Returns regular PHP array of the property values for each item you requested. * 4. Returns an array of associative arrays containing the property values for each item * you requested. * */ public function each($func = null) { $result = null; // return value, if it's detected that one is desired if (is_callable($func)) { $funcInfo = new ReflectionFunction($func); $useIndex = $funcInfo->getNumberOfParameters() > 1; foreach ($this as $index => $item) { $val = $useIndex ? $func($index, $item) : $func($item); if ($val && is_string($val)) { // function returned a string, so we assume they are wanting us to return the result if (is_null($result)) { $result = ''; } // if returned value resulted in {tags}, go ahead and parse them if (strpos($val, '{') !== false && strpos($val, '}')) { $val = wirePopulateStringTags($val, $item); } $result .= $val; } } } else { if (is_string($func) && strpos($func, '{') !== false && strpos($func, '}')) { // string with variables $result = ''; foreach ($this as $item) { $result .= wirePopulateStringTags($func, $item); } } else { // array or string or null if (is_null($func)) { $func = 'name'; } $result = $this->explode($func); } } return $result === null ? $this : $result; }