PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 3.1.5
Jetpack – WP Security, Backup, Speed, & Growth v3.1.5
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / publicize.php

publicize.php in Jetpack – WP Security, Backup, Speed, & Growth 3.1.5, at modules/publicize.php

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