PageRenderTime 47ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/simple-tags/inc/class.client.autolinks.php

https://gitlab.com/vanafroo/landingpage
PHP | 287 lines | 185 code | 41 blank | 61 comment | 53 complexity | 5f5aeefa55252ffed0033edb2cb397dc MD5 | raw file
  1. <?php
  2. class SimpleTags_Client_Autolinks {
  3. static $posts = array();
  4. static $link_tags = array();
  5. /**
  6. * Constructor
  7. *
  8. * @return void
  9. * @author Amaury Balmer
  10. */
  11. public function __construct() {
  12. $auto_link_priority = SimpleTags_Plugin::get_option_value( 'auto_link_priority' );
  13. if ( (int) $auto_link_priority == 0 ) {
  14. $auto_link_priority = 12;
  15. }
  16. // Auto link tags
  17. add_filter( 'the_posts', array( __CLASS__, 'the_posts' ), 10, 2 );
  18. if ( SimpleTags_Plugin::get_option_value( 'auto_link_views' ) != 'no' ) {
  19. add_filter( 'the_content', array( __CLASS__, 'the_content' ), $auto_link_priority );
  20. }
  21. }
  22. /**
  23. * Stock posts ID as soon as possible
  24. * TODO: test if post_type allow post_tag before keep post ID
  25. *
  26. * @param array $posts
  27. *
  28. * @return array
  29. */
  30. public static function the_posts( $posts, $query ) {
  31. if ( ! empty( $posts ) && is_array( $posts ) ) {
  32. foreach ( (array) $posts as $post ) {
  33. self::$posts[] = (int) $post->ID;
  34. }
  35. self::$posts = array_unique( self::$posts );
  36. }
  37. return $posts;
  38. }
  39. /**
  40. * Get tags from current post views
  41. *
  42. * @return boolean
  43. */
  44. public static function get_tags_from_current_posts() {
  45. if ( is_array( self::$posts ) && count( self::$posts ) > 0 ) {
  46. // Generate SQL from post id
  47. $postlist = implode( "', '", self::$posts );
  48. // Generate key cache
  49. $key = md5( maybe_serialize( $postlist ) );
  50. $results = array();
  51. // Get cache if exist
  52. $cache = wp_cache_get( 'generate_keywords', 'simpletags' );
  53. if ( $cache === false ) {
  54. foreach ( self::$posts as $object_id ) {
  55. // Get terms
  56. $terms = get_object_term_cache( $object_id, 'post_tag' );
  57. if ( false === $terms ) {
  58. $terms = wp_get_object_terms( $object_id, 'post_tag' );
  59. }
  60. if ( $terms != false ) {
  61. $results = array_merge( $results, $terms );
  62. }
  63. }
  64. $cache[ $key ] = $results;
  65. wp_cache_set( 'generate_keywords', $cache, 'simpletags' );
  66. } else {
  67. if ( isset( $cache[ $key ] ) ) {
  68. return $cache[ $key ];
  69. }
  70. }
  71. return $results;
  72. }
  73. return array();
  74. }
  75. /**
  76. * Get links for each tag for auto link feature
  77. *
  78. */
  79. public static function prepare_auto_link_tags() {
  80. $auto_link_min = (int) SimpleTags_Plugin::get_option_value( 'auto_link_min' );
  81. if ( $auto_link_min == 0 ) {
  82. $auto_link_min = 1;
  83. }
  84. foreach ( (array) self::get_tags_from_current_posts() as $term ) {
  85. if ( $term->count >= $auto_link_min ) {
  86. self::$link_tags[ $term->name ] = esc_url( get_term_link( $term, $term->taxonomy ) );
  87. }
  88. }
  89. return true;
  90. }
  91. /**
  92. * Replace text by link to tag
  93. *
  94. * @param string $content
  95. *
  96. * @return string
  97. */
  98. public static function the_content( $content = '' ) {
  99. global $post;
  100. // Show only on singular view ? Check context
  101. if ( SimpleTags_Plugin::get_option_value( 'auto_link_views' ) == 'singular' && ! is_singular() ) {
  102. return $content;
  103. }
  104. // user preference for this post ?
  105. $meta_value = get_post_meta( $post->ID, '_exclude_autolinks', true );
  106. if ( ! empty( $meta_value ) ) {
  107. return $content;
  108. }
  109. // Get currents tags if no exists
  110. self::prepare_auto_link_tags();
  111. // Shuffle array
  112. SimpleTags_Client::random_array( self::$link_tags );
  113. // HTML Rel (tag/no-follow)
  114. $rel = SimpleTags_Client::get_rel_attribut();
  115. // only continue if the database actually returned any links
  116. if ( ! isset( self::$link_tags ) || ! is_array( self::$link_tags ) || empty( self::$link_tags ) ) {
  117. return $content;
  118. }
  119. // Case option ?
  120. $case = ( (int) SimpleTags_Plugin::get_option_value( 'auto_link_case' ) == 1 ) ? 'i' : '';
  121. $strpos_fnc = ( $case == 'i' ) ? 'stripos' : 'strpos';
  122. // Prepare exclude terms array
  123. $excludes_terms = explode( ',', SimpleTags_Plugin::get_option_value( 'auto_link_exclude' ) );
  124. if ( $excludes_terms == false ) {
  125. $excludes_terms = array();
  126. } else {
  127. $excludes_terms = array_filter( $excludes_terms, '_delete_empty_element' );
  128. $excludes_terms = array_unique( $excludes_terms );
  129. }
  130. $z = 0;
  131. foreach ( (array) self::$link_tags as $term_name => $term_link ) {
  132. // Exclude terms ? next...
  133. if ( in_array( $term_name, (array) $excludes_terms ) ) {
  134. continue;
  135. }
  136. // Make a first test with PHP function, economize CPU with regexp
  137. if ( $strpos_fnc( $content, $term_name ) === false ) {
  138. continue;
  139. }
  140. if ( (int) SimpleTags_Plugin::get_option_value( 'auto_link_dom' ) == 1 && class_exists( 'DOMDocument' ) && class_exists( 'DOMXPath' ) ) {
  141. self::_replace_by_links_dom( $content, $term_name, $term_link, $case, $rel );
  142. } else {
  143. self::_replace_by_links_regexp( $content, $term_name, $term_link, $case, $rel );
  144. }
  145. $z ++;
  146. if ( $z > (int) SimpleTags_Plugin::get_option_value( 'auto_link_max_by_post' ) ) {
  147. break;
  148. }
  149. }
  150. return $content;
  151. }
  152. /**
  153. * Replace text by link, except HTML tag, and already text into link, use DOMdocument.
  154. * Code take from : http://stackoverflow.com/questions/4044812/regex-domdocument-match-and-replace-text-not-in-a-link
  155. *
  156. * @param string $content
  157. * @param string $search
  158. * @param string $replace
  159. * @param string $case
  160. * @param string $rel
  161. */
  162. private static function _replace_by_links_dom( &$content, $search = '', $replace = '', $case = '', $rel = '' ) {
  163. $dom = new DOMDocument();
  164. // loadXml needs properly formatted documents, so it's better to use loadHtml, but it needs a hack to properly handle UTF-8 encoding
  165. $result = $dom->loadHtml( mb_convert_encoding( $content, 'HTML-ENTITIES', "UTF-8" ) );
  166. if ( $result === false ) {
  167. return false;
  168. }
  169. $xpath = new DOMXPath( $dom );
  170. foreach ( $xpath->query( '//text()[not(ancestor::a)]' ) as $node ) {
  171. $substitute = '<a href="' . $replace . '" class="st_tag internal_tag" ' . $rel . ' title="' . esc_attr( sprintf( SimpleTags_Plugin::get_option_value( 'auto_link_title' ), $search ) ) . "\">$search</a>";
  172. if ( $case == 'i' ) {
  173. $replaced = str_ireplace( $search, $substitute, $node->wholeText );
  174. } else {
  175. $replaced = str_replace( $search, $substitute, $node->wholeText );
  176. }
  177. $newNode = $dom->createDocumentFragment();
  178. $newNode->appendXML( $replaced );
  179. $node->parentNode->replaceChild( $newNode, $node );
  180. }
  181. // get only the body tag with its contents, then trim the body tag itself to get only the original content
  182. $content = mb_substr( $dom->saveXML( $xpath->query( '//body' )->item( 0 ) ), 6, - 7, "UTF-8" );
  183. }
  184. /**
  185. * Replace text by link, except HTML tag, and already text into link, use PregEXP.
  186. *
  187. * @param string $content
  188. * @param string $search
  189. * @param string $replace
  190. * @param string $case
  191. * @param string $rel
  192. */
  193. private static function _replace_by_links_regexp( &$content, $search = '', $replace = '', $case = '', $rel = '' ) {
  194. $must_tokenize = true; // will perform basic tokenization
  195. $tokens = null; // two kinds of tokens: markup and text
  196. $j = 0;
  197. $filtered = ''; // will filter text token by token
  198. $match = '/(\PL|\A)(' . preg_quote( $search, "/" ) . ')(\PL|\Z)/u' . $case;
  199. $substitute = '$1<a href="' . $replace . '" class="st_tag internal_tag" ' . $rel . ' title="' . esc_attr( sprintf( SimpleTags_Plugin::get_option_value( 'auto_link_title' ), $search ) ) . "\">$2</a>$3";
  200. //$match = "/\b" . preg_quote($search, "/") . "\b/".$case;
  201. //$substitute = '<a href="'.$replace.'" class="st_tag internal_tag" '.$rel.' title="'. esc_attr( sprintf( __('Posts tagged with %s', 'simpletags'), $search ) )."\">$0</a>";
  202. // for efficiency only tokenize if forced to do so
  203. if ( $must_tokenize ) {
  204. // this regexp is taken from PHP Markdown by Michel Fortin: http://www.michelf.com/projects/php-markdown/
  205. $comment = '(?s:<!(?:--.*?--\s*)+>)|';
  206. $processing_instruction = '(?s:<\?.*?\?>)|';
  207. $tag = '(?:<[/!$]?[-a-zA-Z0-9:]+\b(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*>)';
  208. $markup = $comment . $processing_instruction . $tag;
  209. $flags = PREG_SPLIT_DELIM_CAPTURE;
  210. $tokens = preg_split( "{($markup)}", $content, - 1, $flags );
  211. $must_tokenize = false;
  212. }
  213. // there should always be at least one token, but check just in case
  214. $anchor_level = 0;
  215. if ( isset( $tokens ) && is_array( $tokens ) && count( $tokens ) > 0 ) {
  216. $i = 0;
  217. foreach ( $tokens as $token ) {
  218. if ( ++ $i % 2 && $token != '' ) { // this token is (non-markup) text
  219. if ( $anchor_level == 0 ) { // linkify if not inside anchor tags
  220. if ( preg_match( $match, $token ) ) { // use preg_match for compatibility with PHP 4
  221. $j ++;
  222. if ( $j <= SimpleTags_Plugin::get_option_value( 'auto_link_max_by_tag' ) || SimpleTags_Plugin::get_option_value( 'auto_link_max_by_tag' ) == 0 ) {// Limit replacement at 1 by default, or options value !
  223. $token = preg_replace( $match, $substitute, $token ); // only PHP 5 supports calling preg_replace with 5 arguments
  224. }
  225. $must_tokenize = true; // re-tokenize next time around
  226. }
  227. }
  228. } else { // this token is markup
  229. if ( preg_match( "#<\s*a\s+[^>]*>#i", $token ) ) { // found <a ...>
  230. $anchor_level ++;
  231. } elseif ( preg_match( "#<\s*/\s*a\s*>#i", $token ) ) { // found </a>
  232. $anchor_level --;
  233. }
  234. }
  235. $filtered .= $token; // this token has now been filtered
  236. }
  237. $content = $filtered; // filtering completed for this link
  238. }
  239. }
  240. }