PicoTargetBlank.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. <?php
  2. // https://github.com/picocms/Pico/issues/394#issuecomment-330030163
  3. // unicode arrow: 🡵 or 🡥 or ↗ (the last one seems to be the most common)
  4. // CSS:
  5. // a.external::after { line-height: 100%; vertical-align: super; font-size: 60%; content: "↗" }
  6. // and if you don't want the arrow on some external links, add the "noarrow" class. CSS:
  7. // a.external.noarrow::after { content: '' }
  8. class PicoTargetBlank extends AbstractPicoPlugin
  9. {
  10. /**
  11. * This plugin is enabled by default
  12. * but you can still disable it in config.yml:
  13. *
  14. * PicoTargetBlank:
  15. * enabled: false
  16. *
  17. * You can also set the rel= attribute, or disable it with an empty string:
  18. * rel: 'noopener noreferrer nofollow
  19. * or
  20. * rel: ''
  21. * etc.
  22. *
  23. * defaults to 'noopener noreferrer' if unspecified.
  24. * Also see:
  25. * pointjupiter.com/what-noopener-noreferrer-nofollow-explained/
  26. */
  27. const API_VERSION = 3;
  28. protected $doit = true;
  29. public function onConfigLoaded(&$config)
  30. {
  31. $this->rel = $this->getPluginConfig('rel', 'noopener noreferrer');
  32. $this->exclude = $this->getPluginConfig('exclude',null);
  33. }
  34. public function onRequestUrl(&$url)
  35. {
  36. $name = $url == "" ? "index" : $url;
  37. if(in_array($url,$this->exclude)) $this->doit=false;
  38. }
  39. public function onContentParsed(&$content)
  40. {
  41. if(trim($content)=="") return;
  42. if($this->doit === false) return;
  43. $dom = new DOMDocument();
  44. $dom->preserveWhiteSpace = true;
  45. // encoding problems require us to jump through hoops here...
  46. //~ $dom->loadXML($utf8.$content,LIBXML_NOWARNING|LIBXML_NONET|LIBXML_COMPACT|LIBXML_NOERROR);
  47. // $content is a html fragment. We like to modify its content with DOMDocument, but it prefers complete documents and completes them
  48. // if necessary. This leads to strangeness. The best thing I found is to give it a complete document with all required headers and tags,
  49. // then strip those off in the end. That way DOMDocument doesn't meddle, and we have control. Hah, hopefully.
  50. $dom->loadHTML('<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$content.'</body></html>',
  51. LIBXML_NONET|LIBXML_COMPACT|LIBXML_HTML_NODEFDTD|LIBXML_NOWARNING);
  52. //~ LIBXML_NOEMPTYTAG|LIBXML_NOENT|LIBXML_HTML_NOIMPLIED|LIBXML_NOERROR);
  53. $trim_before=92; // What we need to trim when saving (the above string added to $content)
  54. $trim_after=-15; // - " -
  55. // please read this: stackoverflow.com/a/11310258
  56. //~ $dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'));
  57. // This we need only once
  58. // remove port from HTTP_HOST, if applicable
  59. $host = preg_replace('/:[0-9]+$/','',$_SERVER['HTTP_HOST']);
  60. // add target _blank to external links
  61. $links = $dom->getElementsByTagName('a');
  62. //~ var_dump($links);
  63. //~ array_walk(iterator_to_array($links),'var_dump');
  64. //~ die();
  65. //~ echo "Before ";
  66. foreach ($links as $item) {
  67. $href = parse_url($item->getAttribute('href'), PHP_URL_HOST);
  68. //~ echo "foreach ".$href.' ';
  69. // IF THIS LINK IS EXTERNAL (differs from the server)...
  70. if (!empty($href) && $href != $host){
  71. // open in new tab
  72. $item->setAttribute('target','_blank');
  73. // set rel attribute (configurable)
  74. if (!empty($this->rel)) $item->setAttribute('rel',$this->rel);
  75. // if it has a class already, preserve it
  76. if ( $item->hasAttribute('class') ) {
  77. $string = $item->getAttribute('class') . ' external';
  78. }
  79. else $string = 'external';
  80. // set class to $string
  81. $item->setAttribute('class',$string);
  82. // if it has a title already, preserve it
  83. if ( $item->hasAttribute('title') ) {
  84. $string = $item->getAttribute('title') . ' - external link: ' . $href;
  85. }
  86. else $string = 'External: ' . $href;
  87. // set title to $string
  88. $item->setAttribute('title',$string);
  89. }
  90. }
  91. //~ // we now have a full html document in $dom. Let's extract only what is between
  92. //~ // <body> tags,as it was before. - stackoverflow.com/a/18090774
  93. //~ $content='';
  94. //~ foreach($dom->getElementsByTagName("body")->item(0)->childNodes as $child) {
  95. //~ $content .= $dom->saveXML($child);
  96. //~ }
  97. $content = $dom->saveHTML();
  98. // remove the tag we added at loadHTML
  99. $content = substr($content,$trim_before,$trim_after);
  100. // please read this: stackoverflow.com/a/20675396
  101. //~ $content = $dom->saveHTML($dom->documentElement);
  102. }
  103. }