Improved the SVG icons to use SVG symbols
[WebKit-https.git] / Websites / webkit.org / wp-content / themes / webkit / functions.php
1 <?php
2
3 // Declare theme features
4 add_theme_support( 'post-thumbnails' );
5
6 add_action( 'init', function () {
7     register_nav_menu('site-nav', __( 'Site Navigation' ));
8     register_nav_menu('footer-nav', __( 'Footer Navigation' ));
9     register_nav_menu('sitemap', __( 'Site Map Page' ));
10 } );
11
12 add_action( 'wp_dashboard_setup', function () {
13     $SurveyWidget = new WebKit_Nightly_Survey();
14     $SurveyWidget->add_widget();
15 });
16
17 function modify_contact_methods($profile_fields) {
18
19     // Add new fields
20     $profile_fields['twitter'] = 'Twitter Handle';
21     unset($profile_fields['aim']);
22     unset($profile_fields['yim']);
23     unset($profile_fields['jabber']);
24
25     return $profile_fields;
26 }
27
28 function get_nightly_build ($type = 'builds') {
29     if (!class_exists('SyncWebKitNightlyBuilds'))
30         return false;
31
32     $WebKitBuilds = SyncWebKitNightlyBuilds::object();
33     $build = $WebKitBuilds->latest($type);
34     return $build;
35 }
36
37 function get_nightly_source () {
38     return get_nightly_build('source');
39 }
40
41 function get_nightly_archives ($limit) {
42     if (!class_exists('SyncWebKitNightlyBuilds'))
43         return array();
44
45     $WebKitBuilds = SyncWebKitNightlyBuilds::object();
46     $builds = $WebKitBuilds->records('builds', $limit);
47     return (array)$builds;
48 }
49
50 function get_nightly_builds_json () {
51     if (!class_exists('SyncWebKitNightlyBuilds'))
52         return '';
53
54     $WebKitBuilds = SyncWebKitNightlyBuilds::object();
55     $records = $WebKitBuilds->records('builds', 100000);
56     $builds = array();
57     foreach ( $records as $build ) {
58         $builds[] = $build[0];
59     }
60     $json = json_encode($builds);
61     return empty($json) ? "''" : $json;
62 }
63
64 add_filter('user_contactmethods', function ($fields) {
65     // Add Twitter field to user profiles
66     $fields['twitter'] = 'Twitter Handle';
67
68     // Remove unused social networks
69     unset($fields['aim']);
70     unset($fields['yim']);
71     unset($fields['jabber']);
72
73     return $fields;
74 });
75
76 add_action('init', function () {
77     register_sidebar(array(
78         'name'=> 'Home Tiles',
79         'id' => 'tiles',
80         'before_widget' => '',
81         'after_widget' => '',
82         'before_title' => '',
83         'after_title' => '',
84     ));
85 } );
86
87 // Start Page internal rewrite handling
88 add_action('after_setup_theme', function () {
89     add_rewrite_rule(
90         'nightly/start/([^/]+)/([0-9]+)/?$',
91         'index.php?pagename=nightly/start&nightly_branch=$matches[1]&nightly_build=$matches[2]',
92         'top'
93     );
94 });
95
96 add_filter('query_vars', function( $query_vars ) {
97     $query_vars[] = 'nightly_build';
98     $query_vars[] = 'nightly_branch';
99     return $query_vars;
100 });
101
102 add_filter('the_title', function( $title ) {
103     if ( is_admin() ) return $title;
104     if ( is_feed() ) return $title;
105
106     $title = str_replace(": ", ": <br>", $title);
107
108     $nowrap_strings = array();
109     if ($nowrap_setting = get_option("webkit_org_nowrap_strings")) {
110         $nowrap_strings = explode("\n", $nowrap_setting);
111     } else add_option("webkit_org_nowrap_strings", "\n");
112
113     foreach ($nowrap_strings as $token) {
114         $nobreak = str_replace(" ", " ", trim($token));
115         $title = str_replace(trim($token), $nobreak, $title);
116     }
117
118     return $title;
119 });
120
121 // For RSS feeds, convert relative URIs to absolute
122 add_filter('the_content', function($content) {
123     if (!is_feed()) return $content;
124     $base = trailingslashit(get_site_url());
125     $content = preg_replace('/<a([^>]*) href="\/([^"]*)"/', '<a$1 href="' . $base . '$2"', $content);
126     $content = preg_replace('/<img([^>]*) src="\/([^"]*)"/', '<img$1 src="' . $base . '$2"', $content);
127     return $content;
128 });
129
130 add_action('wp_head', function () {
131     if (!is_single()) return;
132
133     $style = get_post_meta(get_the_ID(), 'style', true);
134     if (!empty($style))
135         echo '<style type="text/css">' . $style . '</style>';
136
137     $script = get_post_meta(get_the_ID(), 'script', true);
138     if (!empty($script))
139         echo '<script type="text/javascript">' . $script . '</script>';
140 });
141
142 add_action('the_post', function($post) {
143     global $pages;
144
145     if (!(is_single() || is_page())) return;
146
147     $content = $post->post_content;
148     if (strpos($content, 'abovetitle') === false) return;
149     if (strpos($content, '<img') !== 0) return;
150
151     $post->post_title_img = substr($content, 0, strpos($content, ">\n") + 3);
152     $post->post_content = str_replace($post->post_title_img, '', $content);
153     $pages = array($post->post_content);
154 });
155
156 add_action('the_post', function($post) {
157     global $pages;
158
159     if (!(is_single() || is_page())) return;
160
161     $foreword = get_post_meta(get_the_ID(), 'foreword', true);
162     if ( ! $foreword ) return;
163
164     $content = $post->post_content;
165     // Transform Markdown
166     $Markdown = WPCom_Markdown::get_instance();
167     $foreword = wp_unslash( $Markdown->transform($foreword) );
168
169     $post->post_content = '<div class="foreword">' . $foreword . '</div>' . $content;
170     $pages = array($post->post_content);
171 });
172
173 add_filter('the_author', function($display_name) {
174     $post = get_post();
175     if (!(is_single() || is_page())) return;
176     $byline = get_post_meta(get_the_ID(), 'byline', true);
177     return empty($byline) ? $display_name : $byline;
178 });
179
180 function before_the_title() {
181     $post = get_post();
182
183     if ( isset($post->post_title_img) )
184         echo wp_make_content_images_responsive($post->post_title_img);
185 }
186
187 // Hide category 41: Legacy from archives
188 add_filter('pre_get_posts', function ($query) {
189     if ( $query->is_home() )
190         $query->set('cat', '-41');
191     return $query;
192 });
193
194 add_filter( 'get_the_excerpt', function( $excerpt ) {
195     $sentences = preg_split( '/(\.|!|\?)\s/', $excerpt, 2, PREG_SPLIT_DELIM_CAPTURE );
196
197     // if ( empty($sentences[1]) )
198     //     $sentences[1] = '&hellip;';
199
200     return $sentences[0] . $sentences[1];
201
202 });
203
204 function show_404_page() {
205     status_header(404);
206     return include( get_query_template( '404' ) );
207 }
208
209 include('widgets/post.php');
210 include('widgets/icon.php');
211 include('widgets/twitter.php');
212 include('widgets/page.php');
213
214 function table_of_contents() {
215     if ( class_exists('WebKitTableOfContents') )
216         WebKitTableOfContents::markup();
217 }
218
219 function has_table_of_contents() {
220     if ( class_exists('WebKitTableOfContents') )
221         return WebKitTableOfContents::hasIndex();
222 }
223
224 function table_of_contents_index( $content, $post_id ) {
225     if ( ! class_exists('WebKitTableOfContents') )
226         return $content;
227     $content = WebKitTableOfContents::parse($content);
228     WebKitTableOfContents::wp_insert_post($post_id);
229     return $content;
230 }
231
232 function is_super_cache_enabled() {
233     global $super_cache_enabled;
234     return (isset($super_cache_enabled) && true === $super_cache_enabled);
235 }
236
237 function include_post_icons() {
238     echo WebKit_Post_Icons::parse_icons();
239 }
240
241 function get_post_icon() {
242
243     $categories = get_the_category();
244     if (isset($categories[0]))
245         $slug = $categories[0]->slug;
246
247     if ('web-inspector' == $slug) {
248         $tags = get_the_tags();
249         if (isset($tags[0]))
250             $slug = $tags[0]->slug;
251     }
252
253     if (!WebKit_Post_Icons::has_icon($slug))
254         return 'default';
255
256     return $slug;
257 }
258
259 function tag_post_image_luminance($post_id) {
260     $threshold = 128;
261     $tags = array();
262
263     // Get the image data
264     $image_src = wp_get_attachment_image_src( get_post_thumbnail_id($post_id), 'small' );
265     $image_url = $image_src[0];
266
267     if ( empty($image_url) ) return $post_id;
268
269     // detect luminence value
270     $luminance = calculate_image_luminance($image_url);
271     $tags = wp_get_post_tags($post_id, array('fields' => 'names'));
272
273     if ( $luminance < $threshold )
274         $tags[] = 'dark';
275     elseif ( false !== ( $key = array_search('dark', $messages) ) )
276         unset($tags[ $key ]);
277
278     // Set a tag class
279     if ( ! empty($tags) )
280         wp_set_post_tags( $post_id, $tags, true );
281
282     return $post_id;
283 }
284
285 function calculate_image_luminance($image_url) {
286     if (!function_exists('ImageCreateFromString'))
287         return 1;
288
289     // Get original image dimensions
290     $size = getimagesize($image_url);
291
292     // Create image resource from source image
293     $image = ImageCreateFromString(file_get_contents($image_url));
294
295     // Allocate image resource
296     $sample = ImageCreateTrueColor(1, 1);
297
298     // Flood fill with a white background (to properly calculate luminance of PNGs with alpha)
299     ImageFill($sample , 0, 0, ImageColorAllocate($sample, 255, 255, 255));
300
301     // Downsample to 1x1 image
302     ImageCopyResampled($sample, $image, 0, 0, 0, 0, 1, 1, $size[0], $size[1]);
303
304     // Get the RGB value of the pixel
305     $rgb   = ImageColorAt($sample, 0, 0);
306     $red   = ($rgb >> 16) & 0xFF;
307     $green = ($rgb >> 8) & 0xFF;
308     $blue  = $rgb & 0xFF;
309
310     // Calculate relative luminance value (https://en.wikipedia.org/wiki/Relative_luminance)
311     return ( 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue);
312 }
313
314 function html_select_options(array $list, $selected = null, $values = false, $append = false) {
315         if ( ! is_array($list) ) return '';
316
317         $_ = '';
318
319         // Append the options if the selected value doesn't exist
320         if ( ( ! in_array($selected, $list) && ! isset($list[ $selected ])) && $append )
321             $_ .= '<option value="' . esc_attr($selected) . '">' .esc_html($selected) . '</option>';
322
323         foreach ( $list as $value => $text ) {
324
325             $value_attr = $selected_attr = '';
326
327             if ( $values ) $value_attr = ' value="' . esc_attr($value) . '"';
328             if ( ( $values && (string)$value === (string)$selected)
329                 || ( ! $values && (string)$text === (string)$selected ) )
330                     $selected_attr = ' selected="selected"';
331
332             if ( is_array($text) ) {
333                 $label = $value;
334                 $_ .= '<optgroup label="' . esc_attr($label) . '">';
335                 $_ .= html_select_options($text, $selected, $values);
336                 $_ .= '</optgroup>';
337                 continue;
338             } else $_ .= "<option$value_attr$selected_attr>$text</option>";
339
340         }
341         return $_;
342     }
343
344 add_filter('save_post', 'tag_post_image_luminance');
345
346 add_filter('next_post_link', function ( $format ) {
347     return str_replace('href=', 'class="page-numbers next-post" href=', $format);
348 });
349 add_filter('previous_post_link', function ( $format ) {
350     return str_replace('href=', 'class="page-numbers prev-post" href=', $format);
351 });
352
353 // Queue global scripts
354 add_action( 'wp_enqueue_scripts', function () {
355     wp_register_script(
356         'theme-global',
357         get_stylesheet_directory_uri() . '/scripts/global.js',
358         false,
359         '1.0',
360         true
361     );
362
363     wp_enqueue_script( 'theme-global' );
364
365 } );
366
367 class Responsive_Toggle_Walker_Nav_Menu extends Walker_Nav_Menu {
368
369     private $toggleid = null;
370
371     public function start_lvl( &$output, $depth = 0, $args = array() ) {
372         $output .= "\n" . str_repeat("\t", $depth);
373         $classes = array("sub-menu");
374         if ( 0 == $depth ) {
375             $classes[] = "sub-menu-layer";
376         }
377         $id = ( 0 == $depth ) ? " id=\"sub-menu-for-{$this->toggleid}\"" : '';
378         $class_names = esc_attr(join( ' ', $classes ));
379         $output .= "<ul class=\"$class_names\" role=\"menu\"$id>\n";
380     }
381
382     public function end_lvl( &$output, $depth = 0, $args = array() ) {
383         $indent = str_repeat("\t", $depth);
384         $output .= "$indent</ul>\n";
385     }
386
387     public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
388
389         $before = $args->link_before;
390         $after = $args->link_after;
391
392         if ( in_array('menu-item-has-children', $item->classes) && 0 == $depth ) {
393             $this->toggleid = $item->ID;
394             $args->before .= "<input type=\"checkbox\" id=\"toggle-{$item->ID}\" class=\"menu-toggle\" />";
395             $args->link_before = "<label for=\"toggle-{$item->ID}\" class=\"label-toggle\">" . $args->link_before;
396             $args->link_after .= "</label>";
397             $item->url = '#nav-sub-menu';
398         } elseif ( in_array('menu-item-has-children', $item->classes) && 1 == $depth ) {
399             // $item->role = "presentation";
400         } else $toggleid = null;
401
402         $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
403
404         $classes = empty( $item->classes ) ? array() : (array) $item->classes;
405         $classes[] = 'menu-item-' . $item->ID;
406
407         $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
408         $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
409
410         $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
411         $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
412
413         $output .= $indent . '<li' . $id . $class_names . '>';
414
415         $atts = array();
416         $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
417         $atts['target'] = ! empty( $item->target )     ? $item->target     : '';
418         $atts['rel']    = ! empty( $item->xfn )        ? $item->xfn        : '';
419         $atts['href']   = ! empty( $item->url )        ? $item->url        : '';
420         $atts['role']   = ! empty( $item->role )       ? $item->role       : '';
421
422         if ( in_array('menu-item-has-children', $item->classes) && 0 == $depth ) {
423             $atts['aria-haspopup'] = "true";
424             $atts['aria-owns'] = 'sub-menu-for-' . $item->ID;
425             $atts['aria-controls'] = 'sub-menu-for-' . $item->ID;
426             $atts['aria-expanded'] = 'true';
427         }
428
429         $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );
430
431         $attributes = '';
432         foreach ( $atts as $attr => $value ) {
433             if ( ! empty( $value ) ) {
434                 $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
435                 $attributes .= ' ' . $attr . '="' . $value . '"';
436             }
437         }
438
439         $item_output = $args->before;
440         $item_output .= '<a'. $attributes .'>';
441         $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
442         $item_output .= '</a>';
443         $item_output .= $args->after;
444
445         $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
446
447         $args->link_before = $before;
448         $args->link_after = $after;
449     }
450
451 }
452
453 class WebKit_Post_Icons {
454
455     private static $registry = array();
456
457     public static function parse_icons() {
458         if (!empty(self::$registry))
459             return '';
460
461         $svg_string = file_get_contents(get_stylesheet_directory() . '/images/icons.svg');
462         $svg = new SimpleXMLElement( $svg_string );
463         $svg->registerXPathNamespace('svg', 'http://www.w3.org/2000/svg');
464
465         $matches = $svg->xpath('//svg:symbol/@id');
466         foreach ($matches as $symbol)
467             self::$registry[(string)$symbol['id']] = true;
468
469         return $svg_string;
470     }
471
472     public static function has_icon($id) {
473         return isset(self::$registry[$id]);
474     }
475
476 }
477
478 class Front_Page_Posts {
479
480     private static $object;     // Singleton instance
481
482     private static $wp_query;   // WP_Query instance
483
484     public static function &object () {
485         if ( ! self::$object instanceof self )
486             self::$object = new self;
487
488         if ( empty(self::$wp_query) )
489             self::$wp_query = new WP_Query(array('post_type' => 'post', 'posts_per_page' => 16));
490
491         return self::$object;
492     }
493
494     public static function WP_Query() {
495         return self::$wp_query;
496     }
497
498 }
499
500 class WebKit_Nightly_Survey {
501
502     const COOKIE_PREFIX = 'webkitnightlysurvey_';
503     const DATA_SETTING_NAME = 'webkit_nightly_survey_data';
504     const SURVEY_FILENAME = 'survey.json';
505
506     public function add_widget() {
507
508         wp_add_dashboard_widget(
509             'webkit_nightly_survey_results', // Widget slug
510             'WebKit Nightly Survey Results', // Title
511             array($this, 'display_widget')   // Display function
512         );
513
514         if (isset($_GET['wksurvey']) && $_GET['wksurvey'] == 'download')
515             $this->download() && exit;
516
517     }
518
519     public function display_widget() {
520         $data = get_option(self::DATA_SETTING_NAME);
521         $styles = "
522             <style>
523             .survey_table .question {
524                 text-align: left;
525             }
526             .survey_table .score {
527                 font-size: 15px;
528                 min-width: 30px;
529                 text-align: right;
530                 vertical-align: top;
531                 padding-right: 10px;
532             }
533             .survey_table .others {
534                 font-style: italic;
535             }
536             </style>
537         ";
538
539         $response_limit = 10;
540         $table = '';
541         foreach ($data as $question => $responses) {
542             $question_row = '<tr><th colspan="2" class="question">' . $question . '</th></tr>';
543             $response_rows = '';
544             arsort($responses);
545             $response_count = 0;
546             $total_responses = count($responses);
547             foreach ($responses as $response => $votes) {
548                 $response_rows .= '<tr><td class="score">' . intval($votes) . '</td><td class="response">' . stripslashes($response) . '</td></tr>';
549                 if ( ++$response_count >= $response_limit && $response_count < $total_responses) {
550                     $response_rows .= '<tr><td class="score others">' . intval($total_responses - $response_limit) . '</td><td class="response others">more "other" responses</td></tr>';
551                     break;
552                 }
553             }
554             $table .= $question_row . $response_rows;
555         }
556         echo $styles;
557         echo '<table class="survey_table">' . $table . '</table>';
558         echo '<p class="textright"><a class="button button-primary" href="' . add_query_arg('wksurvey','download',admin_url()) . '">Download Results as CSV</a></p>';
559     }
560
561     private function download() {
562         $data = get_option(self::DATA_SETTING_NAME);
563         $table = '';
564
565         header('Content-type: text/csv; charset=UTF-8');
566         header('Content-Disposition: attachment; filename="webkit_nightly_survey.csv"');
567         header('Content-Description: Delivered by webkit.org');
568         header('Cache-Control: maxage=1');
569         header('Pragma: public');
570
571         foreach ($data as $question => $responses) {
572             $question_row = 'Score,' . $question . "\n";
573             $response_rows = '';
574             arsort($responses);
575             foreach ($responses as $response => $votes) {
576                 $response_rows .= intval($votes) . ',' . stripslashes($response) . "\n";
577             }
578             $table .= $question_row . $response_rows . "\n";
579         }
580         echo $table;
581
582         exit;
583     }
584
585     public function process() {
586         if ( empty($_POST) ) return;
587
588         if ( ! wp_verify_nonce($_POST['_nonce'], self::SURVEY_FILENAME) )
589             wp_die('Invalid WebKit Nightly Survey submission.');
590
591         $score = $data = get_option(self::DATA_SETTING_NAME);
592         $Survey = self::survey();
593         foreach ($Survey as $id => $SurveyQuestion) {
594             if ( ! isset($score[ $SurveyQuestion->question ]) )
595                 $score[ $SurveyQuestion->question ] = array();
596             $response = $_POST['questions'][ $id ];
597             $answer = empty($SurveyQuestion->responses[ $response ]) ? $response : $SurveyQuestion->responses[ $response ];
598             if ($answer == 'Other:')
599                 $answer = $_POST['other'][ $id ];
600             $score[ $SurveyQuestion->question ][ $answer ]++;
601         }
602
603         if ( $data === false ) {
604             $deprecated = null;
605             $autoload = 'no';
606             add_option(self::DATA_SETTING_NAME, $score, $deprecated, $autoload);
607         } else {
608             update_option(self::DATA_SETTING_NAME, $score);
609         }
610
611         $httponly = false;
612         $secure = false;
613         setcookie(self::cookie_name(), 1, time() + YEAR_IN_SECONDS, '/', WP_HOST, $secure, $httponly );
614         $_COOKIE[ self::cookie_name() ] = 1;
615     }
616
617     public static function responded() {
618         return isset($_COOKIE[ self::cookie_name() ]);
619     }
620
621     private static function cookie_name() {
622         return self::COOKIE_PREFIX . COOKIEHASH;
623     }
624
625     public static function survey_file() {
626         return dirname(__FILE__) . '/' . self::SURVEY_FILENAME;
627     }
628
629     public static function form_nonce() {
630         return '<input type="hidden" name="_nonce" value="' . wp_create_nonce( self::SURVEY_FILENAME ) . '">';
631     }
632
633     public static function survey () {
634         $survey_json = file_get_contents(self::survey_file());
635         $Decoded = json_decode($survey_json);
636         return isset($Decoded->survey) ? $Decoded->survey : array();
637     }
638
639 }