Reduce Safari Technology Preview Release Notes posts shown on homepage
[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     $title = str_replace(": ", ": <br>", $title);
105     return $title;
106 });
107
108 // For RSS feeds, convert relative URIs to absolute
109 add_filter('the_content', function($content) {
110     if (!is_feed()) return $content;
111     $base = trailingslashit(get_site_url());
112     $content = preg_replace('/<a([^>]*) href="\/([^"]*)"/', '<a$1 href="' . $base . '$2"', $content);
113     $content = preg_replace('/<img([^>]*) src="\/([^"]*)"/', '<img$1 src="' . $base . '$2"', $content);
114     return $content;
115 });
116
117 add_action('wp_head', function () {
118     if (!is_single()) return;
119
120     $style = get_post_meta(get_the_ID(), 'style', true);
121     if (!empty($style))
122         echo '<style type="text/css">' . $style . '</style>';
123
124     $script = get_post_meta(get_the_ID(), 'script', true);
125     if (!empty($script))
126         echo '<script type="text/javascript">' . $script . '</script>';
127 });
128
129 add_action('the_post', function($post) {
130     global $pages;
131
132     if (!(is_single() || is_page())) return;
133
134     $content = $post->post_content;
135     if (strpos($content, 'abovetitle') === false) return;
136     if (strpos($content, '<img') !== 0) return;
137
138     $post->post_title_img = substr($content, 0, strpos($content, ">\n") + 3);
139     $post->post_content = str_replace($post->post_title_img, '', $content);
140     $pages = array($post->post_content);
141 });
142
143 add_action('the_post', function($post) {
144     global $pages;
145
146     if (!(is_single() || is_page())) return;
147
148     $foreword = get_post_meta(get_the_ID(), 'foreword', true);
149     if ( ! $foreword ) return;
150
151     $content = $post->post_content;
152     // Transform Markdown
153     $Markdown = WPCom_Markdown::get_instance();
154     $foreword = wp_unslash( $Markdown->transform($foreword) );
155
156     $post->post_content = '<div class="foreword">' . $foreword . '</div>' . $content;
157     $pages = array($post->post_content);
158 });
159
160 function before_the_title() {
161     $post = get_post();
162
163     if ( isset($post->post_title_img) )
164         echo wp_make_content_images_responsive($post->post_title_img);
165 }
166
167 // Hide category 41: Legacy from archives
168 add_filter('pre_get_posts', function ($query) {
169     if ( $query->is_home() )
170         $query->set('cat', '-41');
171     return $query;
172 });
173
174 add_filter( 'get_the_excerpt', function( $excerpt ) {
175     $sentences = preg_split( '/(\.|!|\?)\s/', $excerpt, 2, PREG_SPLIT_DELIM_CAPTURE );
176
177     // if ( empty($sentences[1]) )
178     //     $sentences[1] = '&hellip;';
179
180     return $sentences[0] . $sentences[1];
181
182 });
183
184 function show_404_page() {
185     status_header(404);
186     return include( get_query_template( '404' ) );
187 }
188
189 include('widgets/post.php');
190 include('widgets/icon.php');
191 include('widgets/twitter.php');
192 include('widgets/page.php');
193
194 function table_of_contents() {
195     if ( class_exists('WebKitTableOfContents') )
196         WebKitTableOfContents::markup();
197 }
198
199 function has_table_of_contents() {
200     if ( class_exists('WebKitTableOfContents') )
201         return WebKitTableOfContents::hasIndex();
202 }
203
204 function table_of_contents_index( $content, $post_id ) {
205     if ( ! class_exists('WebKitTableOfContents') )
206         return $content;
207     $content = WebKitTableOfContents::parse($content);
208     WebKitTableOfContents::wp_insert_post($post_id);
209     return $content;
210 }
211
212 function is_super_cache_enabled() {
213     global $super_cache_enabled;
214     return (isset($super_cache_enabled) && true === $super_cache_enabled);
215 }
216
217 function tag_post_image_luminance( $post_id ) {
218     $threshold = 128;
219     $tags = array();
220
221     // Get the image data
222     $image_src = wp_get_attachment_image_src( get_post_thumbnail_id($post_id), 'small' );
223     $image_url = $image_src[0];
224
225     if ( empty($image_url) ) return $post_id;
226
227     // detect luminence value
228     $luminance = calculate_image_luminance($image_url);
229     $tags = wp_get_post_tags($post_id, array('fields' => 'names'));
230
231     if ( $luminance < $threshold )
232         $tags[] = 'dark';
233     elseif ( false !== ( $key = array_search('dark', $messages) ) )
234         unset($tags[ $key ]);
235
236     // Set a tag class
237     if ( ! empty($tags) )
238         wp_set_post_tags( $post_id, $tags, true );
239
240     return $post_id;
241 }
242
243 function calculate_image_luminance($image_url) {
244     if (!function_exists('ImageCreateFromString'))
245         return 1;
246
247     // Get original image dimensions
248     $size = getimagesize($image_url);
249
250     // Create image resource from source image
251     $image = ImageCreateFromString(file_get_contents($image_url));
252
253     // Allocate image resource
254     $sample = ImageCreateTrueColor(1, 1);
255
256     // Flood fill with a white background (to properly calculate luminance of PNGs with alpha)
257     ImageFill($sample , 0, 0, ImageColorAllocate($sample, 255, 255, 255));
258
259     // Downsample to 1x1 image
260     ImageCopyResampled($sample, $image, 0, 0, 0, 0, 1, 1, $size[0], $size[1]);
261
262     // Get the RGB value of the pixel
263     $rgb   = ImageColorAt($sample, 0, 0);
264     $red   = ($rgb >> 16) & 0xFF;
265     $green = ($rgb >> 8) & 0xFF;
266     $blue  = $rgb & 0xFF;
267
268     // Calculate relative luminance value (https://en.wikipedia.org/wiki/Relative_luminance)
269     return ( 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue);
270 }
271
272 function html_select_options(array $list, $selected = null, $values = false, $append = false) {
273         if ( ! is_array($list) ) return '';
274
275         $_ = '';
276
277         // Append the options if the selected value doesn't exist
278         if ( ( ! in_array($selected, $list) && ! isset($list[ $selected ])) && $append )
279             $_ .= '<option value="' . esc_attr($selected) . '">' .esc_html($selected) . '</option>';
280
281         foreach ( $list as $value => $text ) {
282
283             $value_attr = $selected_attr = '';
284
285             if ( $values ) $value_attr = ' value="' . esc_attr($value) . '"';
286             if ( ( $values && (string)$value === (string)$selected)
287                 || ( ! $values && (string)$text === (string)$selected ) )
288                     $selected_attr = ' selected="selected"';
289
290             if ( is_array($text) ) {
291                 $label = $value;
292                 $_ .= '<optgroup label="' . esc_attr($label) . '">';
293                 $_ .= self::menuoptions($text, $selected, $values);
294                 $_ .= '</optgroup>';
295                 continue;
296             } else $_ .= "<option$value_attr$selected_attr>$text</option>";
297
298         }
299         return $_;
300     }
301
302 add_filter('save_post', 'tag_post_image_luminance');
303
304 add_filter('next_post_link', function ( $format ) {
305     return str_replace('href=', 'class="page-numbers next-post" href=', $format);
306 });
307 add_filter('previous_post_link', function ( $format ) {
308     return str_replace('href=', 'class="page-numbers prev-post" href=', $format);
309 });
310
311 // Queue global scripts
312 add_action( 'wp_enqueue_scripts', function () {
313     wp_register_script(
314         'theme-global',
315         get_stylesheet_directory_uri() . '/scripts/global.js',
316         false,
317         '1.0',
318         true
319     );
320
321     wp_enqueue_script( 'theme-global' );
322
323 } );
324
325 class Responsive_Toggle_Walker_Nav_Menu extends Walker_Nav_Menu {
326
327     private $toggleid = null;
328
329     public function start_lvl( &$output, $depth = 0, $args = array() ) {
330         $output .= "\n" . str_repeat("\t", $depth);
331         $classes = array("sub-menu");
332         if ( 0 == $depth ) {
333             $classes[] = "sub-menu-layer";
334         }
335         $id = ( 0 == $depth ) ? " id=\"sub-menu-for-{$this->toggleid}\"" : '';
336         $class_names = esc_attr(join( ' ', $classes ));
337         $output .= "<ul class=\"$class_names\" role=\"menu\"$id>\n";
338     }
339
340     public function end_lvl( &$output, $depth = 0, $args = array() ) {
341         $indent = str_repeat("\t", $depth);
342         $output .= "$indent</ul>\n";
343     }
344
345     public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
346
347         $before = $args->link_before;
348         $after = $args->link_after;
349
350         if ( in_array('menu-item-has-children', $item->classes) && 0 == $depth ) {
351             $this->toggleid = $item->ID;
352             $args->before .= "<input type=\"checkbox\" id=\"toggle-{$item->ID}\" class=\"menu-toggle\" />";
353             $args->link_before = "<label for=\"toggle-{$item->ID}\" class=\"label-toggle\">" . $args->link_before;
354             $args->link_after .= "</label>";
355             $item->url = '#nav-sub-menu';
356         } elseif ( in_array('menu-item-has-children', $item->classes) && 1 == $depth ) {
357             // $item->role = "presentation";
358         } else $toggleid = null;
359
360         $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
361
362         $classes = empty( $item->classes ) ? array() : (array) $item->classes;
363         $classes[] = 'menu-item-' . $item->ID;
364
365         $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
366         $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
367
368         $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args, $depth );
369         $id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
370
371         $output .= $indent . '<li' . $id . $class_names . '>';
372
373         $atts = array();
374         $atts['title']  = ! empty( $item->attr_title ) ? $item->attr_title : '';
375         $atts['target'] = ! empty( $item->target )     ? $item->target     : '';
376         $atts['rel']    = ! empty( $item->xfn )        ? $item->xfn        : '';
377         $atts['href']   = ! empty( $item->url )        ? $item->url        : '';
378         $atts['role']   = ! empty( $item->role )       ? $item->role       : '';
379
380         if ( in_array('menu-item-has-children', $item->classes) && 0 == $depth ) {
381             $atts['aria-haspopup'] = "true";
382             $atts['aria-owns'] = 'sub-menu-for-' . $item->ID;
383             $atts['aria-controls'] = 'sub-menu-for-' . $item->ID;
384             $atts['aria-expanded'] = 'true';
385         }
386
387         $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );
388
389         $attributes = '';
390         foreach ( $atts as $attr => $value ) {
391             if ( ! empty( $value ) ) {
392                 $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
393                 $attributes .= ' ' . $attr . '="' . $value . '"';
394             }
395         }
396
397         $item_output = $args->before;
398         $item_output .= '<a'. $attributes .'>';
399         $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
400         $item_output .= '</a>';
401         $item_output .= $args->after;
402
403         $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
404
405         $args->link_before = $before;
406         $args->link_after = $after;
407     }
408
409 }
410
411 class Front_Page_Posts {
412
413     private static $object;     // Singleton instance
414
415     private static $wp_query;   // WP_Query instance
416
417     public static function &object () {
418         if ( ! self::$object instanceof self )
419             self::$object = new self;
420
421         if ( empty(self::$wp_query) )
422             self::$wp_query = new WP_Query(array('post_type' => 'post', 'posts_per_page' => 16));
423
424         return self::$object;
425     }
426
427     public static function WP_Query() {
428         return self::$wp_query;
429     }
430
431 }
432
433 class WebKit_Nightly_Survey {
434
435     const COOKIE_PREFIX = 'webkitnightlysurvey_';
436     const DATA_SETTING_NAME = 'webkit_nightly_survey_data';
437     const SURVEY_FILENAME = 'survey.json';
438
439     public function add_widget() {
440
441         wp_add_dashboard_widget(
442             'webkit_nightly_survey_results', // Widget slug
443             'WebKit Nightly Survey Results', // Title
444             array($this, 'display_widget')   // Display function
445         );
446
447         if (isset($_GET['wksurvey']) && $_GET['wksurvey'] == 'download')
448             $this->download() && exit;
449
450     }
451
452     public function display_widget() {
453         $data = get_option(self::DATA_SETTING_NAME);
454         $styles = "
455             <style>
456             .survey_table .question {
457                 text-align: left;
458             }
459             .survey_table .score {
460                 font-size: 15px;
461                 min-width: 30px;
462                 text-align: right;
463                 vertical-align: top;
464                 padding-right: 10px;
465             }
466             .survey_table .others {
467                 font-style: italic;
468             }
469             </style>
470         ";
471
472         $response_limit = 10;
473         $table = '';
474         foreach ($data as $question => $responses) {
475             $question_row = '<tr><th colspan="2" class="question">' . $question . '</th></tr>';
476             $response_rows = '';
477             arsort($responses);
478             $response_count = 0;
479             $total_responses = count($responses);
480             foreach ($responses as $response => $votes) {
481                 $response_rows .= '<tr><td class="score">' . intval($votes) . '</td><td class="response">' . stripslashes($response) . '</td></tr>';
482                 if ( ++$response_count >= $response_limit && $response_count < $total_responses) {
483                     $response_rows .= '<tr><td class="score others">' . intval($total_responses - $response_limit) . '</td><td class="response others">more "other" responses</td></tr>';
484                     break;
485                 }
486             }
487             $table .= $question_row . $response_rows;
488         }
489         echo $styles;
490         echo '<table class="survey_table">' . $table . '</table>';
491         echo '<p class="textright"><a class="button button-primary" href="' . add_query_arg('wksurvey','download',admin_url()) . '">Download Results as CSV</a></p>';
492     }
493
494     private function download() {
495         $data = get_option(self::DATA_SETTING_NAME);
496         $table = '';
497
498         header('Content-type: text/csv; charset=UTF-8');
499         header('Content-Disposition: attachment; filename="webkit_nightly_survey.csv"');
500         header('Content-Description: Delivered by webkit.org');
501         header('Cache-Control: maxage=1');
502         header('Pragma: public');
503
504         foreach ($data as $question => $responses) {
505             $question_row = 'Score,' . $question . "\n";
506             $response_rows = '';
507             arsort($responses);
508             foreach ($responses as $response => $votes) {
509                 $response_rows .= intval($votes) . ',' . stripslashes($response) . "\n";
510             }
511             $table .= $question_row . $response_rows . "\n";
512         }
513         echo $table;
514
515         exit;
516     }
517
518     public function process() {
519         if ( empty($_POST) ) return;
520
521         if ( ! wp_verify_nonce($_POST['_nonce'], self::SURVEY_FILENAME) )
522             wp_die('Invalid WebKit Nightly Survey submission.');
523
524         $score = $data = get_option(self::DATA_SETTING_NAME);
525         $Survey = self::survey();
526         foreach ($Survey as $id => $SurveyQuestion) {
527             if ( ! isset($score[ $SurveyQuestion->question ]) )
528                 $score[ $SurveyQuestion->question ] = array();
529             $response = $_POST['questions'][ $id ];
530             $answer = empty($SurveyQuestion->responses[ $response ]) ? $response : $SurveyQuestion->responses[ $response ];
531             if ($answer == 'Other:')
532                 $answer = $_POST['other'][ $id ];
533             $score[ $SurveyQuestion->question ][ $answer ]++;
534         }
535
536         if ( $data === false ) {
537             $deprecated = null;
538             $autoload = 'no';
539             add_option(self::DATA_SETTING_NAME, $score, $deprecated, $autoload);
540         } else {
541             update_option(self::DATA_SETTING_NAME, $score);
542         }
543         
544         $httponly = false;
545         $secure = false;
546         setcookie(self::cookie_name(), 1, time() + YEAR_IN_SECONDS, '/', WP_HOST, $secure, $httponly );
547         $_COOKIE[ self::cookie_name() ] = 1;
548     }
549
550     public static function responded() {
551         return isset($_COOKIE[ self::cookie_name() ]);
552     }
553
554     private static function cookie_name() {
555         return self::COOKIE_PREFIX . COOKIEHASH;
556     }
557
558     public static function survey_file() {
559         return dirname(__FILE__) . '/' . self::SURVEY_FILENAME;
560     }
561
562     public static function form_nonce() {
563         return '<input type="hidden" name="_nonce" value="' . wp_create_nonce( self::SURVEY_FILENAME ) . '">';
564     }
565
566     public static function survey () {
567         $survey_json = file_get_contents(self::survey_file());
568         $Decoded = json_decode($survey_json);
569         return isset($Decoded->survey) ? $Decoded->survey : array();
570     }
571
572 }