1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Module Name: Publicize |
4
|
|
|
* Module Description: Automated social marketing. |
5
|
|
|
* Sort Order: 10 |
6
|
|
|
* Recommendation Order: 7 |
7
|
|
|
* First Introduced: 2.0 |
8
|
|
|
* Requires Connection: Yes |
9
|
|
|
* Auto Activate: Yes |
10
|
|
|
* Module Tags: Social, Recommended |
11
|
|
|
* Feature: Engagement |
12
|
|
|
* Additional Search Queries: facebook, twitter, google+, googleplus, google, path, tumblr, linkedin, social, tweet, connections, sharing |
13
|
|
|
*/ |
14
|
|
|
|
15
|
|
|
class Jetpack_Publicize { |
16
|
|
|
|
17
|
|
|
public $in_jetpack = true; |
18
|
|
|
|
19
|
|
|
function __construct() { |
20
|
|
|
global $publicize_ui; |
21
|
|
|
|
22
|
|
|
$this->in_jetpack = ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'enable_module_configurable' ) ) ? true : false; |
23
|
|
|
|
24
|
|
|
if ( $this->in_jetpack && method_exists( 'Jetpack', 'module_configuration_load' ) ) { |
25
|
|
|
Jetpack::enable_module_configurable( __FILE__ ); |
26
|
|
|
Jetpack::module_configuration_load( __FILE__, array( $this, 'jetpack_configuration_load' ) ); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
require_once dirname( __FILE__ ) . '/publicize/publicize.php'; |
30
|
|
|
|
31
|
|
|
if ( $this->in_jetpack ) |
32
|
|
|
require_once dirname( __FILE__ ) . '/publicize/publicize-jetpack.php'; |
33
|
|
|
else { |
34
|
|
|
require_once dirname( dirname( __FILE__ ) ) . '/mu-plugins/keyring/keyring.php'; |
35
|
|
|
require_once dirname( __FILE__ ) . '/publicize/publicize-wpcom.php'; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
require_once dirname( __FILE__ ) . '/publicize/ui.php'; |
39
|
|
|
$publicize_ui = new Publicize_UI(); |
40
|
|
|
$publicize_ui->in_jetpack = $this->in_jetpack; |
|
|
|
|
41
|
|
|
|
42
|
|
|
// Jetpack specific checks / hooks |
43
|
|
|
if ( $this->in_jetpack) { |
44
|
|
|
// if sharedaddy isn't active, the sharing menu hasn't been added yet |
45
|
|
|
$active = Jetpack::get_active_modules(); |
46
|
|
View Code Duplication |
if ( in_array( 'publicize', $active ) && !in_array( 'sharedaddy', $active ) ) |
47
|
|
|
add_action( 'admin_menu', array( &$publicize_ui, 'sharing_menu' ) ); |
48
|
|
|
} |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
function jetpack_configuration_load() { |
52
|
|
|
wp_safe_redirect( menu_page_url( 'sharing', false ) ); |
53
|
|
|
exit; |
|
|
|
|
54
|
|
|
} |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
global $publicize_ui; |
58
|
|
|
new Jetpack_Publicize; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Helper functions for shared use in the services files |
62
|
|
|
*/ |
63
|
|
|
class Publicize_Util { |
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Truncates a string to be shorter than or equal to the length specified |
66
|
|
|
* Attempts to truncate on word boundaries |
67
|
|
|
* |
68
|
|
|
* @param string $string |
69
|
|
|
* @param int $length |
70
|
|
|
* @return string |
71
|
|
|
*/ |
72
|
|
|
public static function crop_str( $string, $length = 256 ) { |
73
|
|
|
$string = Publicize_Util::sanitize_message( $string ); |
74
|
|
|
$length = absint( $length ); |
75
|
|
|
|
76
|
|
|
if ( mb_strlen( $string, 'UTF-8' ) <= $length ) { |
77
|
|
|
return $string; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
// @see wp_trim_words() |
81
|
|
|
if ( 'characters' == _x( 'words', 'word count: words or characters?', 'jetpack' ) ) { |
82
|
|
|
return trim( mb_substr( $string, 0, $length - 1, 'UTF-8' ) ) . "\xE2\x80\xA6"; // ellipsis |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
$words = explode( ' ', $string ); |
86
|
|
|
|
87
|
|
|
$return = ''; |
88
|
|
|
while ( strlen( $word = array_shift( $words ) ) ) { |
89
|
|
|
$new_return = $return ? "$return $word" : $word; |
90
|
|
|
$new_return_length = mb_strlen( $new_return, 'UTF-8' ); |
91
|
|
|
if ( $new_return_length < $length - 1 ) { |
92
|
|
|
$return = $new_return; |
93
|
|
|
continue; |
94
|
|
|
} elseif ( $new_return_length == $length - 1 ) { |
95
|
|
|
$return = $new_return; |
96
|
|
|
break; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
if ( !$return ) { |
100
|
|
|
$return = mb_substr( $new_return, 0, $length - 1, 'UTF-8' ); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
break; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
return "$return\xE2\x80\xA6"; // ellipsis |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Returns an array of DOMNodes that are comments (including recursing child nodes) |
112
|
|
|
* |
113
|
|
|
* @param DOMNode $node |
114
|
|
|
* @return array |
115
|
|
|
*/ |
116
|
|
|
|
117
|
|
|
function get_comment_nodes( $node ) { |
118
|
|
|
$comment_nodes = array(); |
119
|
|
|
foreach ( $node->childNodes as $child ) { |
120
|
|
|
|
121
|
|
|
if ( XML_COMMENT_NODE === $child->nodeType ) { |
122
|
|
|
$comment_nodes[] = $child; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
if ( $child->hasChildNodes() ) { |
126
|
|
|
$child_comment_nodes = self::get_comment_nodes( $child ); |
127
|
|
|
$comment_nodes = array_merge( $comment_nodes, $child_comment_nodes ); |
128
|
|
|
} |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
return $comment_nodes; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Truncates HTML so that its textContent (text without markup) is shorter than or equal to the length specified. |
136
|
|
|
* The length of the returned string may be larger than the specified length due to the markup. |
137
|
|
|
* Attempts to truncate on word boundaries. |
138
|
|
|
* |
139
|
|
|
* @param string $string |
140
|
|
|
* @param int $length |
141
|
|
|
* @param array $allowed_tags KSES input |
142
|
|
|
* @return string |
143
|
|
|
*/ |
144
|
|
|
function crop_html( $string, $length = 256, $allowed_tags = array() ) { |
145
|
|
|
$tags = $GLOBALS['allowedtags']; // Markup allowed in comments... |
146
|
|
|
|
147
|
|
|
$tags['img'] = array( // ... plus images ... |
148
|
|
|
'alt' => true, |
149
|
|
|
'height' => true, |
150
|
|
|
'src' => true, |
151
|
|
|
'width' => true, |
152
|
|
|
); |
153
|
|
|
|
154
|
|
|
// ... and some other basics |
155
|
|
|
$tags['p'] = array(); |
156
|
|
|
$tags['ul'] = array(); |
157
|
|
|
$tags['ol'] = array(); |
158
|
|
|
$tags['li'] = array(); |
159
|
|
|
$tags['br'] = array(); |
160
|
|
|
|
161
|
|
|
$tags = array_merge( $tags, $allowed_tags ); |
162
|
|
|
|
163
|
|
|
// Clean up, then KSES to really lock it down |
164
|
|
|
$string = trim( (string) $string ); |
165
|
|
|
$string = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $string ); |
166
|
|
|
$string = wp_kses( $string, $tags ); |
167
|
|
|
|
168
|
|
|
$string = mb_convert_encoding( $string, 'HTML-ENTITIES', 'UTF-8' ); |
169
|
|
|
$dom = new DOMDocument( '1.0', 'UTF-8' ); |
170
|
|
|
|
171
|
|
|
// The @ is not enough to suppress errors when dealing with libxml, |
172
|
|
|
// we have to tell it directly how we want to handle errors. |
173
|
|
|
libxml_use_internal_errors( true ); |
174
|
|
|
@$dom->loadHTML( "<html><body>$string</body></html>" ); |
|
|
|
|
175
|
|
|
libxml_use_internal_errors( false ); |
176
|
|
|
|
177
|
|
|
// Strip comment nodes, if any |
178
|
|
|
$comment_nodes = self::get_comment_nodes( $dom->documentElement ); |
179
|
|
|
foreach ( $comment_nodes as &$comment_node ) { |
180
|
|
|
$comment_node->parentNode->removeChild( $comment_node ); |
181
|
|
|
} |
182
|
|
|
if ( $comment_nodes ) { |
|
|
|
|
183
|
|
|
// Update the $string (some return paths work from just $string) |
184
|
|
|
$string = $dom->saveHTML(); |
185
|
|
|
$string = preg_replace( '/^<!DOCTYPE.+?>/', '', $string ); |
186
|
|
|
$string = str_replace( array('<html>', '</html>', '<body>', '</body>' ), array( '', '', '', '' ), $string ); |
187
|
|
|
$string = trim( $string ); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
// Find the body |
191
|
|
|
$body = false; |
192
|
|
|
foreach ( $dom->childNodes as $child ) { |
193
|
|
|
if ( XML_ELEMENT_NODE === $child->nodeType && 'html' === strtolower( $child->tagName ) ) { |
194
|
|
|
$body = $child->firstChild; |
195
|
|
|
break; |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if ( !$body ) { |
200
|
|
|
return self::crop_str( $string, $length ); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
// If the text (without the markup) is shorter than $length, just return |
204
|
|
|
if ( mb_strlen( $body->textContent, 'UTF-8' ) <= $length ) { |
205
|
|
|
return $string; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
$node = false; |
|
|
|
|
209
|
|
|
do { |
210
|
|
|
$node = self::remove_innermost_last_child( $body, $node_removed_from ); |
211
|
|
|
$new_string_length = mb_strlen( $body->textContent, 'UTF-8' ); |
212
|
|
|
} while ( $new_string_length > $length ); |
213
|
|
|
|
214
|
|
|
$new_string = $dom->saveHTML( $body ); |
215
|
|
|
$new_string = mb_substr( $new_string, 6, -7, 'UTF-8' ); // 6: <body>, 7: </body> |
216
|
|
|
|
217
|
|
|
if ( !$node ) { |
218
|
|
|
return $new_string ? $new_string : self::crop_str( $string, $length ); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
$append_string_length = $length - $new_string_length; |
222
|
|
|
|
223
|
|
|
if ( !$append_string_length ) { |
224
|
|
|
return $new_string; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
if ( $append_string_length > 1 && XML_TEXT_NODE === $node->nodeType ) { // 1: ellipsis |
228
|
|
|
$append_string = self::crop_str( $node->textContent, $append_string_length ); // includes ellipsis |
229
|
|
|
$append_node = $dom->createTextNode( $append_string ); |
230
|
|
|
$node_removed_from->appendChild( $append_node ); |
231
|
|
|
$new_string = $dom->saveHTML( $body ); |
232
|
|
|
$new_string = mb_substr( $new_string, 6, -7, 'UTF-8' ); |
233
|
|
|
} elseif ( $append_string_length > 9 && XML_ELEMENT_NODE === $node->nodeType && 'p' == strtolower( $node->nodeName ) ) { // 9: '<p>X{\xE2\x80\xA6}</p>' |
234
|
|
|
$new_string .= '<p>' . self::crop_str( $node->textContent, $append_string_length - 8 ) . '</p>'; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
// Clean up any empty Paragraphs that might have occurred after removing their children |
238
|
|
|
return trim( preg_replace( '#<p>\s*</p>#i', '', $new_string ) ); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
function remove_innermost_last_child( $node, &$node_removed_from ) { |
242
|
|
|
$node_removed_from = $node; |
243
|
|
|
|
244
|
|
|
if ( !$node->lastChild ) { |
245
|
|
|
return false; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
if ( $node->lastChild->hasChildNodes() ) { |
249
|
|
|
return self::remove_innermost_last_child( $node->lastChild, $node_removed_from ); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
$innermost_last_child = $node->lastChild; |
253
|
|
|
$node->removeChild( $innermost_last_child ); |
254
|
|
|
|
255
|
|
|
return $innermost_last_child; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
function bump_stats_extras_publicize_url( $bin, $post_id ) { |
259
|
|
|
static $done = array(); |
260
|
|
|
if ( isset( $done[$post_id] ) ) { |
261
|
|
|
return; |
262
|
|
|
} |
263
|
|
|
$done[$post_id] = true; |
264
|
|
|
|
265
|
|
|
/** This action is documented in modules/widgets/social-media-icons.php */ |
266
|
|
|
do_action( 'jetpack_bump_stats_extras', 'publicize_url', $bin ); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
public static function build_sprintf( $args ) { |
270
|
|
|
$search = array(); |
271
|
|
|
$replace = array(); |
272
|
|
|
foreach ( $args as $k => $arg ) { |
273
|
|
|
if ( 0 == $k ) { |
274
|
|
|
$string = $arg; |
275
|
|
|
continue; |
276
|
|
|
} |
277
|
|
|
$search[] = "%$arg%"; |
278
|
|
|
$replace[] = "%$k\$s"; |
279
|
|
|
} |
280
|
|
|
return str_replace( $search, $replace, $string ); |
|
|
|
|
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
public static function sanitize_message( $message ) { |
284
|
|
|
$message = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $message ); |
285
|
|
|
$message = wp_kses( $message, array() ); |
286
|
|
|
$message = preg_replace('/[\r\n\t ]+/', ' ', $message); |
287
|
|
|
$message = trim( $message ); |
288
|
|
|
$message = htmlspecialchars_decode( $message, ENT_QUOTES ); |
289
|
|
|
return $message; |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
if( ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) && ! function_exists( 'publicize_init' ) ) { |
294
|
|
|
/** |
295
|
|
|
* Helper for grabbing a Publicize object from the "front-end" (non-admin) of |
296
|
|
|
* a site. Normally Publicize is only loaded in wp-admin, so there's a little |
297
|
|
|
* set up that you might need to do if you want to use it on the front end. |
298
|
|
|
* Just call this function and it returns a Publicize object. |
299
|
|
|
* |
300
|
|
|
* @return Publicize Object |
301
|
|
|
*/ |
302
|
|
|
function publicize_init() { |
303
|
|
|
global $publicize; |
304
|
|
|
|
305
|
|
|
if ( ! class_exists( 'Publicize' ) ) { |
306
|
|
|
require_once dirname( __FILE__ ) . '/publicize/publicize.php'; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
return $publicize; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
} |
313
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.