diff --git a/readme.txt b/readme.txt index 6829fc0..d581463 100755 --- a/readme.txt +++ b/readme.txt @@ -121,6 +121,7 @@ On the WP-Strava settings page you cannot currently remove and add another athle == Changelog == = 2.9.0 = +Added Segment Block https://wordpress.org/support/topic/show-segments-feature/ / https://wordpress.org/support/topic/embed-segments-feature/ Switched Activities List to display moving time instead of elapsed time https://wordpress.org/support/topic/moving-time-instead-of-elapsed-time/ Added calories burned (when available) to Activity and Activities List https://wordpress.org/support/topic/calorie/ diff --git a/src/WPStrava.php b/src/WPStrava.php index 2b7c25a..27ce19d 100644 --- a/src/WPStrava.php +++ b/src/WPStrava.php @@ -41,6 +41,12 @@ class WPStrava { */ private $routes = null; + /** + * Segment object to get segments. + * @var WPStrava_Segments + */ + private $segments = null; + /** * Private constructor (singleton). */ @@ -115,6 +121,10 @@ class WPStrava { return $this->get_routes(); } + if ( 'segments' === $name ) { + return $this->get_segments(); + } + if ( isset( $this->{$name} ) ) { return $this->{$name}; } @@ -165,6 +175,19 @@ class WPStrava { return $this->routes; } + /** + * Get the segments object. + * + * @return WPStrava_Segments + * @since 2.9.0 + */ + public function get_segments() { + if ( ! $this->segments ) { + $this->segments = new WPStrava_Segments(); + } + return $this->segments; + } + /** * Register the wp-strava stylesheet. */ @@ -203,6 +226,7 @@ class WPStrava { 'WPStrava_Blocks_Activity', 'WPStrava_Blocks_Route', 'WPStrava_Blocks_ActivitiesList', + 'WPStrava_Blocks_Segment', ); // automatically load dependencies and version diff --git a/src/WPStrava/Activity.php b/src/WPStrava/Activity.php index 66e8d41..2119ec3 100755 --- a/src/WPStrava/Activity.php +++ b/src/WPStrava/Activity.php @@ -10,7 +10,7 @@ class WPStrava_Activity { /** * Get single activity by ID. * - * @param string $client_id Client ID of athlete to retrieve for + * @param string $client_id Client ID of athlete to retrieve for. * @param int $activity_id ID of activity to retrieve. * @return object stdClass Representing this activity. * @author Justin Foell diff --git a/src/WPStrava/Blocks/Segment.php b/src/WPStrava/Blocks/Segment.php new file mode 100644 index 0000000..89bcb62 --- /dev/null +++ b/src/WPStrava/Blocks/Segment.php @@ -0,0 +1,98 @@ + + * @since 2.9.0 + */ + private $add_script = false; + + /** + * Register the wp-strava/segment block. + * + * @author Justin Foell + * @since 2.9.0 + */ + public function register_block() { + register_block_type( + 'wp-strava/segment', + array( + 'style' => 'wp-strava-block', + 'editor_style' => 'wp-strava-block-editor', + 'editor_script' => 'wp-strava-block', + 'render_callback' => array( $this, 'render_block' ), + 'attributes' => array( + 'url' => array( + 'type' => 'string', + 'default' => '', + ), + 'imageOnly' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'displayMarkers' => array( + 'type' => 'boolean', + 'default' => true, + ), + 'som' => array( + 'type' => 'string', + 'default' => null, + ), + ), + ) + ); + add_action( 'wp_footer', array( $this, 'print_scripts' ) ); + } + + /** + * Render for this block. + * + * @param array $attributes JSON attributes saved in the HTML comment for this block. + * @param string $content The content from JS save() for this block. + * @return string HTML for this block. + * @author Justin Foell + * @since 2.9.0 + */ + public function render_block( $attributes, $content ) { + if ( empty( $attributes['url'] ) ) { + return $content; + } + + $this->add_script = true; + + $matches = array(); + preg_match( '/\/segments\/([0-9].*)$/', $attributes['url'], $matches ); + if ( $matches[1] ) { + // Transform from block attributes to shortcode standard. + $atts = array( + 'id' => $matches[1], + 'image_only' => isset( $attributes['imageOnly'] ) ? $attributes['imageOnly'] : false, + 'markers' => isset( $attributes['displayMarkers'] ) ? $attributes['displayMarkers'] : true, + 'som' => ! empty( $attributes['som'] ) ? $attributes['som'] : null, + ); + + $renderer = new WPStrava_SegmentsRenderer(); + return $renderer->get_html( $atts ); + } + return $content; + } + + /** + * Enqueue style if block is being used. + * + * @author Justin Foell + * @since 2.9.0 + */ + public function print_scripts() { + if ( $this->add_script ) { + wp_enqueue_style( 'wp-strava-style' ); + } + } +} diff --git a/src/WPStrava/SOM.php b/src/WPStrava/SOM.php index 907d27b..deaf7d0 100644 --- a/src/WPStrava/SOM.php +++ b/src/WPStrava/SOM.php @@ -99,4 +99,15 @@ abstract class WPStrava_SOM { public function get_calories_label() { return __( 'kcal', 'wp-strava' ); } + + /** + * Get percent with % symbol. + * + * @param mixed $pct + * @author Justin Foell + * @since 2.9.0 + */ + public function percent( $pct ) { + return number_format_i18n( $pct, 1 ) . '%'; + } } diff --git a/src/WPStrava/Segments.php b/src/WPStrava/Segments.php new file mode 100644 index 0000000..d0f866c --- /dev/null +++ b/src/WPStrava/Segments.php @@ -0,0 +1,72 @@ + + * @since 2.9.0 + */ + public function get_segment( $client_id, $segment_id ) { + return WPStrava::get_instance()->get_api( $client_id )->get( "segments/{$segment_id}" ); + } + + /** + * Get starred segment list from Strava API. + * + * @author Justin Foell + * @param array $args { + * Array of arguments. + * + * @type string $client_id Client ID of athlete to retrieve for. + * @type int|null $quantity Number of records to retrieve (optional). + * } + * @return array Array of segments. + * @since 2.9.0 + */ + public function get_starred_segments( $args ) { + $api = WPStrava::get_instance()->get_api( $args['client_id'] ); + + $get_args = array(); + + if ( ! empty( $args['quantity'] ) && is_numeric( $args['quantity'] ) ) { + $get_args['per_page'] = $args['quantity']; + } + + $data = $api->get( 'segments/starred', $get_args ); + + if ( is_array( $data ) ) { + return $data; + } + + return array(); + } + + /** + * Conditionally display a link based on settings. + * + * @param int $segments_id Strava Segments ID + * @param string $text Text (or HTML) that is the content of link. + * @param string $title Title attribute (default empty). + * @return string Text with link + * @author Justin Foell + * @since 2.9.0 + */ + public function get_segments_link( $segments_id, $text, $title = '' ) { + if ( WPStrava::is_rest_request() || WPStrava::get_instance()->settings->no_link ) { + return $text; + } + $url = esc_url( self::SEGMENTS_URL . $segments_id ); + $title_attr = $title ? " title='" . esc_attr( $title ) . "'" : ''; + return "{$text}"; + } +} diff --git a/src/WPStrava/SegmentsRenderer.php b/src/WPStrava/SegmentsRenderer.php new file mode 100644 index 0000000..c034611 --- /dev/null +++ b/src/WPStrava/SegmentsRenderer.php @@ -0,0 +1,131 @@ + + * @since 2.9.0 + */ +class WPStrava_SegmentsRenderer { + + /** + * Get the HTML for a single segment. + * + * @param array $atts + * @return string HTML for an segment. + * @author Justin Foell + * @since 2.9.0 + */ + public function get_html( $atts ) { + $defaults = array( + 'id' => 0, + 'som' => WPStrava::get_instance()->settings->som, + 'map_width' => '480', + 'map_height' => '320', + 'client_id' => WPStrava::get_instance()->settings->get_default_id(), + 'markers' => true, + 'image_only' => false, + ); + + $atts = wp_parse_args( $atts, $defaults ); + + /* Make sure boolean values are actually boolean + * @see https://wordpress.stackexchange.com/a/119299 + */ + $atts['markers'] = filter_var( $atts['markers'], FILTER_VALIDATE_BOOLEAN ); + $atts['image_only'] = filter_var( $atts['image_only'], FILTER_VALIDATE_BOOLEAN ); + + $segments = WPStrava::get_instance()->segments; + $segment_details = null; + + try { + $segment_details = $segments->get_segment( $atts['client_id'], $atts['id'] ); + } catch ( WPStrava_Exception $e ) { + return $e->to_html(); + } + + $segments_output = ''; + if ( $segment_details ) { + $segments_output .= '
'; + if ( ! $atts['image_only'] ) { + $segments_output .= $this->get_table( $segment_details, $atts['som'] ); + } + + // Sanitize width & height. + $map_width = str_replace( '%', '', $atts['map_width'] ); + $map_height = str_replace( '%', '', $atts['map_height'] ); + $map_width = str_replace( 'px', '', $map_width ); + $map_height = str_replace( 'px', '', $map_height ); + + $segments_output .= $segments->get_segments_link( + $segment_details->id, + WPStrava_StaticMap::get_image_tag( $segment_details, $map_height, $map_width, $atts['markers'], $segment_details->name ), + $segment_details->name + ); + + if ( ! empty( $segment_details->description ) ) { + $segments_output .= '
' . esc_html( $segment_details->description ) . '
'; + } + + $segments_output .= '
'; + } // End if( $segment_details ). + return $segments_output; + } + + /** + * The the segment details in in HTML table. + * + * @param stdClass $segment_details Segment details from the segment class. + * @param string $som System of measure (english/metric). + * @return string HTML Table of segment details. + * @author Justin Foell + * @author Sebastian Erb + * @since 2.9.0 + */ + private function get_table( $segment_details, $som ) { + $strava_som = WPStrava_SOM::get_som( $som ); + $elevation_title = '' . __( 'Elevation Gain', 'wp-strava' ) . ''; + $elevation = ' +
' . $strava_som->elevation( $segment_details->total_elevation_gain ) . '
+
' . $strava_som->get_elevation_label() . '
+ '; + $grade_title = '' . __( 'Avg. Grade', 'wp-strava' ) . ''; + $grade = ' +
' . $strava_som->percent( $segment_details->average_grade ) . '
+
 
