Theme: Single pledge view (#43)

* Theme: Create template part for single pledge

* Plugin: Add functions for handling xprofile data

* Plugin: Add helper function for getting contributor user objects

* Theme: Display aggregated contributor data on single pledge view

* Theme: Add functions, markup, and styles to show team badges

* Theme: Add badge for Test team

* Theme: Add tentative URL for report a pledge link

* Theme: Add support for post thumbnails

* Theme: Add org logo to single pledge view

* Theme: Update `$content_width` to 960

This matches the width of the content in the global header and
the About page.

* Move pledge template to a file used by the template heirarchy

* Clean up some PHPCS errors

* Move pledge structure out to just the list items

* Create file for single pledge styles

* style badge grid into a grid.

* Add a custom image size for the logo

* Update styles

* Un-hide pledges with no confirmed contributors

* Fix content width
This commit is contained in:
Corey McKrill 2019-10-28 10:38:49 -07:00 committed by Kelly Dwan
parent 2ec665bcbb
commit dbdcf26bd9
17 changed files with 683 additions and 53 deletions

View file

@ -98,6 +98,7 @@
<rule ref="WordPress.Files.FileName.NotHyphenatedLowercase">
<exclude-pattern>*/template-parts/content-5ftf_pledge.php$</exclude-pattern>
<exclude-pattern>*/archive-5ftf_pledge.php$</exclude-pattern>
<exclude-pattern>*/single-5ftf_pledge.php$</exclude-pattern>
</rule>
<rule ref="WordPress-Docs">

View file

@ -3,7 +3,7 @@ namespace WordPressDotOrg\FiveForTheFuture\Contributor;
use WordPressDotOrg\FiveForTheFuture;
use WordPressDotOrg\FiveForTheFuture\Pledge;
use WP_Error, WP_Post;
use WP_Error, WP_Post, WP_User;
defined( 'WPINC' ) || die();
@ -196,3 +196,16 @@ function get_pledge_contributors( $pledge_id, $status = 'publish', $contributor_
return $posts;
}
/**
* Get the user objects that correspond with pledge contributor posts.
*
* @param WP_Post[] $contributor_posts
*
* @return WP_User[]
*/
function get_contributor_user_objects( array $contributor_posts ) {
return array_map( function( WP_Post $post ) {
return get_user_by( 'login', $post->post_title );
}, $contributor_posts );
}

View file

@ -193,9 +193,10 @@ function filter_query( $query ) {
'type' => 'NUMERIC',
);
if ( CPT_ID === $query->get( 'post_type' ) ) {
$query->set( 'meta_query', $meta_queries );
}
// @todo Re-enable this check once pledges are public.
// if ( CPT_ID === $query->get( 'post_type' ) ) {
// $query->set( 'meta_query', $meta_queries );
// }
// Searching is restricted to pledges only.
if ( $query->is_search ) {

View file

@ -0,0 +1,85 @@
<?php
namespace WordPressDotOrg\FiveForTheFuture\XProfile;
use WordPressDotOrg\FiveForTheFuture\Contributor;
use wpdb;
defined( 'WPINC' ) || die();
/**
* Pull relevant data from profiles.wordpress.org.
*
* Note that this does not unserialize anything, it just pulls the raw values from the database table.
*
* @global wpdb $wpdb
*
* @param array $user_ids
*
* @return array
*/
function get_xprofile_contribution_data( array $user_ids ) {
global $wpdb;
// The IDs of the xprofile fields we need. Better to use the numerical IDs than the field labels,
// because those are more likely to change.
$field_ids = array(
29, // Hours per week.
30, // Teams, the value of this field is serialized in the database.
);
$sql = $wpdb->prepare(
'
SELECT user_id, field_id, value
FROM bpmain_bp_xprofile_data
WHERE user_id IN ( %1$s )
AND field_id IN ( %2$s )
',
implode( ', ', array_map( 'absint', $user_ids ) ),
implode( ', ', array_map( 'absint', $field_ids ) )
);
return $wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL -- prepare called above.
}
/**
* Aggregate the raw xprofile data for all contributors linked to a given pledge.
*
* @param int $pledge_id
*
* @return array
*/
function get_aggregate_contributor_data_for_pledge( $pledge_id ) {
$contributors = Contributor\get_contributor_user_objects(
// TODO set to 'publish' when finished testing.
Contributor\get_pledge_contributors( $pledge_id, 'pending' )
);
$user_ids = wp_list_pluck( $contributors, 'ID' );
$data = get_xprofile_contribution_data( $user_ids );
$initial = array(
'contributors' => count( $user_ids ),
'hours' => 0,
'teams' => array(),
);
$aggregate_data = array_reduce( $data, function( $carry, $item ) {
switch ( $item['field_id'] ) {
case 29: // Hours.
$carry['hours'] += absint( $item['value'] );
break;
case 30: // Teams.
$value = maybe_unserialize( $item['value'] );
$carry['teams'] = array_merge( $carry['teams'], $value );
break;
}
return $carry;
}, $initial );
$aggregate_data['teams'] = array_unique( $aggregate_data['teams'] );
sort( $aggregate_data['teams'] );
return $aggregate_data;
}

View file

@ -29,6 +29,7 @@ function load() {
require_once get_includes_path() . 'pledge-meta.php';
require_once get_includes_path() . 'pledge-form.php';
require_once get_includes_path() . 'directory.php';
require_once get_includes_path() . 'xprofile.php';
}
/**

View file

@ -23,8 +23,11 @@
@import "archive";
@import "entry-content";
@import "page";
@import "pledge-contributors";
@import "pledge-list";
@import "pledge-single";
@import "site-content";
@import "site-header";
@import "site-title";
@import "team-badges";
@import "wporg-header";

View file

@ -8,8 +8,13 @@
}
h2 {
margin-top: ms(10);
font-size: ms( 2 );
font-weight: 400;
color: $color__text-heading;
}
> :first-child {
margin-top: 0;
}
}

View file

@ -0,0 +1,52 @@
.pledge-contributors {
h3 {
margin-top: 0;
font-size: ms(-1);
color: $color__text-lighter;
}
.pledge-contributor__avatar {
display: inline-block;
background: $color-gray-light-700;
img {
vertical-align: middle;
}
}
&.has-contrib-names {
display: flex;
flex-wrap: wrap;
margin: ms(2) 0;
list-style: none;
justify-content: space-between;
li {
margin: 0 auto 40px;
width: calc( 50% - 20px );
@include breakpoint( $breakpoint-small ) {
width: calc( 33% - 48px );
min-width: 170px;
}
}
li:first-child:last-child {
width: 100%;
a {
max-width: 280px;
}
}
.pledge-contributor__avatar {
margin-bottom: 20px;
}
.pledge-contributor__name {
display: block;
text-align: center;
}
}
}

View file

@ -45,7 +45,7 @@ body.archive.post-type-archive-5ftf_pledge {
}
}
}
.page-header-controls {
font-size: ms(-2);
@ -55,4 +55,32 @@ body.archive.post-type-archive-5ftf_pledge {
font-weight: 700;
}
}
/* Structure */
article {
display: grid;
grid-template-columns: 330px auto;
margin-bottom: ms(12);
.entry-image {
grid-column: 1;
grid-row: 1 / span 2;
margin-right: ms(8);
}
.entry-header {
grid-column: 2;
grid-row: 1;
}
.entry-content {
grid-column: 2;
grid-row: 2;
}
}
.entry-title {
font-size: ms(2);
font-weight: 400;
}
}

View file

@ -0,0 +1,57 @@
body.single.single-5ftf_pledge {
// Expand archive content area to full-width of header.
.site-content .site-main {
padding: 0 10px;
max-width: $size__site-main;
}
.entry-header {
margin: ms(12) 0;
padding-bottom: ms(4);
border-bottom: 1px solid $color-gray-light-500;
@include breakpoint( $breakpoint-mobile ) {
display: flex;
align-items: center;
> div {
flex: auto;
}
.entry-image {
max-width: 330px;
}
}
@include breakpoint( 0, $breakpoint-mobile ) {
text-align: center;
.entry-image {
margin-top: ms(2);
}
}
}
.entry-title {
margin-bottom: 0;
}
.entry-image__logo {
background: transparent;
}
.pledge-company-description {
max-width: $size__content-width;
}
.team-grid {
margin-top: ms(2);
}
.entry-footer {
margin-top: ms(2);
border-top: 1px solid $color-gray-light-500;
padding-top: ms(2);
}
}

View file

@ -6,7 +6,7 @@
padding: 0;
.site-main {
margin: 0 auto;
margin: 0 auto ms(8);
padding: 0 ms( 2 );
max-width: calc( #{ $size__content-width } + #{ ms( 2 ) * 2 } );
}

View file

@ -0,0 +1,196 @@
/**
* Copied from the profiles.wordpress.org theme stylesheet.
*/
.team-grid {
display: flex;
flex-wrap: wrap;
margin: 0;
list-style: none;
li {
display: flex;
align-items: center;
margin: 0 0 1em;
width: 25%;
min-width: 10em;
}
div.dashicons {
margin-right: 0.5em;
&:before {
position: relative;
top: 2px;
font-size: 24px;
}
}
@include breakpoint( 0, $breakpoint-small ) {
li {
width: 100%;
}
}
}
.badge {
margin: 4px;
width: 32px;
height: 32px;
border: 2px solid white;
border-radius: 50%;
box-sizing: border-box;
position: relative;
}
.badge-themes-reviewer {
background: rgba(78, 50, 136, 0.25);
box-shadow: 0 0 0 4px rgb(78, 50, 136);
}
.badge-themes-reviewer:before {
color: rgb(78, 50, 136);
}
.badge-plugins-reviewer {
background: rgba(240, 103, 35, 0.25);
box-shadow: 0 0 0 4px rgb(240, 103, 35);
}
.badge-plugins-reviewer:before {
color: rgb(240, 103, 35);
}
.badge-community {
background: rgba(17, 121, 157, 0.25);
box-shadow: 0 0 0 4px rgb(17, 121, 157);
}
.badge-community:before {
color: rgb(17, 121, 157);
}
.badge-meta {
background: rgba(174, 173, 173, 0.25);
box-shadow: 0 0 0 4px rgb(174, 173, 173);
}
.badge-meta:before {
color: rgb(174, 173, 173);
}
.badge-code-committer {
background: rgba(205, 0, 0, 0.25);
box-shadow: 0 0 0 4px rgb(205, 0, 0);
}
.badge-code-committer:before {
color: rgb(205, 0, 0);
}
.badge-support {
background: rgba(51, 180, 206, 0.25);
box-shadow: 0 0 0 4px rgb(51, 180, 206);
}
.badge-support:before {
color: rgb(51, 180, 206);
}
.badge-wordpress-tv {
background: rgba(115, 173, 48, 0.25);
box-shadow: 0 0 0 4px rgb(115, 173, 48);
}
.badge-wordpress-tv:before {
color: rgb(115, 173, 48);
}
.badge-accessibility {
background: rgba(17, 121, 157, 0.25);
box-shadow: 0 0 0 4px rgb(17, 121, 157);
}
.badge-accessibility:before {
color: rgb(17, 121, 157);
}
.badge-documentation {
background: rgba(59, 114, 54, 0.25);
box-shadow: 0 0 0 4px rgb(59, 114, 54);
}
.badge-documentation:before {
color: rgb(59, 114, 54);
}
.badge-mobile {
background: rgba(251, 161, 108, 0.25);
box-shadow: 0 0 0 4px rgb(251, 161, 108);
}
.badge-mobile:before {
color: rgb(251, 161, 108);
}
.badge-training{
background: rgba(233, 192, 45, 0.25);
box-shadow: 0 0 0 4px rgb(233, 192, 45);
}
.badge-training:before {
color: rgb(233, 192, 45);
}
.badge-translation-editor {
background: rgba(195, 34, 131, 0.25);
box-shadow: 0 0 0 4px rgb(195, 34, 131);
}
.badge-translation-editor:before {
color: rgb(195, 34, 131);
}
.badge-design {
background: rgba(238, 194, 106, 0.25);
box-shadow: 0 0 0 4px rgb(238, 194, 106);
}
.badge-design:before {
color: rgb(238, 194, 106);
}
.badge-marketing {
background: rgba(71, 190, 167, 0.25);
box-shadow: 0 0 0 4px rgb(71, 190, 167);
}
.badge-marketing:before {
color: rgb(71, 190, 167);
}
.badge-wp-cli {
background: rgba(66, 66, 66, 0.25);
box-shadow: 0 0 0 4px rgb(66, 66, 66);
}
.badge-wp-cli:before {
color: rgb(66, 66, 66);
}
.badge-hosting {
background: rgba(83, 88, 166, 0.25);
box-shadow: 0 0 0 4px rgb(83, 88, 166);
}
.badge-hosting:before {
color: rgb(83, 88, 166);
}
.badge-tide {
background: rgb(197, 201, 255);
box-shadow: 0 0 0 4px rgb(21, 38, 255);
}
.badge-tide:before {
color: rgb(21, 38, 255);
}
.badge-security-team {
background: rgba(0, 204, 58, 0.25);
box-shadow: 0 0 0 4px rgb(0, 204, 58);
}
.badge-security-team:before {
color: rgb(0, 204, 58);
}
.badge-test-team {
background: rgba(136, 79, 174, 0.25);
box-shadow: 0 0 0 4px rgb(136, 79, 174);
}
.badge-test-team:before {
color: rgb(136, 79, 174);
}

View file

@ -1,26 +1,4 @@
article.type-5ftf_pledge {
/* Structure */
display: grid;
grid-template-columns: 330px auto;
margin-bottom: ms(12);
.entry-image {
grid-column: 1;
grid-row: 1 / span 2;
margin-right: ms(8);
}
.entry-header {
grid-column: 2;
grid-row: 1;
}
.entry-content {
grid-column: 2;
grid-row: 2;
}
/* Styles */
.entry-image__placeholder {
background: $color-gray-light-100;
@ -42,33 +20,16 @@ article.type-5ftf_pledge {
}
}
.entry-header a {
text-decoration: underline;
}
.entry-title {
margin-top: 0;
font-size: ms(2);
font-weight: 400;
a {
text-decoration: underline;
}
}
.entry-content {
font-size: ms(-1);
color: $color__text-darker;
}
.pledge-contributors h3 {
margin-top: 0;
font-size: ms(-1);
color: $color__text-lighter;
}
.pledge-contributor__avatar {
display: inline-block;
background: $color-gray-light-700;
img {
vertical-align: middle;
}
}
}

View file

@ -37,6 +37,10 @@ function setup() {
add_theme_support( 'wp-block-styles' );
add_theme_support( 'editor-styles' );
add_theme_support( 'post-thumbnails' );
add_image_size( 'pledge-logo', 660, 200 );
add_editor_style( 'css/style-editor.css' );
add_theme_support(
@ -111,7 +115,7 @@ add_action( 'after_setup_theme', __NAMESPACE__ . '\setup' );
* @global int $content_width
*/
function content_width() {
$GLOBALS['content_width'] = apply_filters( 'wporg_plugins_content_width', 640 );
$GLOBALS['content_width'] = 640;
}
add_action( 'after_setup_theme', __NAMESPACE__ . '\content_width', 0 );
@ -204,3 +208,98 @@ function loader_src( $src, $handle ) {
}
add_filter( 'style_loader_src', __NAMESPACE__ . '\loader_src', 10, 2 );
add_filter( 'script_loader_src', __NAMESPACE__ . '\loader_src', 10, 2 );
/**
* Determines the CSS classes for a given team badge.
*
* Based on the `wporg_profiles_get_association_classes` function in the profiles.wordpress.org theme.
*
* @param string $team
*
* @return array
*/
function get_badge_classes( $team ) {
switch ( strtolower( $team ) ) {
case 'accessibility':
$classes = array( 'badge-accessibility', 'dashicons-universal-access' );
break;
case 'cli':
$classes = array( 'badge-wp-cli', 'dashicons-arrow-right-alt2' );
break;
case 'community':
$classes = array( 'badge-community', 'dashicons-groups' );
break;
case 'core':
$classes = array( 'badge-code-committer', 'dashicons-editor-code' );
break;
case 'design':
$classes = array( 'badge-design', 'dashicons-art' );
break;
case 'documentation':
$classes = array( 'badge-documentation', 'dashicons-admin-page' );
break;
case 'hosting':
$classes = array( 'badge-hosting', 'dashicons-cloud' );
break;
case 'marketing':
$classes = array( 'badge-marketing', 'dashicons-format-status' );
break;
case 'meta':
$classes = array( 'badge-meta', 'dashicons-networking' );
break;
case 'mobile':
$classes = array( 'badge-mobile', 'dashicons-smartphone' );
break;
case 'plugins':
$classes = array( 'badge-plugins-reviewer ', 'dashicons-admin-plugins' );
break;
case 'polyglots':
$classes = array( 'badge-translation-editor', 'dashicons-translation' );
break;
case 'security':
$classes = array( 'badge-security-team', 'dashicons-lock' );
break;
case 'support':
$classes = array( 'badge-support', 'dashicons-format-chat' );
break;
case 'test':
$classes = array( 'badge-test-team', 'dashicons-desktop' );
break;
case 'themes':
$classes = array( 'badge-themes-reviewer', 'dashicons-admin-appearance' );
break;
case 'tide':
$classes = array( 'badge-tide', 'dashicons-tide' );
break;
case 'training':
$classes = array( 'badge-training', 'dashicons-welcome-learn-more' );
break;
case 'tv':
$classes = array( 'badge-wordpress-tv', 'dashicons-video-alt2' );
break;
default:
$classes = array();
break;
}
return $classes;
}

View file

@ -0,0 +1,128 @@
<?php
namespace WordPressDotOrg\FiveForTheFuture\Theme;
use WordPressDotOrg\FiveForTheFuture\Contributor;
use WordPressDotOrg\FiveForTheFuture\XProfile;
use WP_Post;
use const WordPressDotOrg\FiveForTheFuture\PledgeMeta\META_PREFIX;
$contribution_data = XProfile\get_aggregate_contributor_data_for_pledge( get_the_ID() );
$contributors = Contributor\get_contributor_user_objects(
// TODO set to 'publish' when finished testing.
Contributor\get_pledge_contributors( get_the_ID(), 'pending' )
);
$report_page = get_page_by_path( 'report' );
get_header(); ?>
<main id="main" class="site-main" role="main">
<?php while ( have_posts() ) : the_post(); // phpcs:ignore ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<header class="entry-header">
<div>
<?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
<span class="pledge-url">
<?php
printf(
'<a href="%1$s">%1$s</a>',
esc_url( $post->{ META_PREFIX . 'org-url' } )
);
?>
</span>
</div>
<div class="entry-image">
<?php if ( has_post_thumbnail() ) : ?>
<div class="entry-image__logo">
<?php the_post_thumbnail( 'pledge-logo' ); ?>
</div>
<?php else : ?>
<div class="entry-image__placeholder"></div>
<?php endif; ?>
</div><!-- .entry-image -->
</header>
<div class="entry-content">
<h2><?php esc_html_e( 'About', 'wporg' ); ?></h2>
<div class="pledge-company-description">
<?php echo wp_kses_post( wpautop( $post->{ META_PREFIX . 'org-description' } ) ); ?>
</div>
<?php if ( ! empty( $contributors ) ) : ?>
<h2><?php esc_html_e( 'Contributions', 'wporg' ); ?></h2>
<p>
<?php
echo wp_kses_post( sprintf(
__( '%1$s sponsors %2$s for a total of <strong>%3$s</strong> hours per week.', 'wporg' ),
get_the_title(),
sprintf(
_n( '<strong>%d</strong> contributor', '<strong>%d</strong> contributors', $contribution_data['contributors'], 'wporg' ),
number_format_i18n( absint( $contribution_data['contributors'] ) )
),
number_format_i18n( absint( $contribution_data['hours'] ) )
) );
?>
</p>
<p>
<?php
echo wp_kses_post( sprintf(
__( 'Contributors from %s work on the following teams:', 'wporg' ),
get_the_title()
) );
?>
</p>
<ul class="team-grid">
<?php foreach ( $contribution_data['teams'] as $team ) :
$badge_classes = get_badge_classes( $team );
?>
<li>
<div class="badge item dashicons <?php echo esc_attr( implode( ' ', $badge_classes ) ); ?>"></div>
<span class="badge-label"><?php echo esc_html( $team ); ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<h2><?php esc_html_e( 'Contributors', 'wporg' ); ?></h2>
<?php if ( ! empty( $contributors ) ) : ?>
<ul class="pledge-contributors has-contrib-names">
<?php foreach ( $contributors as $contributor ) : ?>
<li class="pledge-contributor">
<span class="pledge-contributor__avatar">
<?php echo get_avatar( $contributor->user_email, 280 ); ?>
</span>
<?php
printf(
'<a class="pledge-contributor__name" href="%1$s">%2$s</a>',
sprintf(
'https://profiles.wordpress.org/%s/',
sanitize_key( $contributor->user_login ) // phpcs:ignore WordPress.Security.EscapeOutput -- sanitize_key will catch any security issues.
),
esc_html( $contributor->display_name )
);
?>
</li>
<?php endforeach; ?>
</ul>
<?php else : ?>
<p><?php esc_html_e( 'No confirmed contributors yet.', 'wporg' ); ?></p>
<?php endif; ?>
</div>
<footer class="entry-footer">
<a href="<?php the_permalink( $report_page ); ?>"><?php esc_html_e( 'Report a problem', 'wporg' ); ?></a>
</footer>
</article>
<?php endwhile; ?>
</main><!-- #main -->
<?php get_footer();

View file

@ -1,5 +1,4 @@
<?php
namespace WordPressDotOrg\FiveForTheFuture\Theme;
get_header(); ?>
@ -10,6 +9,7 @@ get_header(); ?>
the_post();
get_template_part( 'template-parts/content', get_post_type() );
endwhile; ?>
</main><!-- #main -->

View file

@ -32,12 +32,12 @@ $contributor_title = sprintf(
<div class="entry-image">
<?php if ( has_post_thumbnail() ) : ?>
<div class="entry-image__logo">
<?php the_post_thumbnail(); ?>
<?php the_post_thumbnail( 'pledge-logo' ); ?>
</div>
<?php else : ?>
<div class="entry-image__placeholder"></div>
<?php endif; ?>
</div><!-- .post-thumbnail -->
</div><!-- .entry-image -->
<header class="entry-header">
<?php if ( is_singular() ) : ?>