Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 79 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
| Tracks | |
0.00% |
0 / 79 |
|
0.00% |
0 / 7 |
1332 | |
0.00% |
0 / 1 |
| init | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| __construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| is_enabled | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 | |||
| platform | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| record | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
156 | |||
| record_refusal | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
| comment_posted | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
132 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Usage events. |
| 4 | * |
| 5 | * @package automattic/jetpack-comments |
| 6 | */ |
| 7 | |
| 8 | namespace Automattic\Jetpack\Comments; |
| 9 | |
| 10 | use Automattic\Jetpack\Connection\Manager; |
| 11 | use Automattic\Jetpack\Status; |
| 12 | use Automattic\Jetpack\Status\Host; |
| 13 | use Automattic\Jetpack\Terms_Of_Service; |
| 14 | use Automattic\Jetpack\Tracking; |
| 15 | |
| 16 | /** |
| 17 | * Records how the form is used, in Tracks. The browser records the rest through the same gate. |
| 18 | */ |
| 19 | class Tracks { |
| 20 | |
| 21 | /** |
| 22 | * Singleton instance. |
| 23 | * |
| 24 | * @var Tracks|null |
| 25 | */ |
| 26 | private static $instance = null; |
| 27 | |
| 28 | /** |
| 29 | * Register the hooks. Safe to call more than once. |
| 30 | * |
| 31 | * @return Tracks |
| 32 | */ |
| 33 | public static function init() { |
| 34 | if ( null === self::$instance ) { |
| 35 | self::$instance = new self(); |
| 36 | } |
| 37 | |
| 38 | return self::$instance; |
| 39 | } |
| 40 | |
| 41 | /** |
| 42 | * Count the comments the form posts. |
| 43 | */ |
| 44 | private function __construct() { |
| 45 | add_action( 'comment_post', array( $this, 'comment_posted' ), 10, 3 ); |
| 46 | } |
| 47 | |
| 48 | /** |
| 49 | * Whether events may be recorded: always on WordPress.com, on Atomic outside offline mode, |
| 50 | * and elsewhere once the owner agreed to Jetpack's terms, which offline mode also fails. |
| 51 | * |
| 52 | * @return bool |
| 53 | */ |
| 54 | public static function is_enabled() { |
| 55 | $platform = self::platform(); |
| 56 | |
| 57 | return 'simple' === $platform |
| 58 | || ( 'atomic' === $platform && ! ( new Status() )->is_offline_mode() ) |
| 59 | || ( new Terms_Of_Service() )->has_agreed(); |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * The host the event came from. |
| 64 | * |
| 65 | * @return string `simple`, `atomic` or `self_hosted`. |
| 66 | */ |
| 67 | public static function platform() { |
| 68 | $host = new Host(); |
| 69 | |
| 70 | if ( $host->is_wpcom_simple() ) { |
| 71 | return 'simple'; |
| 72 | } |
| 73 | |
| 74 | return $host->is_woa_site() ? 'atomic' : 'self_hosted'; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Record an event. A visitor's is recorded under the site's owner, as Jetpack Forms does, and holds nothing they typed. |
| 79 | * |
| 80 | * @param string $event_name Full event name, e.g. `jetpack_comments_comment_posted`. |
| 81 | * @param array $properties Event properties. |
| 82 | * @return void |
| 83 | */ |
| 84 | public static function record( $event_name, array $properties = array() ) { |
| 85 | if ( ! self::is_enabled() ) { |
| 86 | return; |
| 87 | } |
| 88 | |
| 89 | $properties['blog_id'] = Checkpoint::blog_id(); |
| 90 | $properties['platform'] = self::platform(); |
| 91 | $properties['version'] = Comments::PACKAGE_VERSION; |
| 92 | |
| 93 | $user = wp_get_current_user(); |
| 94 | |
| 95 | if ( ( new Host() )->is_wpcom_simple() ) { |
| 96 | if ( ! $user->exists() ) { |
| 97 | $user = get_userdata( (int) wpcom_get_blog_owner( get_current_blog_id() ) ); |
| 98 | } |
| 99 | |
| 100 | if ( ! function_exists( 'tracks_record_event' ) && function_exists( 'require_lib' ) ) { |
| 101 | require_lib( 'tracks/client' ); |
| 102 | } |
| 103 | |
| 104 | if ( $user && function_exists( 'tracks_record_event' ) ) { |
| 105 | tracks_record_event( $user, $event_name, $properties ); |
| 106 | } |
| 107 | |
| 108 | return; |
| 109 | } |
| 110 | |
| 111 | if ( ! $user->exists() ) { |
| 112 | $user = get_userdata( (int) ( new Manager() )->get_connection_owner_id() ); |
| 113 | } |
| 114 | |
| 115 | if ( ! $user ) { |
| 116 | return; |
| 117 | } |
| 118 | |
| 119 | // Atomic does not always store the agreement Tracking checks for; masterbar works around it the same way. |
| 120 | $agreed = static function ( $value, $name ) { |
| 121 | return Terms_Of_Service::OPTION_NAME === $name ? true : $value; |
| 122 | }; |
| 123 | |
| 124 | if ( 'atomic' === $properties['platform'] ) { |
| 125 | add_filter( 'jetpack_options', $agreed, 10, 2 ); |
| 126 | } |
| 127 | |
| 128 | ( new Tracking() )->tracks_record_event( $user, $event_name, $properties ); |
| 129 | |
| 130 | remove_filter( 'jetpack_options', $agreed, 10 ); |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * Record a comment the form turned away, where a browser on this site sent it. Anything else is a bot, |
| 135 | * and would cost an outbound request to record. |
| 136 | * |
| 137 | * @param string $reason The error code the sign-in was refused with. |
| 138 | * @return void |
| 139 | */ |
| 140 | public static function record_refusal( $reason ) { |
| 141 | if ( ! empty( $_SERVER['HTTP_ORIGIN'] ) && Checkpoint::is_same_site_request() ) { |
| 142 | self::record( 'jetpack_comments_comment_refused', array( 'reason' => $reason ) ); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | /** |
| 147 | * Who posted a comment through the form, and with what. |
| 148 | * |
| 149 | * @param int $comment_id The comment ID. |
| 150 | * @param int|string $approved 1, 0, `spam` or `trash`. |
| 151 | * @param array $commentdata Comment data. |
| 152 | * @return void |
| 153 | */ |
| 154 | public function comment_posted( $comment_id, $approved, $commentdata = array() ) { |
| 155 | // phpcs:disable WordPress.Security.NonceVerification.Missing -- Comment_Form::verify_nonce() checked it; its presence marks a comment this form sent. |
| 156 | if ( empty( $_POST[ Comment_Form::NONCE_NAME ] ) ) { |
| 157 | return; |
| 158 | } |
| 159 | |
| 160 | // A theme drew its own form, so the app never showed on this site. |
| 161 | if ( ! empty( $_POST[ Comment_Form::THEME_FORM ] ) ) { |
| 162 | self::record( |
| 163 | 'jetpack_comments_theme_form_posted', |
| 164 | array( |
| 165 | 'theme' => get_stylesheet(), |
| 166 | 'status' => (string) $approved, |
| 167 | 'post_type' => (string) get_post_type( (int) ( $commentdata['comment_post_ID'] ?? 0 ) ), |
| 168 | ) |
| 169 | ); |
| 170 | return; |
| 171 | } |
| 172 | |
| 173 | $code = ! empty( $_POST[ Checkpoint::CODE_FIELD ] ); |
| 174 | $passport = ! empty( $_POST[ Checkpoint::PASSPORT_FIELD ] ); |
| 175 | |
| 176 | if ( is_user_logged_in() ) { |
| 177 | $identity = 'user'; |
| 178 | } elseif ( $code || $passport ) { |
| 179 | // Checkpoint::admit() turned the comment away unless one of these let the commenter in. |
| 180 | $identity = 'wordpress'; // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText -- Commenter.kind in the app. |
| 181 | } elseif ( empty( $commentdata['comment_author'] ) && empty( $commentdata['comment_author_email'] ) ) { |
| 182 | $identity = 'anonymous'; |
| 183 | } else { |
| 184 | $identity = 'guest'; |
| 185 | } |
| 186 | |
| 187 | // Paragraphs and list items come with every comment and every list. |
| 188 | $content = (string) ( $commentdata['comment_content'] ?? '' ); |
| 189 | preg_match_all( '#<!-- wp:([a-z0-9-]+(?:/[a-z0-9-]+)?)#', $content, $names ); |
| 190 | $blocks = array_diff( array_unique( $names[1] ), array( 'paragraph', 'list-item' ) ); |
| 191 | sort( $blocks ); |
| 192 | |
| 193 | self::record( |
| 194 | 'jetpack_comments_comment_posted', |
| 195 | array( |
| 196 | 'identity' => $identity, |
| 197 | 'new_sign_in' => $code && ! is_user_logged_in(), |
| 198 | 'is_reply' => ! empty( $commentdata['comment_parent'] ), |
| 199 | 'editor' => has_blocks( $content ) ? 'blocks' : 'textarea', |
| 200 | 'blocks' => implode( ',', $blocks ), |
| 201 | 'subscribe_comments' => ! empty( $_POST['subscribe_comments'] ) || ! empty( $_POST['subscribe'] ), |
| 202 | 'subscribe_blog' => ! empty( $_POST['subscribe_blog'] ), |
| 203 | 'remember_details' => ! empty( $_POST['wp-comment-cookies-consent'] ), |
| 204 | 'status' => (string) $approved, |
| 205 | 'post_type' => (string) get_post_type( (int) ( $commentdata['comment_post_ID'] ?? 0 ) ), |
| 206 | ) |
| 207 | ); |
| 208 | // phpcs:enable WordPress.Security.NonceVerification.Missing |
| 209 | } |
| 210 | } |