+ '; + + if ( WPStrava::get_instance()->settings->hide_elevation ) { + $elevation_title = ''; + $elevation = ''; + $grade_title = ''; + $grade_title = ''; + } + + return ' + + + + + ' . $grade_title . ' + ' . $elevation_title . ' + + + + + + ' . $grade . ' + ' . $elevation . ' + + +
' . __( 'Distance', 'wp-strava' ) . '
+
' . $strava_som->distance( $segment_details->distance ) . '
+
' . $strava_som->get_distance_label() . '
+
+ '; + } +} diff --git a/src/blocks/segment/block.json b/src/blocks/segment/block.json new file mode 100644 index 0000000..ef9fd19 --- /dev/null +++ b/src/blocks/segment/block.json @@ -0,0 +1,30 @@ +{ + "name": "wp-strava/segment", + "title": "Strava Segment", + "category": "embed", + "icon": "chart-area", + "description": "Embed a Strava Segment", + "keywords": [ "segment", "map" ], + "textdomain": "wp-strava", + "attributes": { + "url": { + "type": "string", + "default": "" + }, + "imageOnly": { + "type": "boolean", + "default": false + }, + "displayMarkers": { + "type": "boolean", + "default": true + }, + "som": { + "type": "string", + "default": null + } + }, + "editorScript": "file:../../../build/index.js", + "editorStyle": "file:../../../build/editor.css", + "style": "file:../../../build/style.css" +} diff --git a/src/blocks/segment/edit.js b/src/blocks/segment/edit.js new file mode 100644 index 0000000..2ce5416 --- /dev/null +++ b/src/blocks/segment/edit.js @@ -0,0 +1,125 @@ +/* global wp, wpStrava */ +import EmbedPlaceholder from '../components/embed-placeholder'; +import EmbedControls from '../components/embed-controls'; +import SOMOverride from '../components/som-override'; + +const { __ } = wp.i18n; +const { Component } = wp.element; +const { InspectorControls } = wp.editor; +const { PanelBody, ToggleControl, ServerSideRender } = wp.components; +const { isEmpty } = lodash; + +class Edit extends Component { + + constructor() { + super( ...arguments ); + this.setUrl = this.setUrl.bind( this ); + this.switchBackToURLInput = this.switchBackToURLInput.bind( this ); + this.toggleImageOnly = this.toggleImageOnly.bind( this ); + this.toggleDisplayMarkers = this.toggleDisplayMarkers.bind( this ); + this.overrideSOM = this.overrideSOM.bind( this ); + + this.state = { + url: this.props.attributes.url, + imageOnly: this.props.attributes.imageOnly, + displayMarkers: this.props.attributes.displayMarkers, + som: this.props.attributes.som, + editingURL: isEmpty( this.props.attributes.url ) ? true : false, + }; + } + + setUrl( event ) { + if ( event ) { + event.preventDefault(); + } + this.setState( { editingURL: false } ); + this.props.setAttributes( { url: this.state.url } ); + } + + switchBackToURLInput() { + this.setState( { editingURL: true } ); + } + + toggleImageOnly( checked ) { + this.setState( { imageOnly: checked } ); + this.props.setAttributes( { imageOnly: checked } ); + } + + toggleDisplayMarkers( checked ) { + this.setState( { displayMarkers: checked } ); + this.props.setAttributes( { displayMarkers: checked } ); + } + + overrideSOM( newSOM ) { + this.setState( { som: newSOM } ); + this.props.setAttributes( { som: newSOM } ); + } + + render() { + const { + url, + editingURL, + imageOnly, + displayMarkers, + som + } = this.state; + + // Newly inserted block or we've clicked the edit button. + if ( editingURL ) { + return ( + + this.setState( { url: event.target.value } ) + } + /> + ); + } + + return ( + <> + + + + + this.toggleImageOnly( checked ) } + /> + this.toggleDisplayMarkers( checked ) } + /> + + + + + ); + } +} + +export default Edit; diff --git a/src/blocks/segment/index.js b/src/blocks/segment/index.js new file mode 100644 index 0000000..70a50a1 --- /dev/null +++ b/src/blocks/segment/index.js @@ -0,0 +1,9 @@ +/* global wp, wpStrava */ +import { registerBlockType } from '@wordpress/blocks'; +import edit from './edit'; +import metadata from './block.json'; + +metadata.edit = edit; +metadata.save = () => null; + +registerBlockType( metadata.name, metadata ); diff --git a/src/index.js b/src/index.js index 056e447..0fdc131 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ import './blocks/activity'; import './blocks/route'; import './blocks/activitieslist'; +import './blocks/segment'; diff --git a/tests/WPStrava/SOMEnglishTest.php b/tests/WPStrava/SOMEnglishTest.php index 26e3d63..c6e5a5f 100644 --- a/tests/WPStrava/SOMEnglishTest.php +++ b/tests/WPStrava/SOMEnglishTest.php @@ -107,4 +107,15 @@ class WPStrava_SOMEnglishTest extends TestCase { $this->assertEquals( '1,304', $this->som->calories( 1304 ) ); } + /** + * Test that 5.7 grade is 5.7% using both string and int inputs. + * + * @author Justin Foell + * @since 2.9.0 + */ + public function test_percent() { + $this->assertEquals( '5.7%', $this->som->percent( '5.7' ) ); + $this->assertEquals( '5.7%', $this->som->percent( 5.7 ) ); + } + }