b.org: Implement rich results for blog posts
[blender-org.git] / themes / bthree / functions.php
1 <?php
2
3 // Disable XML-RPC.
4 add_filter('xmlrpc_enabled', '__return_false');
5
6
7 // Menu Registration.
8 add_action('init', 'register_menus');
9 function register_menus() {
10     register_nav_menus(
11         array(
12             'header-menu' => __('Top Header Navigation'))
13     );
14 }
15
16
17 // Sidebar Registration.
18 if (function_exists('register_sidebar')) {
19     register_sidebar(array( 'name' => 'Banner Widgets', 'id' => 'banner-widgets'));
20 }
21
22
23 // Ignore certain items.
24 // Set via "Hide from Navigation" under Page Layout when editing a page.
25 function ids_excluded_from_menu() {
26     $post_ids = array();
27     $args = array(
28         'post_type' => 'page',
29         'meta_key' => 'hide_from_navigation',
30         'meta_value' => true,
31         'nopaging' => true
32     );
33
34     $pages = new WP_Query($args);
35
36     if ($pages){
37         $post_ids = wp_list_pluck( $pages->posts, 'ID' ); // comes out as array.
38         $post_ids = implode(",", $post_ids); // makes a simple list split by comma 1,2,3
39     }
40
41     return $post_ids;
42 }
43
44
45 // Second/Third Navigation Headers
46 function nav_tree($secondary_menu = false) {
47     global $post;
48
49     // Get an array of all the parents for the current page
50     $ancestors          = array_reverse(get_ancestors($post->ID, 'page'));
51     $ancestor_oldest    = ($ancestors ? $ancestors[0] : 0);
52     $show_secondary_nav = ($secondary_menu and has_nav_menu('secondary-menu'));
53
54     // Add the current page to the array, to get siblings (in case of top pages)
55     array_push($ancestors, $post->ID);
56
57     $excluded_ids = ids_excluded_from_menu();
58
59     $i = 0;
60
61     // For each ancestor, build a header nav
62     foreach($ancestors as $ancestor):
63         $plist = wp_list_pages('title_li=&child_of=' . $ancestor .'&depth=1&echo=0&exclude=' . $excluded_ids);
64
65         /* Show parent link if:
66          * The current ancestor is the oldest/top-most level. (e.g. only show on 2nd level nav, not 3rd level)
67          * There are less than 2 ancestors.
68          * The current ancestor is not in the excluded ids list,
69          * and if it's the first navigation bar (not tertiary navbar). */
70         $show_parent = ((($ancestor == $ancestor_oldest) or (count($ancestors) < 2)) and !in_array($ancestor, array($excluded_ids))) and ($i < 1);
71
72         if($plist or $show_secondary_nav) { ?>
73             <nav class="navbar navbar-secondary" role="navigation">
74                 <div class="container">
75                     <ul class="navbar-nav">
76                         <?php if($show_secondary_nav):
77                             wp_nav_menu(
78                                 array('theme_location' => 'secondary-menu',
79                                       'container' => '',
80                                       'items_wrap' => '%3$s')
81                             ); ?>
82                         <?php
83                         elseif ($show_parent):
84                         ?>
85                         <li class="nav-item nav-parent">
86                             <a class="nav-link" href="<?=get_the_permalink($ancestor_oldest)?>">
87                                 <?=get_the_title($ancestor)?>
88                             </a>
89                         </li>
90                         <li class="nav-item nav-separator">
91                             <i class="i-chevron-right"></i>
92                         </li>
93                         <?php endif; ?>
94                         <?php echo $plist; ?>
95                     </ul>
96                 </div>
97             </nav>
98         <?php }
99
100         $i++;
101
102     endforeach;
103 }
104
105
106 // Override Editor Options
107 add_filter('tiny_mce_before_init', 'override_mce_options');
108 function override_mce_options($initArray) {
109     $opts = '*[*]';
110     $initArray['valid_elements'] = $opts;
111     $initArray['extended_valid_elements'] = $opts;
112     return $initArray;
113 }
114
115
116 // Page/Post Thumbnails
117 add_theme_support('post-thumbnails');
118
119 // Custom size for square thumbnails, also cropped from the center.
120 add_image_size('square', 200, 200, array('center', 'center'));
121
122 // 16 by 9. (25% of FHD) For cards like in homepage news, features, training.
123 add_image_size('thumbnail_card', 480, 270, array('center', 'center'));
124
125 // Default is the listing size, cropped from the center
126 set_post_thumbnail_size(480, 270, array('center', 'center'));
127
128
129 // Search Input Form
130 add_filter( 'get_search_form', 'search_form' );
131 function search_form( $form ) {
132     $form = '
133         <section class="blog-search">
134             <form role="search" method="get" id="search-form" action="' . home_url( '/' ) . '" >
135                 <label class="screen-reader-text" for="s">' . __('',  'domain') . '</label>
136                 <input type="search" value="' . get_search_query() . '" name="s" id="s" placeholder="Search..." />
137             </form>
138         </section>';
139     return $form;
140 }
141
142
143 // Get Title
144 function get_site_title($description = false){
145     $title = '';
146     if (!is_front_page()):
147         $title .= wp_title('', false,'') . ' &mdash; ';
148     endif;
149
150     $title .= get_bloginfo('name');
151
152     if (is_front_page() or $description == true):
153         $title .= ' - ' . get_bloginfo('description');
154     endif;
155
156     return $title;
157 }
158
159
160 // Get page/post description to use in opengraph
161 function get_shareable_description(){
162     $description = get_bloginfo('description');
163
164     if (has_excerpt()):
165         $description = get_the_excerpt();
166     endif;
167
168     return $description;
169 }
170
171
172 // Get page/post image to use in opengraph
173 function get_shareable_image(){
174     $image = get_template_directory_uri() . '/assets_shared/images/blender_the_freedom_to_create_01.jpg';
175     $custom_image = get_field('social_media_image');
176
177     if ($custom_image):
178         $image = $custom_image['url'];
179     elseif (has_post_thumbnail()):
180         $image = get_the_post_thumbnail_url();
181     endif;
182
183     return $image;
184 }
185
186 /**
187  * Filter the except length to 20 words.
188  *
189  * @param int $length Excerpt length.
190  * @return int (Maybe) modified excerpt length.
191  */
192 add_filter( 'excerpt_length', 'wpdocs_custom_excerpt_length', 999);
193 function wpdocs_custom_excerpt_length( $length ) {
194     return 20;
195 }
196
197
198 // Add Excerpts Support to Pages
199 add_action( 'init', 'pages_excerpt_add' );
200 function pages_excerpt_add() {
201     add_post_type_support( 'page', 'excerpt' );
202 }
203
204
205 // Advanced Custom Fields Options Page
206 if( function_exists('acf_add_options_page') ) {
207     acf_add_options_page(array(
208         'page_title'    => 'Sitewide Settings',
209         'menu_title'    => 'blender.org',
210         'menu_slug'     => 'custom-settings'
211     ));
212 }
213
214
215 /* Get the mirror list from the Site-wide Settings page (the list is built with ACF) */
216 function get_mirrors($skip_mirror = null) {
217     $mirrors = get_field('mirrors', 'option');
218     $mirrors_active = array();
219
220     if (!$mirrors) {
221         trigger_error('No mirrors found. Check Sitewide Settings page config.', E_USER_ERROR);
222     }
223
224     foreach ($mirrors as $mirror) {
225         /* Only add a mirror to hte list if:
226          * * Mirror is enabled.
227          * * The URL is different than the mirror picked by GeoIp2 ($skip_mirror).
228          * * If it's not already on the list.
229          */
230
231         if (!$mirror['active']){
232             continue;
233         }
234
235         if ($mirror['url'] == $skip_mirror['url']){
236             continue;
237         }
238
239         if (!in_array($mirror, $mirrors_active)){
240             array_push($mirrors_active, $mirror);
241         }
242     }
243
244     return $mirrors_active;
245 }
246
247
248
249 /**
250  * Select the closest mirror to the user, based on their location.
251  *
252  * We look up the COUNTRY_CODE, if there's a match with one of the mirrors we add it to the list. If we don't find
253  * a match we try REGION_CODE. If multiple mirror exist, select one randomly.
254  *
255  * @return array of the closest mirror's properties.
256  */
257 function get_mirror($code_country, $code_continent) {
258     $mirrors_all = get_mirrors();
259     $mirrors = array();
260     $mirrors_in_country = array();
261
262     foreach ($mirrors_all as $mirror) {
263         // Append mirrors that match the user's country.
264         if ($code_country == $mirror['code_country']['value']) {
265             array_push($mirrors_in_country, $mirror);
266         }
267         // Append mirrors that match the continent only if we don't have a match with the user's country.
268         elseif (empty($mirrors_in_country) && $code_continent == $mirror['code_continent']) {
269             array_push($mirrors, $mirror);
270         }
271     }
272
273     if (!empty($mirrors_in_country)) {
274         // Replace any continent mirrors with the one from our country only
275         $mirrors = $mirrors_in_country;
276     } elseif (count($mirrors) == 0) {
277         // If nothing was matched, select all mirrors available.
278         $mirrors = $mirrors_all;
279     }
280
281     $rand_index = array_rand($mirrors);
282     return $mirrors[$rand_index];
283 }
284
285
286 /* Redirect for downloads (Blender, Asset Bundles) to use mirrors. 
287  * Needs to refresh the cache. Save Permalinks, and flush cache with WC3. */
288 add_action('init', 'blender_download_mirror');
289 function blender_download_mirror() {
290     // Optionally use ^download/Blender\d\.\d+\S*/blender?
291     add_rewrite_rule('^download/release/Blender?', 'index.php?pagename=thanks', 'top');    
292     add_rewrite_rule('^download/demo/bundles/bundles?', 'index.php?pagename=thanks', 'top');
293 }
294
295
296 function updateDownloadCount($build_name, $mirror_url)
297 {
298
299     /*
300     This function requires the following table to exist:
301     CREATE TABLE `blender_downloads` (
302       `id` int(11) NOT NULL AUTO_INCREMENT,
303       `build_name` varchar(64) DEFAULT NULL,
304       `downloads` int(11) DEFAULT NULL,
305       `date` date DEFAULT NULL,
306       `mirror` varchar(128) DEFAULT NULL,
307       PRIMARY KEY (`id`)
308     ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
309     */
310
311     global $wpdb;
312     $mirror_hostname = parse_url($mirror_url)['host'];
313     $date = date('Y-m-d');
314     // Build filter for a combination of build_name, date and mirror
315     $result = $wpdb->get_results(
316         $wpdb->prepare(
317                 "
318                 SELECT * FROM blender_downloads
319                 WHERE build_name = %s
320                 AND date = %s
321                 AND mirror = %s
322                 ",
323                 $build_name,
324                 $date,
325                 $mirror_hostname
326            ));
327
328     // If no combination of build_name, date and mirror is found, insert row
329     if(empty($result)) {
330         $wpdb->query(
331            $wpdb->prepare(
332                 "
333                 INSERT INTO blender_downloads
334                 ( build_name, downloads, date, mirror )
335                 VALUES ( %s, %d, %s, %s )
336                 ",
337                 $build_name,
338                 1,
339                 $date,
340                 $mirror_hostname
341            )
342         );
343     } else {
344         // If a combination of build_name, date and mirror is found, update the downloads count by one
345         $wpdb->query(
346            $wpdb->prepare(
347                 "
348                 UPDATE blender_downloads
349                 SET downloads = downloads + 1
350                 WHERE build_name = %s
351                 AND date = %s
352                 AND mirror = %s
353                 ",
354                 $build_name,
355                 $date,
356                 $mirror_hostname
357             )
358         );
359     }
360 }
361
362
363 // General Tools
364 function slugify($string){
365     $slugified = str_replace("/", "-", $string);
366     $slugified = trim(preg_replace("/\\s+/", " ", $slugified));
367
368     // Replace spaces with dashes.
369     $slugified = strtolower(str_replace(" ", '-', $slugified));
370
371     // Remove special characters.
372     $slugified = preg_replace("/[^A-Za-z0-9\-]/", "", $slugified);
373
374     // Remove recurring dashes (e.g. when "2000 - 2010" converts to "2000---2010")
375     $slugified = preg_replace("/([-])\\1+/", "$1", $slugified);
376
377     return $slugified;
378 };
379
380
381 // Use slug as class on body for page-wide styling
382 function get_class_body(){
383     global $post;
384
385     $slug = 'generic';
386
387     if ($post):
388         $slug = $post->post_name;
389         $header_large = (get_field('header_size') ? ' has-header' : '');
390     endif;
391
392     $class_body = 'ps-' . $slug . $header_large;
393
394     return $class_body;
395 }
396
397
398
399 function load_scripts() {
400     global $post;
401
402     if( is_page() || is_single() )
403     {
404         switch($post->post_name) // post_name is the post slug which is more consistent for matching to
405         {
406             case 'quest':
407             //  wp_enqueue_script('quest', get_template_directory_uri() . '/assets/js/generated/page-quest.js', array('jquery'), '', true);
408                 break;
409         }
410     }
411 }
412
413 // add_action('wp_enqueue_scripts', 'load_scripts');
414
415 // Find out if we are in the Code Blog based on the active theme (see style.css)
416 function is_code_blog(){
417     return get_stylesheet() == 'bthree-code';
418 }
419
420 function add_nav_menu_link_class( $atts, $item, $args ) {
421     $atts['class'] = 'nav-link';
422     return $atts;
423 }
424
425 add_filter( 'nav_menu_link_attributes', 'add_nav_menu_link_class', 10, 3);
426
427
428 // Training & Courses.
429 function acf_load_training_trainer_choices( $field ) {
430     global $post;
431
432     if(!is_object($post))
433         return;
434
435     // Get the parent of courses (must use the Training Homepage template).
436     $parent_id = wp_get_post_parent_id($post->ID);
437     $field['choices'] = array();
438
439     if( have_rows('trainers', $parent_id) ) {
440             while( have_rows('trainers', $parent_id) ) {
441
442                     the_row();
443
444                     $label = get_sub_field('trainer_name');
445                     $value = get_row_index();
446
447                     $field['choices'][$value] = $label; // append to choices
448             }
449     }
450     return $field;
451 }
452
453 add_filter('acf/load_field/name=course_trainer', 'acf_load_training_trainer_choices');
454
455
456 function acf_load_training_status_choices( $field ) {
457     global $post;
458
459     if(!is_object($post))
460         return;
461
462     // Get the parent of courses (must use the Training Homepage template).
463     $parent_id = wp_get_post_parent_id($post->ID);
464     $field['choices'] = array();
465
466     if( have_rows('statuses', $parent_id) ) {
467             while( have_rows('statuses', $parent_id) ) {
468
469                     the_row();
470
471                     $label = get_sub_field('status_name');
472                     $value = get_row_index();
473
474                     $field['choices'][$value] = $label; // append to choices
475             }
476     }
477     return $field;
478 }
479
480 add_filter('acf/load_field/name=course_status', 'acf_load_training_status_choices');
481
482
483 function acf_load_training_price_choices( $field ) {
484     global $post;
485
486     if(!is_object($post))
487         return;
488
489     // Get the parent of courses (must use the Training Homepage template).
490     $parent_id = wp_get_post_parent_id($post->ID);
491     $field['choices'] = array();
492
493     if( have_rows('prices', $parent_id) ) {
494         while( have_rows('prices', $parent_id) ) {
495
496             the_row();
497
498             $label = get_sub_field('price_amount');
499             $value = get_row_index();
500
501             $field['choices'][$value] = $label; // append to choices
502         }
503     }
504     return $field;
505 }
506
507 add_filter('acf/load_field/name=course_price_amount', 'acf_load_training_price_choices');
508
509
510 // Expand the list of allowed MIME types for upload.
511 function custom_mime_types($mimes) {
512
513     // Use 'application/x-gzip' for compressed .blend files, or 'x-blender' for uncompressed.
514     $mimes['blend'] = 'application/x-gzip';
515
516     return $mimes;
517 }
518
519 add_filter( 'upload_mimes', 'custom_mime_types' );
520
521
522 // UI Components.
523 function show_card_label($label){
524     if ($label):
525         echo '<em class="d-block text-info">' . $label . '</em>';
526     endif;
527 }
528
529
530 // Remove "Website" field from comments form.
531 function comments_remove_website_field($fields) {
532    unset($fields['url']);
533    return $fields;
534 }
535 add_filter('comment_form_default_fields', 'comments_remove_website_field');
536
537
538 /* Whether to show an image or video tags.
539  * Used in Cards block. */
540 function mediaThumbnail($filename, $name) {
541         $extension = pathinfo($filename)['extension'];
542         $video_ext = ['mp4', 'mov', 'webm'];
543
544         if (in_array($extension, $video_ext)) {
545                 echo '
546                         <video loop autoplay muted>
547                                 <source src="' . $filename . '" type="video/' . $extension . '">
548                         </video>'
549                 ;
550         } else {
551                 echo '<img src="' . $filename . '" alt="' . $name . '"/>';
552         }
553 }
554
555
556 // Shortcodes.
557 function b3_shortcode_blender_version() {
558
559     $label = get_field('blender_version', 'option');
560     return $label;
561 }
562
563 add_shortcode('blender_version', 'b3_shortcode_blender_version');
564
565
566 // Custom styling for editor.
567 add_action( 'after_setup_theme', 'b3_editor_css' );
568
569 function b3_editor_css(){
570     add_theme_support( 'editor-styles' );
571     add_editor_style( 'style-editor.css' );
572 }
573
574
575 // Javascript to customize blocks.
576 function b3_enqueue() {
577         wp_enqueue_script(
578                 'b3-script',
579                 get_template_directory_uri() . '/assets/js/bthree.js',
580                 array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post')
581         );
582 }
583 add_action( 'enqueue_block_editor_assets', 'b3_enqueue' );
584
585
586 // Custom Blocks Category
587 function b3_block_category( $categories, $post ) {
588     return array_merge(
589         $categories,
590         array(
591             array(
592                 'slug' => 'b3',
593                 'title' => __( 'Blender.org Blocks', 'b3' ),
594             ),
595         )
596     );
597 }
598 add_filter( 'block_categories', 'b3_block_category', 10, 2);
599
600
601
602
603 // Custom ACF Blocks
604 function register_acf_block_types() {
605     acf_register_block_type(array(
606             'name'              => 'word-cloud',
607             'title'             => __('Word Cloud'),
608             'description'       => __('A centered list of words or links.'),
609             'render_template'   => '/blocks/word-cloud.php',
610             'category'          => 'b3',
611             'icon'              => 'admin-links',
612             'keywords'          => array( 'word-cloud', 'links', 'cloud' ),
613     ));
614
615     acf_register_block_type(array(
616                 'name'              => 'cards',
617                 'title'             => __('Cards'),
618                 'description'       => __('A list of card elements.'),
619                 'render_template'   => '/blocks/cards.php',
620                 'category'          => 'b3',
621                 'icon'              => 'grid-view',
622                 'keywords'          => array( 'cards', ),
623     ));
624
625     acf_register_block_type(array(
626                 'name'              => 'pages-list',
627                 'title'             => __('List of Pages'),
628                 'description'       => __('List pages as cards.'),
629                 'render_template'   => '/blocks/pages-list.php',
630                 'category'          => 'b3',
631                 'icon'              => 'editor-paragraph',
632                 'keywords'          => array( 'pages', 'list', 'card'),
633     ));
634
635     acf_register_block_type(array(
636                 'name'              => 'compare-media',
637                 'title'             => __('Compare Media'),
638                 'description'       => __('Slide between two media items to compare them.'),
639                 'render_template'   => '/blocks/compare-media.php',
640                 'category'          => 'b3',
641                 'icon'              => 'image-flip-horizontal',
642                 'keywords'          => array( 'compare', ),
643     ));
644
645     acf_register_block_type(array(
646                 'name'              => 'tabs',
647                 'title'             => __('Tabs'),
648                 'description'       => __('Multiple images or videos organized in tabs.'),
649                 'render_template'   => '/blocks/tabs.php',
650                 'category'          => 'b3',
651                 'icon'              => 'images-alt2',
652                 'keywords'          => array( 'tab', 'tabs', ),
653     ));
654
655     acf_register_block_type(array(
656                 'name'              => 'paragraph-plus',
657                 'title'             => __('Paragraph+'),
658                 'description'       => __('Multiple images or videos organized in paragraph-plus.'),
659                 'render_template'   => '/blocks/paragraph-plus.php',
660                 'category'          => 'b3',
661                 'icon'              => 'editor-paragraph',
662                 'keywords'          => array( 'paragraph-plus', ),
663     ));
664
665     acf_register_block_type(array(
666         'name'              => 'accordion',
667         'title'             => __('Accordion'),
668         'description'       => __('A collapsable block with title and description.'),
669         'render_template'   => '/blocks/accordion.php',
670         'category'          => 'b3',
671         'icon'              => 'editor-insertmore',
672         'keywords'          => array( 'accordion', ),
673     ));
674
675     acf_register_block_type(array(
676         'name'              => 'chart-bars',
677         'title'             => __('Chart Bars'),
678         'description'       => __('A list of bars to use as stats.'),
679         'render_template'   => '/blocks/chart-bars.php',
680         'category'          => 'b3',
681         'icon'              => 'chart-bar',
682         'keywords'          => array( 'chart-bars', 'stats', 'chart', 'graph'),
683     ));
684 }
685
686 // Check if function exists and hook into setup.
687 if( function_exists('acf_register_block_type') ) {
688         add_action('acf/init', 'register_acf_block_types');
689 }
690
691 ?>