metatag.inc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. <?php
  2. /**
  3. * @file
  4. * Metatag primary classes.
  5. */
  6. /**
  7. * The master interface for all tags.
  8. */
  9. interface DrupalMetaTagInterface {
  10. /**
  11. * Constructor.
  12. *
  13. * @param array $info
  14. * The information about the meta tag from metatag_get_info().
  15. */
  16. function __construct(array $info, array $data = array());
  17. public function getForm();
  18. //function validateForm();
  19. //function processForm();
  20. public function getValue();
  21. function getWeight();
  22. function getElement();
  23. function tidyValue($value);
  24. function convertUrlToAbsolute($url);
  25. function truncate($value);
  26. function maxlength();
  27. static function text_summary($text, $size);
  28. }
  29. /**
  30. * The default meta tag class from which all others inherit.
  31. */
  32. class DrupalDefaultMetaTag implements DrupalMetaTagInterface {
  33. /**
  34. * All of the basic information about this tag.
  35. *
  36. * @var array
  37. */
  38. protected $info;
  39. /**
  40. * The values submitted for this tag.
  41. *
  42. * @var array
  43. */
  44. protected $data = array('value' => '');
  45. /**
  46. * This item's weight; used for sorting the output.
  47. *
  48. * @var float
  49. */
  50. protected $weight = 0;
  51. /**
  52. * Constructor.
  53. */
  54. function __construct(array $info, array $data = NULL) {
  55. $this->info = $info;
  56. if (isset($data)) {
  57. $this->data = $data;
  58. }
  59. }
  60. /**
  61. * Calculate the weight of this meta tag.
  62. *
  63. * @return int
  64. * Weight.
  65. */
  66. function getWeight() {
  67. static $counter = 0;
  68. // If no weight value is found, stack this meta tag at the end.
  69. $weight = 100;
  70. if (!empty($this->info['weight'])) {
  71. $weight = $this->info['weight'];
  72. }
  73. return $weight + ($counter++ * 0.1);
  74. }
  75. /**
  76. * Build the form for this meta tag.
  77. *
  78. * @return array
  79. * A standard FormAPI array.
  80. */
  81. public function getForm(array $options = array()) {
  82. return array();
  83. }
  84. /**
  85. * Get the string value of this meta tag.
  86. *
  87. * @return string
  88. * The value of this meta tag.
  89. */
  90. public function getValue(array $options = array()) {
  91. $value = $this->tidyValue($this->data['value']);
  92. // Translate the final output string prior to output. Use the
  93. // 'output' i18n_string object type, and pass along the meta tag's
  94. // options as the context so it can be handled appropriately.
  95. $value = metatag_translate_metatag($value, $this->info['name'], $options, NULL, TRUE);
  96. return $this->truncate($this->tidyValue($this->data['value']));
  97. }
  98. /**
  99. * Get the HTML tag for this meta tag.
  100. *
  101. * @return array
  102. * A render array for this meta tag.
  103. */
  104. public function getElement(array $options = array()) {
  105. $value = $this->getValue($options);
  106. if (strlen($value) === 0) {
  107. return array();
  108. }
  109. // The stack of elements that will be output.
  110. $elements = array();
  111. // Dynamically add each option to this setting.
  112. $base_element = isset($this->info['element']) ? $this->info['element'] : array();
  113. // Single item.
  114. if (empty($this->info['multiple'])) {
  115. $values = array($value);
  116. }
  117. // Multiple items.
  118. else {
  119. $values = array_filter(explode(',', $value));
  120. }
  121. // Loop over each item.
  122. if (!empty($values)) {
  123. foreach ($values as $ctr => $value) {
  124. $value = trim($value);
  125. // Some meta tags must be output as secure URLs.
  126. if (!empty($this->info['secure'])) {
  127. $value = str_replace('http://', 'https://', $value);
  128. }
  129. // Combine the base configuration for this meta tag with the value.
  130. $element = $base_element + array(
  131. '#theme' => 'metatag',
  132. '#tag' => 'meta',
  133. '#id' => 'metatag_' . $this->info['name'] . '_' . $ctr,
  134. '#name' => $this->info['name'],
  135. '#value' => $value,
  136. '#weight' => $this->getWeight(),
  137. );
  138. // Add header information if desired.
  139. if (!empty($this->info['header'])) {
  140. $element['#attached']['drupal_add_http_header'][] = array($this->info['header'], $value);
  141. }
  142. $elements[] = array($element, $element['#id']);
  143. }
  144. }
  145. if (!empty($elements)) {
  146. return array(
  147. '#attached' => array('drupal_add_html_head' => $elements),
  148. );
  149. }
  150. }
  151. /**
  152. * Remove unwanted formatting from a meta tag.
  153. *
  154. * @param string $value
  155. * The meta tag value to be tidied up.
  156. *
  157. * @return string
  158. * The meta tag value after it has been tidied up.
  159. */
  160. public function tidyValue($value) {
  161. // Check for Media strings from the WYSIWYG submodule.
  162. if (module_exists('media_wysiwyg') && strpos($value, '[[{') !== FALSE) {
  163. // In https://www.drupal.org/node/2129273 media_wysiwyg_filter() was
  164. // changed to require several additional arguments.
  165. $langcode = language_default('language');
  166. $value = media_wysiwyg_filter($value, NULL, NULL, $langcode, NULL, NULL);
  167. }
  168. // Specifically replace encoded spaces, because some WYSIWYG editors are
  169. // silly. Do this before decoding the other HTML entities so that the output
  170. // doesn't end up with a bunch of a-circumflex characters.
  171. $value = str_replace('&nbsp;', ' ', $value);
  172. // Decode HTML entities.
  173. $value = decode_entities($value);
  174. // Remove any HTML code that might have been included.
  175. $value = strip_tags($value);
  176. // Strip errant whitespace.
  177. $value = str_replace(array("\r\n", "\n", "\r", "\t"), ' ', $value);
  178. $value = str_replace(' ', ' ', $value);
  179. $value = str_replace(' ', ' ', $value);
  180. $value = trim($value);
  181. return $value;
  182. }
  183. /**
  184. * Make sure a given URL is absolute.
  185. *
  186. * @param string $url
  187. * The URL to convert to an absolute URL.
  188. *
  189. * @return string
  190. * The argument converted to an absolute URL.
  191. */
  192. function convertUrlToAbsolute($url) {
  193. // Convert paths relative to the hostname, that start with a slash, to
  194. // ones that are relative to the Drupal root path; ignore protocol-relative
  195. // URLs.
  196. if (strpos($url, base_path()) === 0 && strpos($url, '//') !== 0) {
  197. // Logic:
  198. // * Get the length of the base_path(),
  199. // * Get a portion of the image's path starting from the position equal
  200. // to the base_path()'s length; this will result in a path relative
  201. // to the Drupal installation's base directory.
  202. $len = strlen(base_path());
  203. $url = substr($url, $len);
  204. }
  205. // Pass everything else through file_create_url(). The alternative is to
  206. // use url() but it would insert '?q=' into the path.
  207. return file_create_url($url);
  208. }
  209. /**
  210. * Shorten a string to a certain length using text_summary().
  211. *
  212. * @param string $value
  213. * String to shorten.
  214. *
  215. * @return string
  216. * Shortened string.
  217. */
  218. function truncate($value) {
  219. $maxlength = $this->maxlength();
  220. if (!empty($value) && $maxlength > 0) {
  221. $value = $this->text_summary($value, $maxlength);
  222. }
  223. return $value;
  224. }
  225. /**
  226. * Identify the maximum length of which strings will be allowed.
  227. *
  228. * @return int
  229. * Maxlenght.
  230. */
  231. function maxlength() {
  232. if (isset($this->info['maxlength'])) {
  233. return intval(variable_get('metatag_maxlength_' . $this->info['name'], $this->info['maxlength']));
  234. }
  235. return 0;
  236. }
  237. /**
  238. * Copied from text.module with the following changes:.
  239. *
  240. * Change 1: $size is required.
  241. * Change 2: $format is removed.
  242. * Change 3: Don't trim at the end of short sentences
  243. * (https://www.drupal.org/node/1620104).
  244. * Change 4: Word boundaries (https://www.drupal.org/node/1482178).
  245. * Change 5: Trim the final string.
  246. */
  247. static function text_summary($text, $size) {
  248. // if (!isset($size)) {
  249. // // What used to be called 'teaser' is now called 'summary', but
  250. // // the variable 'teaser_length' is preserved for backwards compatibility.
  251. // $size = variable_get('teaser_length', 600);
  252. // }
  253. // Find where the delimiter is in the body.
  254. $delimiter = strpos($text, '<!--break-->');
  255. // If the size is zero, and there is no delimiter,
  256. // the entire body is the summary.
  257. if ($size == 0 && $delimiter === FALSE) {
  258. return $text;
  259. }
  260. // If a valid delimiter has been specified, use it to chop off the summary.
  261. if ($delimiter !== FALSE) {
  262. return substr($text, 0, $delimiter);
  263. }
  264. // We check for the presence of the PHP evaluator filter in the current
  265. // format. If the body contains PHP code, we do not split it up to prevent
  266. // parse errors.
  267. // if (isset($format)) {
  268. // $filters = filter_list_format($format);
  269. // if (isset($filters['php_code']) && $filters['php_code']->status && strpos($text, '<?') !== FALSE) {
  270. // return $text;
  271. // }
  272. // }
  273. // If we have a short body, the entire body is the summary.
  274. if (drupal_strlen($text) <= $size) {
  275. return $text;
  276. }
  277. // If the delimiter has not been specified, try to split at paragraph or
  278. // sentence boundaries.
  279. // The summary may not be longer than maximum length specified.
  280. // Initial slice.
  281. $summary = truncate_utf8($text, $size);
  282. // Store the actual length of the UTF8 string -- which might not be the same
  283. // as $size.
  284. $max_rpos = strlen($summary);
  285. // How much to cut off the end of the summary so that it doesn't end in the
  286. // middle of a paragraph, sentence, or word.
  287. // Initialize it to maximum in order to find the minimum.
  288. $min_rpos = $max_rpos;
  289. // Store the reverse of the summary. We use strpos on the reversed needle
  290. // and haystack for speed and convenience.
  291. $reversed = strrev($summary);
  292. // Build an array of arrays of break points grouped by preference.
  293. $break_points = array();
  294. // A paragraph near the end of sliced summary is most preferable.
  295. $break_points[] = array('</p>' => 0);
  296. // If no complete paragraph then treat line breaks as paragraphs.
  297. // $line_breaks = array('<br />' => 6, '<br>' => 4);
  298. // Newline only indicates a line break if line break converter
  299. // filter is present.
  300. // if (isset($filters['filter_autop'])) {
  301. // $line_breaks["\n"] = 1;
  302. // }
  303. // $break_points[] = $line_breaks;
  304. // If the first paragraph is too long, split at the end of a sentence.
  305. // $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
  306. // From https://www.drupal.org/node/1482178.
  307. // If the first sentence is too long, split at the first word break.
  308. $word_breaks = array(' ' => 0, "\t" => 0);
  309. $break_points[] = $word_breaks;
  310. // Iterate over the groups of break points until a break point is found.
  311. foreach ($break_points as $points) {
  312. // Look for each break point, starting at the end of the summary.
  313. foreach ($points as $point => $offset) {
  314. // The summary is already reversed, but the break point isn't.
  315. $rpos = strpos($reversed, strrev($point));
  316. if ($rpos !== FALSE) {
  317. $min_rpos = min($rpos + $offset, $min_rpos);
  318. }
  319. }
  320. // If a break point was found in this group, slice and stop searching.
  321. if ($min_rpos !== $max_rpos) {
  322. // Don't slice with length 0. Length must be <0 to slice from RHS.
  323. $summary = ($min_rpos === 0) ? $summary : substr($summary, 0, 0 - $min_rpos);
  324. break;
  325. }
  326. }
  327. // If the htmlcorrector filter is present, apply it to the generated
  328. // summary.
  329. // if (isset($filters['filter_htmlcorrector'])) {
  330. // $summary = _filter_htmlcorrector($summary);
  331. // }
  332. return trim($summary);
  333. }
  334. }
  335. /**
  336. * Text-based meta tag controller.
  337. */
  338. class DrupalTextMetaTag extends DrupalDefaultMetaTag {
  339. /**
  340. * {@inheritdoc}
  341. */
  342. public function getForm(array $options = array()) {
  343. $options += array(
  344. 'token types' => array(),
  345. );
  346. $form['value'] = isset($this->info['form']) ? $this->info['form'] : array();
  347. $form['value'] += array(
  348. '#type' => 'textfield',
  349. '#title' => $this->info['label'],
  350. '#description' => !empty($this->info['description']) ? $this->info['description'] : '',
  351. '#default_value' => isset($this->data['value']) ? $this->data['value'] : '',
  352. '#element_validate' => array('token_element_validate'),
  353. '#token_types' => $options['token types'],
  354. '#maxlength' => 1024,
  355. );
  356. // Optional handling for items that allow multiple values.
  357. if (!empty($this->info['multiple'])) {
  358. $form['value']['#description'] .= ' ' . t('Multiple values may be used, separated by a comma. Note: Tokens that return multiple values will be handled automatically.');
  359. }
  360. // Optionally limit the field to a certain length.
  361. $maxlength = $this->maxlength();
  362. if (!empty($maxlength)) {
  363. $form['value']['#description'] .= ' ' . t('This will be truncated to a maximum of %max characters.', array('%max' => $maxlength));
  364. }
  365. // Optional handling for images.
  366. if (!empty($this->info['image'])) {
  367. $form['value']['#description'] .= ' ' . t('This will be able to extract the URL from an image field.');
  368. }
  369. // Optional handling for languages.
  370. if (!empty($this->info['is_language'])) {
  371. $form['value']['#description'] .= ' ' . t('This will not be displayed if it is set to the "Language neutral" (i.e. "und").');
  372. }
  373. // Optional support for select_or_other.
  374. if ($form['value']['#type'] == 'select' && !empty($this->info['select_or_other']) && module_exists('select_or_other')) {
  375. $form['value']['#type'] = 'select_or_other';
  376. $form['value']['#other'] = t('Other (please type a value)');
  377. $form['value']['#multiple'] = FALSE;
  378. $form['value']['#other_unknown_defaults'] = 'other';
  379. $form['value']['#other_delimiter'] = FALSE;
  380. $form['value']['#theme'] = 'select_or_other';
  381. $form['value']['#select_type'] = 'select';
  382. $form['value']['#element_validate'] = array('select_or_other_element_validate');
  383. }
  384. // Support for dependencies, using Form API's #states system.
  385. // @see metatag.api.php.
  386. // @see https://api.drupal.org/drupal_process_states
  387. if (!empty($this->info['dependencies'])) {
  388. foreach ($this->info['dependencies'] as $specs) {
  389. $form['value']['#states']['visible'][':input[name*="[' . $specs['dependency'] . '][' . $specs['attribute'] . ']"]'] = array(
  390. $specs['condition'] => $specs['value'],
  391. );
  392. }
  393. }
  394. return $form;
  395. }
  396. /**
  397. * {@inheritdoc}
  398. */
  399. public function getValue(array $options = array()) {
  400. $options += array(
  401. 'instance' => '',
  402. 'token data' => array(),
  403. // Remove any remaining token after the string is parsed.
  404. 'clear' => TRUE,
  405. 'sanitize' => variable_get('metatag_token_sanitize', FALSE),
  406. 'raw' => FALSE,
  407. );
  408. $value = $this->data['value'];
  409. if (empty($options['raw'])) {
  410. // Give other modules the opportunity to use hook_metatag_pattern_alter()
  411. // to modify defined token patterns and values before replacement.
  412. drupal_alter('metatag_pattern', $value, $options['token data'], $this->info['name']);
  413. $value = token_replace($value, $options['token data'], $options);
  414. }
  415. // Special handling for language meta tags.
  416. if (!empty($this->info['is_language'])) {
  417. // If the meta tag value equals LANGUAGE_NONE, i.e. "und", then don't
  418. // output it.
  419. if (is_string($value) && $value == LANGUAGE_NONE) {
  420. $value = '';
  421. }
  422. }
  423. // Special handling for images and other URLs.
  424. if (!empty($this->info['image']) || !empty($this->info['url'])) {
  425. // Support multiple items, whether it's needed or not. Also remove the
  426. // empty values and reindex the array.
  427. $values = array_values(array_filter(explode(',', $value)));
  428. // If this meta tag does *not* allow multiple items, only keep the first
  429. // one.
  430. if (empty($this->info['multiple']) && !empty($values[0])) {
  431. $values = array($values[0]);
  432. }
  433. foreach ($values as $key => &$image_value) {
  434. // Remove any unwanted whitespace around the value.
  435. $image_value = trim($image_value);
  436. // If this contains embedded image tags, extract the image URLs.
  437. if (!empty($this->info['image']) && strip_tags($image_value) != $image_value) {
  438. $matches = array();
  439. preg_match('/src="([^"]*)"/', $image_value, $matches);
  440. if (!empty($matches[1])) {
  441. $image_value = $matches[1];
  442. }
  443. }
  444. // Convert the URL to an absolute URL.
  445. $image_value = $this->convertUrlToAbsolute($image_value);
  446. // Replace spaces the URL encoded entity to avoid validation problems.
  447. $image_value = str_replace(' ', '%20', $image_value);
  448. }
  449. // Combine the multiple values into a single string.
  450. $value = implode(',', $values);
  451. }
  452. // Clean up the string a bit.
  453. $value = $this->tidyValue($value);
  454. // Optionally truncate the value.
  455. $value = $this->truncate($value);
  456. // Translate the final output string prior to output. Use the
  457. // 'output' i18n_string object type, and pass along the meta tag's
  458. // options as the context so it can be handled appropriately.
  459. $value = metatag_translate_metatag($value, $this->info['name'], $options, NULL, TRUE);
  460. return $value;
  461. }
  462. }
  463. /**
  464. * Link type meta tag controller.
  465. */
  466. class DrupalLinkMetaTag extends DrupalTextMetaTag {
  467. /**
  468. * {@inheritdoc}
  469. */
  470. public function getElement(array $options = array()) {
  471. $element = isset($this->info['element']) ? $this->info['element'] : array();
  472. $value = $this->getValue($options);
  473. if (strlen($value) === 0) {
  474. return array();
  475. }
  476. $element += array(
  477. '#theme' => 'metatag_link_rel',
  478. '#tag' => 'link',
  479. '#id' => 'metatag_' . $this->info['name'],
  480. '#name' => $this->info['name'],
  481. '#value' => $value,
  482. '#weight' => $this->getWeight(),
  483. );
  484. if (!isset($this->info['header']) || !empty($this->info['header'])) {
  485. // Also send the generator in the HTTP header.
  486. // @todo This does not support 'rev' or alternate link headers.
  487. $element['#attached']['drupal_add_http_header'][] = array(
  488. 'Link', '<' . $value . '>;' . drupal_http_header_attributes(array('rel' => $element['#name'])), TRUE,
  489. );
  490. }
  491. return array(
  492. '#attached' => array('drupal_add_html_head' => array(array($element, $element['#id']))),
  493. );
  494. }
  495. }
  496. /**
  497. * Title meta tag controller.
  498. *
  499. * This extends DrupalTextMetaTag as we need to alter variables in
  500. * template_preprocess_html() rather output a normal meta tag.
  501. */
  502. class DrupalTitleMetaTag extends DrupalTextMetaTag {
  503. /**
  504. * {@inheritdoc}
  505. */
  506. public function getElement(array $options = array()) {
  507. $element = array();
  508. if ($value = $this->getValue($options)) {
  509. $element['#attached']['metatag_set_preprocess_variable'][] = array(
  510. 'html',
  511. 'head_title',
  512. decode_entities($value),
  513. );
  514. $element['#attached']['metatag_set_preprocess_variable'][] = array(
  515. 'html',
  516. 'head_array',
  517. array('title' => $value),
  518. );
  519. }
  520. return $element;
  521. }
  522. }
  523. /**
  524. * Multiple value meta tag controller.
  525. */
  526. class DrupalListMetaTag extends DrupalDefaultMetaTag {
  527. /**
  528. * {@inheritdoc}
  529. */
  530. function __construct(array $info, array $data = NULL) {
  531. // Ensure that the $data['value] argument is an array.
  532. if (empty($data['value'])) {
  533. $data['value'] = array();
  534. }
  535. $data['value'] = (array) $data['value'];
  536. parent::__construct($info, $data);
  537. }
  538. /**
  539. * {@inheritdoc}
  540. */
  541. public function getForm(array $options = array()) {
  542. $form['value'] = isset($this->info['form']) ? $this->info['form'] : array();
  543. $form['value'] += array(
  544. '#type' => 'checkboxes',
  545. '#title' => $this->info['label'],
  546. '#description' => !empty($this->info['description']) ? $this->info['description'] : '',
  547. '#default_value' => isset($this->data['value']) ? $this->data['value'] : array(),
  548. );
  549. return $form;
  550. }
  551. /**
  552. * {@inheritdoc}
  553. */
  554. public function getValue(array $options = array()) {
  555. $values = array_keys(array_filter($this->data['value']));
  556. sort($values);
  557. $value = implode(', ', $values);
  558. $value = $this->tidyValue($value);
  559. // Translate the final output string prior to output. Use the
  560. // 'output' i18n_string object type, and pass along the meta tag's
  561. // options as the context so it can be handled appropriately.
  562. $value = metatag_translate_metatag($value, $this->info['name'], $options, NULL, TRUE);
  563. return $value;
  564. }
  565. }
  566. /**
  567. * Date interval meta tag controller.
  568. */
  569. class DrupalDateIntervalMetaTag extends DrupalDefaultMetaTag {
  570. /**
  571. * {@inheritdoc}
  572. */
  573. public function getForm(array $options = array()) {
  574. $form['value'] = array(
  575. '#type' => 'textfield',
  576. '#title' => t('!title interval', array('!title' => $this->info['label'])),
  577. '#default_value' => isset($this->data['value']) ? $this->data['value'] : '',
  578. '#element_validate' => array('element_validate_integer_positive'),
  579. '#maxlength' => 4,
  580. '#description' => isset($this->info['description']) ? $this->info['description'] : '',
  581. );
  582. $form['period'] = array(
  583. '#type' => 'select',
  584. '#title' => t('!title interval type', array('!title' => $this->info['label'])),
  585. '#default_value' => isset($this->data['period']) ? $this->data['period'] : '',
  586. '#options' => array(
  587. '' => t('- none -'),
  588. 'day' => t('Day(s)'),
  589. 'week' => t('Week(s)'),
  590. 'month' => t('Month(s)'),
  591. 'year' => t('Year(s)'),
  592. ),
  593. );
  594. return $form;
  595. }
  596. /**
  597. * {@inheritdoc}
  598. */
  599. public function getValue(array $options = array()) {
  600. $value = '';
  601. if (!empty($this->data['value'])) {
  602. $interval = intval($this->data['value']);
  603. if (!empty($interval) && !empty($this->data['period'])) {
  604. $period = $this->data['period'];
  605. $value = format_plural($interval, '@count ' . $period, '@count ' . $period . 's');
  606. }
  607. }
  608. // Translate the final output string prior to output. Use the 'output'
  609. // i18n_string object type, and pass along the meta tag's options as the
  610. // context so it can be handled appropriately.
  611. $value = metatag_translate_metatag($value, $this->info['name'], $options, NULL, TRUE);
  612. return $value;
  613. }
  614. }