PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 15.0
Jetpack – WP Security, Backup, Speed, & Growth v15.0
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 / shortcodes / gist.php
gist.php
268 lines 8.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * GitHub's Gist site supports oEmbed but their oembed provider only
4 * returns raw HTML (no styling) and the first little bit of the code.
5 *
6 * Their JavaScript-based embed method is a lot better, so that's what we're using.
7 *
8 * Supported formats:
9 * Full URL: https://gist.github.com/57cc50246aab776e110060926a2face2
10 * Full URL with username: https://gist.github.com/jeherve/57cc50246aab776e110060926a2face2
11 * Full URL linking to specific file: https://gist.github.com/jeherve/57cc50246aab776e110060926a2face2#file-wp-config-php
12 * Full URL, no username, linking to specific file: https://gist.github.com/57cc50246aab776e110060926a2face2#file-wp-config-php
13 * Gist ID: [gist]57cc50246aab776e110060926a2face2[/gist]
14 * Gist ID within tag: [gist 57cc50246aab776e110060926a2face2]
15 * Gist ID with username: [gist jeherve/57cc50246aab776e110060926a2face2]
16 * Gist private ID with username: [gist xknown/fc5891af153e2cf365c9]
17 *
18 * @package automattic/jetpack
19 */
20
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit( 0 );
23 }
24
25 wp_embed_register_handler( 'github-gist', '#https?://gist\.github\.com/([a-zA-Z0-9/]+)(\#file\-[a-zA-Z0-9\_\-]+)?#', 'github_gist_embed_handler' );
26 add_shortcode( 'gist', 'github_gist_shortcode' );
27
28 /**
29 * Handle gist embeds.
30 *
31 * @since 2.8.0
32 *
33 * @global WP_Embed $wp_embed
34 *
35 * @param array $matches Results after parsing the URL using the regex in wp_embed_register_handler().
36 * @param array $attr Embed attributes.
37 * @param string $url The original URL that was matched by the regex.
38 * @param array $rawattr The original unmodified attributes.
39 * @return string The embed HTML.
40 */
41 function github_gist_embed_handler( $matches, $attr, $url, $rawattr ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
42 // Let the shortcode callback do all the work.
43 return github_gist_shortcode( $matches, $url );
44 }
45
46 /**
47 * Extract an ID from a Gist shortcode or a full Gist URL.
48 *
49 * @since 7.3.0
50 *
51 * @param string $gist Gist shortcode or full Gist URL.
52 *
53 * @return array $gist_info {
54 * Array of information about our gist.
55 * @type string $id Unique identifier for the gist.
56 * @type string $file File name if the gist links to a specific file.
57 * }
58 */
59 function jetpack_gist_get_shortcode_id( $gist = '' ) {
60 $gist_info = array(
61 'id' => '',
62 'file' => '',
63 'ts' => 8,
64 );
65 // Simple shortcode, with just an ID.
66 if ( ctype_alnum( $gist ) ) {
67 $gist_info['id'] = $gist;
68 }
69
70 // Full URL? Only keep the relevant parts.
71 $parsed_url = wp_parse_url( $gist );
72 if (
73 ! empty( $parsed_url )
74 && is_array( $parsed_url )
75 && isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) && isset( $parsed_url['path'] )
76 ) {
77 // Not a Gist URL? Bail.
78 if ( 'gist.github.com' !== $parsed_url['host'] ) {
79 return array(
80 'id' => '',
81 'file' => '',
82 'ts' => 8,
83 );
84 }
85
86 // Keep the file name if there was one.
87 if ( ! empty( $parsed_url['fragment'] ) ) {
88 $gist_info['file'] = preg_replace( '/(?:file-)(.+)/', '$1', $parsed_url['fragment'] );
89 }
90
91 // Validate the path structure - should be either username/gistid or just gistid
92 $path = trim( $parsed_url['path'], '/' );
93 if ( ! preg_match( '#^([a-zA-Z0-9_-]+/)?([a-f0-9]+)$#', $path ) ) {
94 return array(
95 'id' => '',
96 'file' => '',
97 'ts' => 8,
98 );
99 }
100
101 $gist_info['id'] = $path;
102 // Reassign $gist with the identifier to clean it up below.
103 $gist = $path;
104
105 // Parse the query args to obtain the tab spacing.
106 if ( ! empty( $parsed_url['query'] ) ) {
107 $query_args = array();
108 wp_parse_str( $parsed_url['query'], $query_args );
109 if ( ! empty( $query_args['ts'] ) ) {
110 $gist_info['ts'] = absint( $query_args['ts'] );
111 }
112 }
113 }
114
115 // Not a URL nor an ID? Look for "username/id", "/username/id", or "id", and only keep the ID.
116 if ( preg_match( '#^/?(([a-z0-9_-]+/)?([a-z0-9]+))$#i', $gist, $matches ) ) {
117 $gist_info['id'] = $matches[3];
118 }
119
120 return $gist_info;
121 }
122
123 /**
124 * Callback for gist shortcode.
125 *
126 * @since 2.8.0
127 *
128 * @param array $atts Attributes found in the shortcode.
129 * @param string $content Content enclosed by the shortcode.
130 *
131 * @return string The gist HTML.
132 */
133 function github_gist_shortcode( $atts, $content = '' ) {
134
135 if ( empty( $atts[0] ) && empty( $content ) ) {
136 if ( current_user_can( 'edit_posts' ) ) {
137 return esc_html__( 'Please specify a Gist URL or ID.', 'jetpack' );
138 } else {
139 return '<!-- Missing Gist ID -->';
140 }
141 }
142
143 $id = ( ! empty( $content ) ) ? $content : $atts[0];
144
145 // Parse a URL to get an ID we can use.
146 $gist_info = jetpack_gist_get_shortcode_id( $id );
147 if ( empty( $gist_info['id'] ) ) {
148 if ( current_user_can( 'edit_posts' ) ) {
149 return esc_html__( 'The Gist ID you provided is not valid. Please try a different one.', 'jetpack' );
150 } else {
151 return '<!-- Invalid Gist ID -->';
152 }
153 } else {
154 // Add trailing .json to all unique gist identifiers.
155 $id = $gist_info['id'] . '.json';
156 }
157
158 // The file name can come from the URL passed, or from a shortcode attribute.
159 if ( ! empty( $gist_info['file'] ) ) {
160 $file = $gist_info['file'];
161 } elseif ( ! empty( $atts['file'] ) ) {
162 $file = $atts['file'];
163 } else {
164 $file = '';
165 }
166
167 // Replace - by . to get a real file name from slug.
168 if ( ! empty( $file ) ) {
169 // Find the last -.
170 $dash_position = strrpos( $file, '-' );
171 if ( false !== $dash_position ) {
172 // Replace the - by a period.
173 $file = substr_replace( $file, '.', $dash_position, 1 );
174 }
175
176 $file = rawurlencode( $file );
177 }
178
179 // Set the tab size, allowing attributes to override the query string.
180 $tab_size = $gist_info['ts'];
181 if ( ! empty( $atts['ts'] ) ) {
182 $tab_size = absint( $atts['ts'] );
183 }
184
185 if (
186 class_exists( 'Jetpack_AMP_Support' )
187 && Jetpack_AMP_Support::is_amp_request()
188 ) {
189 /*
190 * According to <https://www.ampproject.org/docs/reference/components/amp-gist#height-(required)>:
191 *
192 * > Note: You must find the height of the gist by inspecting it with your browser (e.g., Chrome Developer Tools).
193 *
194 * However, this does not seem to be the case any longer. The actual height of the content does get set in the
195 * page after loading. So this is just the initial height.
196 * See <https://github.com/ampproject/amphtml/pull/17738>.
197 */
198 $height = 240;
199
200 $amp_tag = sprintf(
201 '<amp-gist layout="fixed-height" data-gistid="%s" height="%s"',
202 esc_attr( basename( $id, '.json' ) ),
203 esc_attr( $height )
204 );
205 if ( ! empty( $file ) ) {
206 $amp_tag .= sprintf( ' data-file="%s"', esc_attr( $file ) );
207 }
208 $amp_tag .= '></amp-gist>';
209 return $amp_tag;
210 }
211
212 // URL points to the entire gist, including the file name if there was one.
213 $id = ( ! empty( $file ) ? $id . '?file=' . $file : $id );
214 $return = false;
215
216 $request = wp_remote_get( esc_url_raw( 'https://gist.github.com/' . esc_attr( $id ) ) );
217 $request_code = wp_remote_retrieve_response_code( $request );
218
219 if ( 200 === $request_code ) {
220 $request_body = wp_remote_retrieve_body( $request );
221 $request_data = json_decode( $request_body );
222
223 wp_enqueue_style( 'jetpack-gist-styling', esc_url( $request_data->stylesheet ), array(), JETPACK__VERSION );
224
225 $gist = substr_replace( $request_data->div, sprintf( 'style="tab-size: %1$s" ', absint( $tab_size ) ), 5, 0 );
226
227 // Add inline styles for the tab style in the opening div of the gist.
228 $gist = preg_replace(
229 '#(\<div\s)+(id=\"gist[0-9]+\")+(\sclass=\"gist\"\>)?#',
230 sprintf( '$1style="tab-size: %1$s" $2$3', absint( $tab_size ) ),
231 $request_data->div,
232 1
233 );
234
235 // Add inline style to prevent the bottom margin to the embed that themes like TwentyTen, et al., add to tables.
236 $return = sprintf( '<style>.gist table { margin-bottom: 0; }</style>%1$s', $gist );
237 }
238
239 if (
240 // No need to check for a nonce here, that's already handled by Core further up.
241 // phpcs:disable WordPress.Security.NonceVerification.Missing
242 isset( $_POST['type'] )
243 && 'embed' === $_POST['type']
244 && isset( $_POST['action'] )
245 && 'parse-embed' === $_POST['action']
246 // phpcs:enable WordPress.Security.NonceVerification.Missing
247 ) {
248 return github_gist_simple_embed( $id, $tab_size );
249 }
250
251 return $return;
252 }
253
254 /**
255 * Use script tag to load shortcode in editor.
256 * Can't use wp_enqueue_script here.
257 *
258 * @since 3.9.0
259 *
260 * @param string $id The ID of the gist.
261 * @param int $tab_size The tab size of the gist.
262 * @return string The script tag of the gist.
263 */
264 function github_gist_simple_embed( $id, $tab_size = 8 ) {
265 $id = str_replace( 'json', 'js', $id );
266 return '<script src="' . esc_url( "https://gist.github.com/$id?ts=$tab_size" ) . '"></script>'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
267 }
268