generated from 10up/plugin-scaffold
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathcontent-sync.php
258 lines (223 loc) · 7.62 KB
/
content-sync.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<?php
/**
* Content sync
*
* @package SophiWP
*/
namespace SophiWP\ContentSync;
use WP_Error;
use function SophiWP\Settings\get_sophi_settings;
use function SophiWP\Utils\get_supported_post_types;
use SophiWP\Utils;
use Snowplow\Tracker\Tracker;
use Snowplow\Tracker\Subject;
use Snowplow\Tracker\Emitters\SyncEmitter;
/**
* Default setup routine
*
* @return void
*/
function setup() {
$n = function( $function ) {
return __NAMESPACE__ . "\\$function";
};
add_action( 'wp_after_insert_post', $n( 'track_event' ), 10, 4 );
}
/**
* Sending data to SnowPlow.
*
* @param int $post_id Post ID.
* @param WP_Post $post Post object.
* @param bool $update Whether this is an existing post being updated.
* @param null|WP_Post $post_before Null for new posts, the WP_Post object prior
* to the update for updated posts.
*
* @return null|WP_Error
*/
function track_event( $post_id, $post, $update, $post_before ) {
$new_status = $post->post_status;
$old_status = $post_before ? $post_before->post_status : '';
// Don't send any event if the page is assigned to the front page or posts page
if ( $post_id === (int) get_option( 'page_on_front' ) || $post_id === (int) get_option( 'page_for_posts' ) ) {
return;
}
// Don't send any event when creating new article.
if ( 'auto-draft' === $new_status || 'inherit' === $new_status ) {
return;
}
$tracker = init_tracker();
$action = '';
if ( ! in_array( $post->post_type, get_supported_post_types(), true ) ) {
return new WP_Error(
'sophi_unsupported_post_type',
'This post type is not supported.'
);
}
if ( is_wp_error( $tracker ) ) {
return $tracker;
}
// publish, update, delete or unpublish
if ( 'publish' === $new_status && 'publish' !== $old_status ) {
$action = 'publish';
} elseif ( 'publish' === $new_status && 'publish' === $old_status ) {
$action = 'update';
} elseif ( 'trash' === $new_status ) {
$action = 'delete';
} elseif ( 'publish' !== $new_status && 'publish' === $old_status ) {
$action = 'unpublish';
}
if ( ! $action ) {
return new WP_Error(
'sophi_invalid_action',
'The publishing action is invalid.'
);
}
if ( class_exists( 'WPSEO_Meta' ) ) {
// Detect if the current request comes from Quick Edit.
if (
! empty( $_POST['_inline_edit'] )
&& wp_verify_nonce( $_POST['_inline_edit'], 'inlineeditnonce' )
&& ! empty( $_POST['action'] )
&& 'inline-save' === $_POST['action']
) {
return send_track_event( $tracker, $post, $action );
}
$pending_action = get_transient( 'sophi_content_sync_pending_' . $post->ID );
// Only set temporary action when publishing content
if ( ! $pending_action && 'publish' === $action ) {
set_transient( 'sophi_content_sync_pending_' . $post->ID, $action, MINUTE_IN_SECONDS );
}
return send_track_event( $tracker, $post, $action );
}
send_track_event( $tracker, $post, $action );
}
/**
* Send the track event to Sophi SnowPlow server.
*
* @since 1.0.4
*
* @param Tracker $tracker Tracker object.
* @param WP_Post $post WP_Post object.
* @param string $action Publishing action.
*/
function send_track_event( $tracker, $post, $action ) {
$pending_action = get_transient( 'sophi_content_sync_pending_' . $post->ID );
$data = get_post_data( $post );
$data['action'] = $action;
if ( $pending_action ) {
$data['action'] = $pending_action;
delete_transient( 'sophi_content_sync_pending_' . $post->ID );
}
$tracker->trackUnstructEvent(
[
'schema' => 'iglu:com.sophi/content_update/jsonschema/2-0-3',
'data' => $data,
],
[
[
'schema' => 'iglu:com.globeandmail/environment/jsonschema/1-0-9',
'data' => [
'environment' => get_sophi_settings( 'environment' ),
'client' => get_sophi_settings( 'tracker_client_id' ),
],
],
]
);
}
/**
* Initialize Snowplow tracker.
*
* @return Tracker|WP_Error
*/
function init_tracker() {
$collector_url = get_sophi_settings( 'collector_url' );
$tracker_client_id = get_sophi_settings( 'tracker_client_id' );
if ( ! $collector_url ) {
return new WP_Error(
'sophi_missing_collector_url',
'The collector URL is missing.'
);
}
if ( ! $tracker_client_id ) {
return new WP_Error(
'sophi_missing_tracker_client_id',
'The Tracker Client ID is missing.'
);
}
$app_id = sprintf( '%s:cms', $tracker_client_id );
$emitter = new SyncEmitter( $collector_url, 'https', 'POST', 1, false );
$subject = new Subject();
return new Tracker( $emitter, $subject, 'sophiTag', $app_id, false );
}
/**
* Prepare post data to send to Snowplow.
*
* @param WP_Post $post Post object.
*
* @return array
*/
function get_post_data( $post ) {
$content = apply_filters( 'the_content', get_the_content( null, false, $post ) );
$content = str_replace( ']]>', ']]>', $content );
$canonical_url = wp_get_canonical_url( $post );
$keywords = [];
$permalink = get_permalink( $post );
// Support Yoast SEO canonical URL and focus keyphrase.
if ( class_exists( 'WPSEO_Meta' ) ) {
$yoast_canonical = get_post_meta( $post->ID, '_yoast_wpseo_canonical', true );
if ( $yoast_canonical ) {
$canonical_url = $yoast_canonical;
}
$yoast_focuskw = get_post_meta( $post->ID, '_yoast_wpseo_focuskw', true );
if ( ! empty( $yoast_focuskw ) ) {
// Limit focus keyphrase to max length of 128.
$keywords = [ substr( $yoast_focuskw, 0, 128 ) ];
}
}
$parsed_url = wp_parse_url( $permalink );
$hostname = $parsed_url['host'] ?? '';
$path = $parsed_url['path'] ?? '';
$data = [
'contentId' => strval( $post->ID ),
'headline' => get_the_title( $post ),
'byline' => [ get_the_author_meta( 'display_name', $post->post_author ) ],
'accessCategory' => 'free access',
'publishedAt' => gmdate( \DateTime::RFC3339, strtotime( $post->post_date_gmt ) ),
'plainText' => wp_strip_all_tags( $content ),
'size' => str_word_count( wp_strip_all_tags( $content ) ),
'sectionNames' => Utils\get_post_categories( $post->ID ),
'modifiedAt' => gmdate( \DateTime::RFC3339, strtotime( $post->post_modified_gmt ) ),
'tags' => Utils\get_post_tags( $post ),
'url' => $permalink,
'type' => Utils\get_post_content_type( $post ),
'promoImageUri' => '',
'thumbnailImageUri' => get_the_post_thumbnail_url( $post, 'full' ),
'embeddedImagesCount' => Utils\get_number_of_embedded_images( $content ),
'classificationCode' => '',
'collectionName' => '',
'isSponsored' => false,
'promoPlainText' => '',
'keywords' => $keywords,
'creditLine' => '',
'ownership' => '',
'editorialAccessName' => '',
'subtype' => '',
'redirectToUrl' => '',
'hostname' => $hostname,
'path' => $path,
];
$data = array_filter( $data );
// Add canonical after filtering the falsy items.
$data['isCanonical'] = untrailingslashit( $canonical_url ) === untrailingslashit( $permalink );
/**
* Filter post data for content sync events (aka "CMS updates" in Sophi.io terms) sent to Sophi Collector. This allows control over data before it is sent to Collector in case it needs to be modified for unique site needs. Note that if you add, change, or remove any fields with this that those changes will need to be coordinated with the Sophi.io team to ensure content is appropriately received by Collector.
*
* @since 1.0.0
* @hook sophi_post_data
*
* @param {array} $post_data Formatted post data.
*
* @return {array} Formatted post data.
*/
return apply_filters( 'sophi_post_data', $data );
}