<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[BuddyPress][13370] trunk: Silently introduce block support for Activity content</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://buddypress.trac.wordpress.org/changeset/13370">13370</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://buddypress.trac.wordpress.org/changeset/13370","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>imath</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2022-11-19 10:26:02 +0000 (Sat, 19 Nov 2022)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Silently introduce block support for Activity content

Functions and filters bringing support for Block based activity content are disabled by default. We're adding these so that it's easier to progress on building a Block Based activity post form from a feature as a plugin and the BP Attachments BuddyPress Add-on. To allow Activity content to support blocks, we simply need to use this filter `add_filter( 'bp_is_activity_blocks_active', '__return_true' ); `.

This commit also allowes community users to share more than 2 links when these links are coming from the website BuddyPress is activated on.

Closes https://github.com/buddypress/buddypress/pull/38
Fixes <a href="http://buddypress.trac.wordpress.org/ticket/8765">#8765</a></pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcbpactivityclassesclassbpactivitycomponentphp">trunk/src/bp-activity/classes/class-bp-activity-component.php</a></li>
<li><a href="#trunksrcbpcorebpcoremoderationphp">trunk/src/bp-core/bp-core-moderation.php</a></li>
<li><a href="#trunktestsphpunittestcasescorefunctionsphp">trunk/tests/phpunit/testcases/core/functions.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunksrcbpactivitybpactivityblockfunctionsphp">trunk/src/bp-activity/bp-activity-block-functions.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcbpactivitybpactivityblockfunctionsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/src/bp-activity/bp-activity-block-functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/bp-activity/bp-activity-block-functions.php                             (rev 0)
+++ trunk/src/bp-activity/bp-activity-block-functions.php       2022-11-19 10:26:02 UTC (rev 13370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,126 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * BuddyPress Activity Block functions.
+ *
+ * @package buddypress\bp-activity-block-functions
+ * @since 11.0.0
+ */
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+       exit;
+}
+
+/**
+ * Determine whether an activity or its content string has blocks.
+ *
+ * @since 11.0.0
+ * @see parse_blocks()
+ *
+ * @param string|int|BP_Activity_Activity|null $activity Activity content, Activity ID, or Activity object.
+ * @return bool Whether the activity content has blocks.
+ */
+function bp_activity_has_blocks( $activity = null ) {
+       if ( is_null( $activity ) ) {
+               return false;
+       }
+
+       if ( ! is_string( $activity ) ) {
+               if ( is_int( $activity ) ) {
+                       $bp_activity = new BP_Activity_Activity( $activity );
+               } else {
+                       $bp_activity = $activity;
+               }
+
+               if ( $bp_activity instanceof BP_Activity_Activity ) {
+                       $activity = $bp_activity->content;
+               }
+       }
+
+       return has_blocks( $activity );
+}
+
+/**
+ * If `bp_activity_do_blocks()` needs to remove `wpautop()` from the `bp_get_activity_content_body` filter, this re-adds it afterwards,
+ * for subsequent `bp_get_activity_content_body` usage.
+ *
+ * @since 11.0.0
+ *
+ * @param string $content The activity content running through this filter.
+ * @return string The unmodified activity content.
+ */
+function bp_activity_restore_wpautop_hook( $content ) {
+       $current_priority = has_filter( 'bp_get_activity_content_body', 'bp_activity_restore_wpautop_hook' );
+
+       add_filter( 'bp_get_activity_content_body', 'wpautop', $current_priority - 1 );
+       remove_filter( 'bp_get_activity_content_body', 'bp_activity_restore_wpautop_hook', $current_priority );
+
+       return $content;
+}
+
+/**
+ * Parses dynamic blocks out of the activity content and re-renders them.
+ *
+ * @since 11.0.0
+ *
+ * @param string $content Activity content.
+ * @return string The block based activity content.
+ */
+function bp_activity_do_blocks( $content ) {
+       $blocks = parse_blocks( $content );
+       $output = '';
+
+       foreach ( $blocks as $block ) {
+               $output .= render_block( $block );
+       }
+
+       // If there are blocks in this content, we shouldn't run wpautop() on it later.
+       $priority = has_filter( 'bp_get_activity_content_body', 'wpautop' );
+       if ( false !== $priority && doing_filter( 'bp_get_activity_content_body' ) && bp_activity_has_blocks( $content ) ) {
+               remove_filter( 'bp_get_activity_content_body', 'wpautop', $priority );
+               add_filter( 'bp_get_activity_content_body', 'bp_activity_restore_wpautop_hook', $priority + 1 );
+       }
+
+       return $output;
+}
+add_filter( 'bp_get_activity_content_body', 'bp_activity_do_blocks', 9 );
+
+/**
+ * Make sure only Emoji chars are saved into the DB.
+ *
+ * @since 11.0.0
+ *
+ * @param string $activity_content The activity content.
+ * @return string The sanitized activity content.
+ */
+function bp_activity_blocks_preserve_emoji_chars( $activity_content ) {
+       preg_match_all( '/\<img[^>]*alt=\"([^"]*)\".?\>/', $activity_content, $matches );
+
+       if ( isset( $matches[0][0] ) && isset( $matches[1][0] ) ) {
+               foreach ( $matches[0] as $key => $match ) {
+                       if ( false !== strpos( $matches[0][ $key ], 's.w.org/images/core/emoji' ) && isset( $matches[1][ $key ] ) ) {
+                               $activity_content = str_replace( $matches[0][ $key ], $matches[1][ $key ], $activity_content );
+                       }
+               }
+       }
+
+       return $activity_content;
+}
+add_filter( 'bp_activity_content_before_save', 'bp_activity_blocks_preserve_emoji_chars', 2 );
+
+/**
+ * Allow usage of the paragraph tag and the link’s target attribute into Activity content.
+ *
+ * @since 11.0.0
+ *
+ * @param array $tags The activity allowed tags.
+ * @return array The block based activity allowed tags.
+ */
+function bp_activity_blocks_allowed_tags( $tags = array() ) {
+       if ( isset( $tags['a'] ) && ! isset( $tags['a']['target'] ) ) {
+               $tags['a']['target'] = true;
+       }
+
+       return array_merge( $tags, array( 'p' => true ) );
+}
+add_filter( 'bp_activity_allowed_tags', 'bp_activity_blocks_allowed_tags' );
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/src/bp-activity/bp-activity-block-functions.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunksrcbpactivityclassesclassbpactivitycomponentphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/bp-activity/classes/class-bp-activity-component.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/bp-activity/classes/class-bp-activity-component.php     2022-11-18 21:26:56 UTC (rev 13369)
+++ trunk/src/bp-activity/classes/class-bp-activity-component.php       2022-11-19 10:26:02 UTC (rev 13370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -79,6 +79,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        $includes[] = 'embeds';
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                /*
+                * Activity blocks feature.
+                *
+                * By default this feature is inactive. We're including specific block functions
+                * in version 11.0.0 so these can be used by BuddyPress Add-ons such as BP Attachments
+                * or BP Activity Block Editor.
+                */
+               if ( bp_is_active( $this->id, 'blocks' ) ) {
+                       $includes[] = 'block-functions';
+               }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( is_admin() ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $includes[] = 'admin';
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span></span></pre></div>
<a id="trunksrcbpcorebpcoremoderationphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/bp-core/bp-core-moderation.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/bp-core/bp-core-moderation.php  2022-11-18 21:26:56 UTC (rev 13369)
+++ trunk/src/bp-core/bp-core-moderation.php    2022-11-19 10:26:02 UTC (rev 13370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -77,8 +77,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // Define local variable(s).
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $_post     = array();
-       $match_out = '';
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $_post   = array();
+       $matches = '';
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /** User Data ************************************************************
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -90,9 +90,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // If data exists, map it.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! empty( $user ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $_post['author'] = $user->display_name;
-                       $_post['email']  = $user->user_email;
-                       $_post['url']    = $user->user_url;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $_post['user_id'] = $user->ID;
+                       $_post['author']  = $user->display_name;
+                       $_post['email']   = $user->user_email;
+                       $_post['url']     = $user->user_url;
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -111,20 +112,32 @@
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! empty( $max_links ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // How many links?
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $num_links = preg_match_all( '/(http|ftp|https):\/\//i', $content, $match_out );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $num_links = preg_match_all( '/(http|ftp|https):\/\/(.+?)([\s\'"])/i', $content, $matches );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Allow for bumping the max to include the user's URL.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // Neutralize the current site's URL.
+               if ( isset( $matches[0] ) && is_array( $matches[0] ) ) {
+                       foreach ( $matches[0] as $found_url ) {
+                               if ( 0 === strpos( $found_url, home_url() ) ) {
+                                       $num_links -=1;
+                               }
+                       }
+               }
+
+               // Allow for bumping the max according to the user's URL or content data.
</ins><span class="cx" style="display: block; padding: 0 10px">                 if ( ! empty( $_post['url'] ) ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        $user_url = $_post['url'];
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        /**
</span><span class="cx" style="display: block; padding: 0 10px">                         * Filters the maximum amount of links allowed to include the user's URL.
</span><span class="cx" style="display: block; padding: 0 10px">                         *
</span><span class="cx" style="display: block; padding: 0 10px">                         * @since 1.6.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         * @since 11.0.0 Introduced the $content parameter as WordPress did the same in 4.7.0.
</ins><span class="cx" style="display: block; padding: 0 10px">                          *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                         * @param string $num_links How many links found.
-                        * @param string $value     User's url.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                  * @param string $num_links    How many links found.
+                        * @param string $user_url     User's url.
+                        * @param array  $content      The content being moderated.
</ins><span class="cx" style="display: block; padding: 0 10px">                          */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $num_links = apply_filters( 'comment_max_links_url', $num_links, $_post['url'] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $num_links = apply_filters( 'comment_max_links_url', $num_links, $user_url, $content );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Das ist zu viele links!
</span></span></pre></div>
<a id="trunktestsphpunittestcasescorefunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/testcases/core/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/testcases/core/functions.php  2022-11-18 21:26:56 UTC (rev 13369)
+++ trunk/tests/phpunit/testcases/core/functions.php    2022-11-19 10:26:02 UTC (rev 13370)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -917,4 +917,68 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertTrue( 2 === count( $versions ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Override the comment max links option.
+        */
+       public function override_comment_max_links() {
+               return 2;
+       }
+
+       /**
+        * @ticket BP8765
+        */
+       public function test_bp_core_check_for_moderation() {
+               $u = self::factory()->user->create();
+               //<a href="%1$s">Hello</a>
+               $content = sprintf(
+                       '<!-- wp:paragraph -->
+                       <p><strong>%1$s %2$s!</p>
+                       <!-- /wp:paragraph -->',
+                       'Hello',
+                       'World'
+               );
+
+               add_filter( 'pre_option_comment_max_links', array( $this, 'override_comment_max_links' ), 10, 0 );
+
+               $test = bp_core_check_for_moderation( $u, '', $content );
+               $this->assertTrue( $test );
+
+               $content = sprintf(
+                       '<!-- wp:paragraph -->
+                       <p><strong>%1$s %2$s!</p>
+                       <!-- /wp:paragraph -->',
+                       'Hello',
+                       '<a href="https://foo.bar">World</a>'
+               );
+
+               $test = bp_core_check_for_moderation( $u, '', $content );
+               $this->assertTrue( $test );
+
+               $content = sprintf(
+                       '<!-- wp:paragraph -->
+                       <p><strong>%1$s %2$s!</p>
+                       <!-- /wp:paragraph -->',
+                       '<a href="https://bar.foo">Hello</a>',
+                       '<a href="https://foo.bar">World</a>'
+               );
+
+               $test = bp_core_check_for_moderation( $u, '', $content );
+               $this->assertFalse( $test );
+
+               $content = sprintf(
+                       '<!-- wp:paragraph -->
+                       <p><strong>%1$s %2$s !</p>
+                       <!-- /wp:paragraph -->
+                       <!-- wp:bp/image-attachment {"align":"center","url":"%3$s/bp-attachments/public/members/admin/view/635168b2e5aae34973bcb0c1f0bd86fd/","src":"%3$s/wp-content/uploads/buddypress/public/members/1/bp-6-0-0-slated.jpg"} /-->',
+                       'Hello',
+                       'World',
+                       home_url()
+               );
+
+               $test = bp_core_check_for_moderation( $u, '', $content );
+               $this->assertTrue( $test );
+
+               remove_filter( 'pre_option_comment_max_links', array( $this, 'override_comment_max_links' ), 10, 0 );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>