From c59055a7e2a0db350067165c77032d2b152dd65f Mon Sep 17 00:00:00 2001 From: Silvan Hagen Date: Thu, 5 Sep 2019 17:20:37 +0200 Subject: [PATCH] Setting terms from comments, display archive links, see #8 and #9 --- classes/Comment_Tags.php | 88 +++++++++++++++++++++++++ classes/Hashtag_Parser.php | 129 ++++++++++++++++++++++++++++++++++++- classes/Post_Tags.php | 73 +++++++++++++++++++++ package-lock.json | 2 +- package.json | 2 +- readme.txt | 10 ++- spaces-global-tags.php | 10 ++- 7 files changed, 307 insertions(+), 7 deletions(-) diff --git a/classes/Comment_Tags.php b/classes/Comment_Tags.php index 9d7a56d..2e6d4ad 100644 --- a/classes/Comment_Tags.php +++ b/classes/Comment_Tags.php @@ -8,5 +8,93 @@ * @since 0.7.0 */ class Comment_Tags extends Hashtag_Parser { + /** + * @var string $taxonomy the multisite taxonomy used. + * + * @since 0.8.0 + */ + static $taxonomy; + + /** + * Comment_Tags constructor. + * + * @param string $taxonomy the multisite taxonomy used. + * + * @since 0.8.0 + */ + public function __construct( $taxonomy = 'global_comment_tag' ) { + + self::$taxonomy = $taxonomy; + + self::register(); + } + + /** + * Register with WordPress hooks. + * + * @since 0.8.0 + */ + public function register() { + + add_action( 'wp_insert_comment', [ $this, 'update_comment' ] ); + add_action( 'edit_comment', [ $this, 'update_comment' ] ); + + /** + * When displaying a tag, update the markup with a link to the tag. + */ + add_filter( 'comment_text', [ '\Spaces_Global_Tags\Comment_Tags', 'tag_comment_links'], 15 ); + + } + + /** + * Markup tags in comments with links to the archive page. + * + * @param string $content Comment content. + * + * @since 0.8.0 + * + * @return string|void + */ + static function tag_comment_links( $content ) { + $taxonomy = self::$taxonomy; + return parent::tag_links( $content, $taxonomy ); + } + + /** + * Update new or existing comment. + * + * @param int $comment_id ID of a comment. + * + * @since 0.8.0 + */ + public function update_comment( $comment_id ) { + /** + * Get the comment object. + */ + $comment = get_comment( $comment_id ); + + /** + * Find raw tags in the comment_content. + */ + $tags = self::find_tags( $comment->comment_content ); + + $this->update_post( $comment->comment_post_ID, $tags ); + + } + + /** + * Update post with tags from comment. + * + * @param int $post_id ID of a post. + * @param array $terms Array of terms for $this->taxonomy. + */ + public function update_post( $post_id, $terms ) { + + /** + * Append the comment tags on the associated post. + */ + set_post_multisite_terms( $post_id, $terms, self::$taxonomy, get_current_blog_id(), true ); + + } } diff --git a/classes/Hashtag_Parser.php b/classes/Hashtag_Parser.php index 40590ec..863a5cc 100644 --- a/classes/Hashtag_Parser.php +++ b/classes/Hashtag_Parser.php @@ -16,6 +16,13 @@ abstract class Hashtag_Parser { */ const TAGS_REGEX = '/(?:^|\s|>|\()#(?!\d{1,2}(?:$|\s|<|\)|\p{P}{1}\s))([\p{L}\p{N}\_\-\.]*[\p{L}\p{N}]+)(?:$|\b|\s|<|\))/iu'; + /** + * Find tags in a string. + * + * @param $content + * + * @return mixed|void + */ static function find_tags( $content ) { /** * Placeholder for all tags found. @@ -30,13 +37,13 @@ static function find_tags( $content ) { $content = wp_pre_kses_less_than( $content ); $content = wp_kses_normalize_entities( $content ); - $dom = new DOMDocument; + $dom = new \DOMDocument; libxml_use_internal_errors( true ); $dom->loadHTML( '' . $content ); libxml_use_internal_errors( false ); - $xpath = new DOMXPath( $dom ); + $xpath = new \DOMXPath( $dom ); $textNodes = $xpath->query( '//text()' ); foreach ( $textNodes as $textNode ) { @@ -59,4 +66,122 @@ static function find_tags( $content ) { return apply_filters( 'spaces_global_tags_found_tags', $tags, $content ); } + + /** + * Parses and links tags within a string. + * Run on the_content and comment_text. + * + * @param string $content The content. + * @param string $taxonomy Taxonomy name. + * + * @return string The linked content. + */ + static function tag_links( $content, $taxonomy ) { + if ( empty( $content ) ) { + return $content; + } + + $tags = self::find_tags( $content ); + + $tags = array_unique( $tags ); + + usort( $tags, [ '\Spaces_Global_Tags\Hashtag_Parser', '_sortByLength' ] ); + + static $tag_links = []; + + static $tag_info = []; + + foreach ( $tags as $tag ) { + if ( isset( $tag_info[ $tag ] ) ) { + continue; + } + $info = get_multisite_term_by( 'slug', $tag, $taxonomy ); + if ( ! $info ) { + $info = get_multisite_term_by( 'name', $tag, $taxonomy ); + } + $tag_info[ $tag ] = $info; + } + $content = wp_pre_kses_less_than( $content ); + $content = wp_kses_normalize_entities( $content ); + + $dom = new \DOMDocument; + + libxml_use_internal_errors( true ); + @$dom->loadHTML( '' . $content ); + libxml_use_internal_errors( false ); + + $xpath = new \DOMXPath( $dom ); + + $textNodes = $xpath->query( '//text()' ); + + foreach( $textNodes as $textNode ) { + if ( ! $textNode->parentNode ) { + continue; + } + $parent = $textNode; + while( $parent ) { + if ( ! empty( $parent->tagName ) && in_array( strtolower( $parent->tagName ), array( 'pre', 'code', 'a', 'script', 'style', 'head' ) ) ) { + continue 2; + } + $parent = $parent->parentNode; + } + $text = $textNode->nodeValue; + $totalCount = 0; + foreach ( $tags as $tag ) { + if ( empty( $tag_info[ $tag ] ) ) { + continue; + } + if ( empty( $tag_links[ $tag ] ) ) { + $tag_url = get_multisite_term_link( $tag_info[ $tag ], $taxonomy ); + $replacement = "#" . htmlentities( $tag ) . ""; + $replacement = apply_filters( 'spaces_global_tags_tag_link', $replacement, $tag ); + $tag_links[ $tag ] = $replacement; + } else { + $replacement = $tag_links[ $tag ]; + } + $count = 0; + $text = preg_replace( "/(^|\s|>|\()#$tag(($|\b|\s|<|\)))/", '$1' . $replacement . '$2', $text, -1, $count ); + $totalCount += $count; + } + if ( ! $totalCount ) { + continue; + } + $text = wp_pre_kses_less_than( $text ); + $text = wp_kses_normalize_entities( $text ); + + $newNodes = new \DOMDocument; + + libxml_use_internal_errors( true ); + @$newNodes->loadHTML( '
' . $text . '
' ); + libxml_use_internal_errors( false ); + + foreach( $newNodes->getElementsByTagName( 'body' )->item( 0 )->childNodes->item( 0 )->childNodes as $newNode ) { + $cloneNode = $dom->importNode( $newNode, true ); + if ( ! $cloneNode ) { + continue 2; + } + $textNode->parentNode->insertBefore( $cloneNode, $textNode ); + } + $textNode->parentNode->removeChild( $textNode ); + } + $html = ''; + // Sometime, DOMDocument will put things in the head instead of the body. + // We still need to keep them in our output. + $search_tags = array( 'head', 'body' ); + foreach ( $search_tags as $tag ) { + $list = $dom->getElementsByTagName( $tag ); + if ( 0 === $list->length ) { + continue; + } + foreach ( $list->item( 0 )->childNodes as $node ) { + $html .= $dom->saveHTML( $node ); + } + } + return $html; + } + + static function _sortByLength( $a, $b ) { + return strlen( $b ) - strlen( $a ); + } + } diff --git a/classes/Post_Tags.php b/classes/Post_Tags.php index 06b2cb1..fd11d39 100644 --- a/classes/Post_Tags.php +++ b/classes/Post_Tags.php @@ -9,4 +9,77 @@ */ class Post_Tags extends Hashtag_Parser { + /** + * @var string $taxonomy the multisite taxonomy used. + * + * @since 0.8.0 + */ + static $taxonomy; + + /** + * Comment_Tags constructor. + * + * @param string $taxonomy the multisite taxonomy used. + * + * @since 0.8.0 + */ + public function __construct( $taxonomy = 'global_post_tag' ) { + + self::$taxonomy = $taxonomy; + + self::register(); + } + + /** + * Register with WordPress hooks. + * + * @since 0.8.0 + */ + public function register() { + + add_action( 'transition_post_status', [ $this, 'process_tags' ], 12, 3 ); + + /** + * When displaying a tag, update the markup with a link to the tag. + */ + add_filter( 'the_content', [ '\Spaces_Global_Tags\Post_Tags', 'tag_post_links'], 15 ); + + } + + /** + * Markup tags in posts with links to the archive page. + * + * @param string $content The Content of the post. + * + * @since 0.8.0 + * + * @return string|void + */ + static function tag_post_links( $content ) { + $taxonomy = self::$taxonomy; + return parent::tag_links( $content, $taxonomy ); + } + + /** + * Fires when the post is published or edited and + * sets the tags accordingly. + * + * @param boolean $new Status being switched to + * @param boolean $old Status being switched from + * @param object $post The full Post object + * + * @since 0.8.0 + * + * @return void + */ + public function process_tags( $new, $old, $post ) { + + if ( 'publish' !== $new ) { + return; + } + $tags = self::find_tags( $post->post_content ); + // TODO: Needs fixing, creates the tags, but not yet adds them to the post. + set_post_multisite_terms( $post->ID, $tags, self::$taxonomy, get_current_blog_id(), true ); + } + } diff --git a/package-lock.json b/package-lock.json index 0c7ee82..095f005 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "spaces-global-tags", - "version": "0.7.0", + "version": "0.9.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 951befe..eacc667 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spaces-global-tags", - "version": "0.7.0", + "version": "0.9.0", "main": "Gruntfile.js", "author": "Silvan Hagen", "devDependencies": { diff --git a/readme.txt b/readme.txt index 4966efd..55ebea3 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: neverything Tags: multisite, wpmu, taxonomies Requires at least: 4.5 Tested up to: 5.2.2 -Stable tag: 0.7.0 +Stable tag: 0.8.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -27,7 +27,7 @@ Clone it, love it, hate it. = Does this replace the default tags in a WordPress multisite = -No. +No. But it does disable them on posts. = Is there a global search available = @@ -37,6 +37,12 @@ No. == Changelog == += 0.8.0 = +Add composer.json support. +Implement PSR4 autoloader for classes. +Add method to filter displayed comment/content with links to the tag archive pages. +Implement logic for parsing and settings terms for posts in comments. + = 0.7.0 = Implement abstract class Hashtag_Parser. Setting up Post_Tags and Comment_Tags classes. diff --git a/spaces-global-tags.php b/spaces-global-tags.php index db5bf6f..b149447 100644 --- a/spaces-global-tags.php +++ b/spaces-global-tags.php @@ -7,7 +7,7 @@ * Author URI: https://silvanhagen.com * Text Domain: spaces-global-tags * Domain Path: /languages - * Version: 0.7.0 + * Version: 0.8.0 * Network: true * * @package Spaces_Global_Tags @@ -18,6 +18,8 @@ */ namespace Spaces_Global_Tags; +require __DIR__ . '/vendor/autoload.php'; + /** * Dependency check. */ @@ -169,6 +171,8 @@ function register_global_post_tag_taxonomy() { $post_types = apply_filters( 'multisite_taxonomy_tags_post_types', [ 'post' ] ); register_multisite_taxonomy( 'global_post_tag', $post_types, $args ); + + new Post_Tags(); } add_action( 'init', __NAMESPACE__ . '\register_global_post_tag_taxonomy', 0 ); @@ -211,7 +215,11 @@ function register_global_comment_tag_taxonomy() { $post_types = apply_filters( 'multisite_taxonomy_tags_post_types', [ 'post' ] ); register_multisite_taxonomy( 'global_comment_tag', $post_types, $args ); + + new Comment_Tags(); } add_action( 'init', __NAMESPACE__ . '\register_global_comment_tag_taxonomy', 0 ); + +