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