-
Notifications
You must be signed in to change notification settings - Fork 1
/
gist_filter.module
512 lines (466 loc) · 17.9 KB
/
gist_filter.module
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
<?php
/**
* @file
* Provides the ability to embed gists from Github.
*
* This module provides an input filter to add a gist (from Github) anywhere
* input filters are accepted. You have the option of replacing the filter tag
* with a link to the gist, displaying the gist inside <code> tags or embedding
* the gist itself.
*/
/**
* Implements hook_filter_info().
*/
function gist_filter_filter_info() {
$filters['gist_filter'] = array(
'title' => t('Gist filter (Github Gists)'),
'description' => t('Substitutes [gist:xx] tags with the gist located at https://gist.github.com/user/xx.'),
'process callback' => 'gist_filter_gist_filter_process',
'default settings' => array(
'gist_filter_display_method' => config_get('gist_filter.settings', 'default_display_method'),
),
'settings callback' => 'gist_filter_gist_filter_settings',
'tips callback' => 'gist_filter_filter_tips',
'cache' => TRUE,
'weight' => 0,
);
return $filters;
}
/**
* Implements hook_filter_info_alter().
*/
function gist_filter_filter_info_alter(&$info) {
// If the Token Filter module is enabled we need to ensure it is processed
// after Gist Filter to avoid it trying to interpret the token-like structure
// of an embedded Gist filter.
if (module_exists('token_filter')) {
$info['filter_tokens']['weight'] = 1;
}
}
/**
* Implements hook_menu().
*
*/
function gist_filter_menu() {
$items = array();
$items['admin/config/content/gist-filter'] = array(
'title' => 'Gist Filter Global Settings',
'description' => 'Enter global settings for the Gist Filter module',
'page callback' => 'backdrop_get_form',
'page arguments' => array('gist_filter_config_form'),
'access arguments' => array('administer gist filter'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Implements hook_permission().
*/
function gist_filter_permission() {
return array(
'administer gist filter' => array(
'title' => t('Administer Gist Filter global settings'),
'description' => t('Configure global settings for Gist Filter, such as authentication, theme and cache.'),
),
);
}
/**
* Implements hook_config_info().
*/
function gist_filter_config_info() {
return array(
'gist_filter.settings' => array(
'label' => t('Gist Filter settings'),
'group' => t('Configuration'),
),
);
}
/**
* Implements hook_flush_caches().
*/
function gist_filter_flush_caches() {
// Only add to the list of all caches to flush if enabled in settings.
$clear_with_all = config_get('gist_filter.settings', 'cache_clear_with_all');
// Also check the database table exists.
if (db_table_exists('cache_gists') && $clear_with_all) {
return array('cache_gists');
}
}
/**
* Implements hook_admin_bar_cache_info().
*/
function gist_filter_admin_bar_cache_info() {
// Add to the list of caches in the admin menu that can be individually
// cleared.
$caches['cache_gists'] = array(
'title' => t('Gists'),
'callback' => '_cache_gists_clear',
);
return $caches;
}
/**
* Clear the gists cache.
*/
function _cache_gists_clear() {
// Clear all items from the gists cache.
cache('cache_gists')->flush();
// Also clear the entity_node and page caches to remove gists cached within a
// page.
cache('cache_entity_node')->flush();
cache('page')->flush();
}
/**
* Submit callback; clear gist cache.
*
* @ingroup forms
*/
function _cache_gists_clear_submit($form, &$form_state) {
_cache_gists_clear();
backdrop_set_message(t('Gist, page and entity_node caches cleared.'));
}
/**
* Implements hook_theme().
*/
function gist_filter_theme() {
return array(
'gist_filter_code' => array(
'variables' => array('file' => array()),
),
);
}
/**
* Process callback for gist_filter.
*/
function gist_filter_gist_filter_process($text, $filter, $format) {
// Get the default display method for the text format.
$display = $filter->settings['gist_filter_display_method'];
$callback = '_gist_display_' . $display;
// If 'embed' is set in the token, override the default display method.
if (preg_match('@\[gistembed\:(?<id>[\w/]+)(?:\:(?<file>[\w\.]+))?\]@', $text)) {
$callback = '_gist_display_embed';
return preg_replace_callback('@\[gistembed\:(?<id>[\w/]+)(?:\:(?<file>[\w\.]+))?\]@', $callback, $text);
}
// If 'code' is set in the token, override the default display method.
if (preg_match('@\[gistcode\:(?<id>[\w/]+)(?:\:(?<file>[\w\.]+))?\]@', $text)) {
$callback = '_gist_display_code';
return preg_replace_callback('@\[gistcode\:(?<id>[\w/]+)(?:\:(?<file>[\w\.]+))?\]@', $callback, $text);
}
// If 'link' is set in the token, override the default display method.
if (preg_match('@\[gistlink\:(?<id>[\w/]+)(?:\:(?<file>[\w\.]+))?\]@', $text)) {
$callback = '_gist_display_link';
return preg_replace_callback('@\[gistlink\:(?<id>[\w/]+)(?:\:(?<file>[\w\.]+))?\]@', $callback, $text);
}
// Otherwise use the default display method for the text format.
return preg_replace_callback('@\[gist\:(?<id>[\w/]+)(?:\:(?<file>[\w\.]+))?\]@', $callback, $text);
}
/**
* Global settings page callback for gist_filter.
*/
function gist_filter_config_form($form, &$form_state) {
$form = array();
$config_file = 'gist_filter.settings';
$form['#config'] = $config_file;
$config = config($config_file);
// Container for general settings.
$form['general'] = array(
'#type' => 'details',
'#summary' => t('General'),
'#details' => t("Gist Filter enables you easily embed a gist anywhere in a filtered text field."),
'#open' => TRUE,
);
// Select the default display method for new text formats.
$form['general']['default_display_method'] = array(
'#type' => 'select',
'#title' => 'Default Display Method',
'#options' => array(
'code' => t('Code tags'),
'embed' => t('Embed'),
'link' => t('Link'),
),
'#required' => TRUE,
'#default_value' => $config->get('default_display_method'),
'#description' => t("You can change the default display method and this will affect any new text formats created."),
);
// Container for 'code' display settings.
$form['code'] = array(
'#type' => 'details',
'#summary' => t('Code'),
'#open' => TRUE,
);
// Prepare description for GitHub token field.
$description = array(
'<p>' . t("Each time a gist is retrieved to display as 'code', GitHub counts this towards your API rate limit. Anonymous users get 60 per hour; authenticated users get 5,000 per hour and Enterprise users get 15,000 per hour. If your number of gists is low, you probably won't need a token.") . '</p>',
'<p>' . t("If you don't already have a token, you will need to go to <a href='@github-tokens' target='_blank'>your GitHub tokens page</a> and create a token.", array(
'@github-tokens' => 'https://github.com/settings/tokens',
)) . '</p>',
);
$description = implode('', $description);
// GitHub Authentication token.
$form['code']['github_token'] = array(
'#type' => 'textfield',
'#title' => 'GitHub Personal Access Token',
'#default_value' => $config->get('github_token', ''),
'#description' => $description,
);
// Container for 'embed' display settings.
$form['embed'] = array(
'#type' => 'details',
'#summary' => t('Embed'),
'#open' => TRUE,
);
// Define the list of themes.
$gist_themes = array(
'default' => 'Default',
'chaos' => 'Chaos',
'cobalt' => 'Cobalt',
'idle-fingers' => 'Idle Fingers',
'monokai' => 'Monokai',
'obsidian' => 'Obsidian',
'one-dark' => 'One Dark',
'pastel-on-dark' => 'Pastel On Dark',
'solarized-dark' => 'Solarized Dark',
'solarized-light' => 'Solarized Light',
'terminal' => 'Terminal',
'tomorrow-night' => 'Tomorrow Night',
'twilight' => 'Twilight',
);
// Select the theme for the embed window.
$form['embed']['gist_embed_theme'] = array(
'#type' => 'select',
'#title' => 'Gist Embed Theme',
'#options' => $gist_themes,
'#required' => TRUE,
'#default_value' => $config->get('gist_embed_theme'),
'#description' => t("Themes are from <a href='@developer'>Will Boyd</a> and can be previewed on <a href='@preview-page'>this page</a>.", array(
'@developer' => 'https://github.com/lonekorean/gist-syntax-themes',
'@preview-page' => 'https://lonekorean.github.io/gist-syntax-themes/',
)),
);
// Allow disabling the 'noscript' display of code block to save on API calls.
$form['embed']['enable_noscript_display'] = array(
'#type' => 'checkbox',
'#title' => "Display using 'code' method if Javascript is disabled",
'#description' => t("Enabling this option will include the 'code' display method of the gist to display if Javascript is disabled."),
'#default_value' => $config->get('enable_noscript_display'),
);
// Container for cache settings.
$cache_scenarios = array(
t("A gist is embedded using the <strong>code</strong> display method."),
t("A gist is embedded using the <strong>embed</strong> display method, and the site is set to display using the <strong>code</strong> method if Javascript is disabled."),
);
$description = array(
'<p>' . t("Gist Filter keeps gists saved in the cache if they haven't changed at source. Not only does this help pages with gists on to load faster, but it also reduces the risk of your site exceeding your GitHub API rate limit.") . '</p>',
'<p>' . t("Gists are saved in the cache in the following scenarios:") . '</p>',
theme('item_list', array('items' => $cache_scenarios)),
'<p>' . t("Once saved in the cache, a gist by default remains in the cache. If the gist is updated, the cached gist will be updated when the page cache is next cleared.") . '</p>',
'<p>' . t("If the setting below is enabled, then the gists cache will be cleared when running 'Flush all caches' or 'Page and else'. Otherwise, the gists cache can be cleared by selecting 'Gists' from the 'Flush all caches' submenu or using the button below.") . '</p>',
);
$description = implode('', $description);
$form['cache'] = array(
'#type' => 'details',
'#summary' => t('Cache'),
'#details' => $description,
'#open' => TRUE,
);
$form['cache']['clear'] = array(
'#type' => 'submit',
'#value' => t('Clear gists cache'),
'#submit' => array('_cache_gists_clear_submit'),
'#description' => t("Don't forget to save any setting changes before clearing the cache."),
);
// Configure whether or not gist cache is cleared when flushing all caches.
$form['cache']['cache_clear_with_all'] = array(
'#type' => 'checkbox',
'#title' => "Clear the gist cache when flushing all caches",
'#description' => t("If you have a lot of gists throughout the site displayed as 'code' (or 'embed' with 'code' when Javascript is disabled) and those gists do not change regularly, then disabling the cache clear can avoid you breaching your GitHub API rate limit."),
'#default_value' => $config->get('cache_clear_with_all'),
);
return system_settings_form($form);
}
/**
* Settings callback for gist_filter.
*/
function gist_filter_gist_filter_settings($form, $form_state, $filter, $format) {
$settings['gist_filter_display_method'] = array(
'#title' => t('Gist default display method'),
'#type' => 'select',
'#options' => array(
'code' => t('Code tags'),
'embed' => t('Embed'),
'link' => t('Link'),
),
'#default_value' => isset($filter->settings['gist_filter_display_method']) ? $filter->settings['gist_filter_display_method'] : $defaults['gist_filter_display_method'],
);
return $settings;
}
/**
* Filter tip callback for gist_filter.
*/
function gist_filter_filter_tips($filter, $format, $long = FALSE) {
$display = $filter->settings['gist_filter_display_method'];
// Define the tip for each display method override.
$tip_link = t('Use [gistlink:####] where #### is your gist number to create a link to the gist.');
$tip_embed = t('Use [gistembed:####] where #### is your gist number to embed the gist.');
$tip_code = t('Use [gistcode:####] where #### is your gist number to include the gist within code tags.');
// Define the action text for the default display method and the override
// tips to include.
$override_tips = array();
switch ($display) {
case 'link':
$default_action = t('create a link to the gist');
$override_tips[] = $tip_embed;
$override_tips[] = $tip_code;
break;
case 'embed':
$default_action = t('embed the gist');
$override_tips[] = $tip_link;
$override_tips[] = $tip_code;
break;
case 'code':
$default_action = t('include the gist within code tags');
$override_tips[] = $tip_link;
$override_tips[] = $tip_embed;
break;
}
// Define the tip for the default display method.
$default_tip = array(t('Use [gist:####] where #### is your gist number to %action.</br>To add a specific file use [gist:123abc456def7890:myfile.sh] or the
equivalent from the examples below.', array('%action' => $default_action)));
// Merge and return the tips.
$tips = array_merge($default_tip, $override_tips);
return implode('</br>', $tips);
}
/**
* Theme function to render the contents of a gist file in <code> tags.
*/
function theme_gist_filter_code($vars) {
$file = $vars['file'];
return '<div class="backdrop-gist-file"><pre type="' . backdrop_strtolower($file['language']) . '">' . check_plain($file['content']) . '</pre></div>';
}
/**
* Replace the text with the content of the Gist, wrapped in <code> tags.
*/
function _gist_display_code($matches) {
// Get the Gist from the Github API.
$data = gist_filter_get_gist($matches[1]);
$output = '';
// If a file was specified, just render that one file.
if (isset($matches[2]) && !empty($matches[2]) && isset($data['files'][$matches[2]])) {
$output = theme('gist_filter_code', array(
'file' => $data['files'][$matches[2]],
));
}
// Otherwise, render all files.
else {
if (isset($data['files'])) {
foreach ($data['files'] as $file) {
$output .= theme('gist_filter_code', array(
'file' => $file,
));
}
}
else {
// There may be a message here.
watchdog('gist_filter', 'Possible error retrieving gist %gist_id: "%json"', array(
'%gist_id' => $matches[1],
// phpcs:ignore
'%json' => print_r($data, TRUE),
), WATCHDOG_ERROR);
}
}
return $output;
}
/**
* Replace the text with embedded script.
*/
function _gist_display_embed($matches) {
// Get the embed theme setting.
$config_file = 'gist_filter.settings';
$config = config($config_file);
$theme = $config->get('gist_embed_theme');
// Make the url agnostic to the scheme so gist will embed on http and https.
$gist_url = '//gist.github.com/' . $matches[1];
$gist_url = isset($matches[2]) && !empty($matches[2]) ? $gist_url . '.js?file=' . $matches[2] : $gist_url . '.js';
// Also grab the content and display it in code tags (in case the user does
// not have Javascript) if enabled.
$output = ($config->get('enable_noscript_display')) ? '<noscript>' . _gist_display_code($matches) . '</noscript>' : '';
// If the theme is not default then use javascript to add the CSS.
if ($theme != 'default') {
$theme_url = "https://cdn.rawgit.com/lonekorean/gist-syntax-themes/d49b91b3/stylesheets/$theme.css";
$theme_script = "<script>";
$theme_script .= "var head = document.getElementsByTagName('HEAD')[0];";
$theme_script .= "var link = document.createElement('link');";
$theme_script .= "link.rel = 'stylesheet';";
$theme_script .= "link.type = 'text/css';";
$theme_script .= "link.href = '$theme_url';";
$theme_script .= "head.appendChild(link);";
$theme_script .= "</script>";
$output .= $theme_script;
}
// Output the script with URL.
$output .= '<script src="' . $gist_url . '"></script>';
return $output;
}
/**
* Replace the text with a link.
*/
function _gist_display_link($matches) {
$gist_url = 'https://gist.github.com/' . $matches[1];
$gist_url = isset($matches[2]) && !empty($matches[2]) ? $gist_url . '#file_' . $matches[2] : $gist_url;
return l($gist_url, $gist_url);
}
/**
* Helper function to retrieve a Gist from the Github API.
*
* @param string $id
* The ID of the Gist to grab.
*
* @return array
* The data from the Github API.
*/
function gist_filter_get_gist($id) {
static $gists = array();
// First, try the static cache.
if (!isset($gists[$id])) {
// Not in the static cache so we will check the cache tables and if it
// exists, we will check to see if the source has changed. If it doesn't
// exist, we will download and cache it.
// Start preparing the request.
// Define standard header.
$headers = array();
$headers['Content-Type'] = 'application/json';
// Include the GitHub Personal Access token if it is set.
$token = config_get('gist_filter.settings', 'github_token');
if (!empty($token)) {
$headers['Authorization'] = 'token ' . $token;
}
// Compile the gist cache ID.
$cid = 'gist_filter:gist:' . $id;
// Check if this gist is already in the cache.
// $gist = cache_get($cid);
if ($cached = cache_get($cid, 'cache_gists')) {
// Retrieve the gist so it can be used if the source hasn't changed.
$gist = $cached->data;
// Retrieve the gist etag so we can check if the source has changed or
// not.
$gist_etag = $gist['etag'];
// Prepare the header for checking against the etag.
$headers['If-None-Match'] = "$gist_etag";
}
// Finalise the request.
$options = array();
$options['headers'] = $headers;
// Compile and submit the request.
$url = 'https://api.github.com/gists/' . $id;
$response = backdrop_http_request($url, $options);
// Process the response.
if ($response->code == "200") {
$gist = backdrop_json_decode($response->data);
$gist['etag'] = $response->headers['etag'];
// Cache the gist.
cache_set($cid, $gist, 'cache_gists', CACHE_PERMANENT);
}
}
// Return the cached or retrieved gist.
$gists[$id] = $gist;
return $gists[$id];
}