Skip to content

Commit

Permalink
Merge pull request #1129 from alleyinteractive/feature/issue-1126/add…
Browse files Browse the repository at this point in the history
…-support-for-aside-subcomponents

Add Support for Aside Subcomponents
  • Loading branch information
kevinfodness authored May 31, 2024
2 parents 4fd2824 + 403bda0 commit b5f8bdc
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 66 deletions.
86 changes: 71 additions & 15 deletions admin/class-admin-apple-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* @package Apple_News
*/

use Apple_Exporter\Components\Component;

/**
* This class is in charge of handling the management of custom JSON.
*/
Expand Down Expand Up @@ -35,6 +37,14 @@ class Admin_Apple_JSON extends Apple_News {
*/
private $selected_component = '';

/**
* Holds the selected subcomponent from the request.
*
* @since 2.5.0
* @var string
*/
private $selected_subcomponent = '';

/**
* Holds the selected theme from the request.
*
Expand Down Expand Up @@ -100,6 +110,14 @@ public function action_router() {
$this->selected_component = '';
}

// Store the selected subcomponent value for use later.
$this->selected_subcomponent = isset( $_POST['apple_news_subcomponent'] )
? sanitize_text_field( wp_unslash( $_POST['apple_news_subcomponent'] ) )
: '';
if ( ! array_key_exists( $this->selected_subcomponent, $this->list_components() ) ) {
$this->selected_subcomponent = '';
}

// Store the selected theme for use later.
if ( ! empty( $_POST['apple_news_theme'] ) ) {
$this->selected_theme = sanitize_text_field( wp_unslash( $_POST['apple_news_theme'] ) );
Expand Down Expand Up @@ -185,10 +203,14 @@ public function page_json_render() {
? $this->get_selected_component()
: '';

// If we have a class, get its specs.
$specs = ( ! empty( $selected_component ) )
? $this->get_specs( $selected_component )
: [];
// Handle subcomponents.
$component_can_be_parent = $this->get_component_class( $selected_component )?->can_be_parent();
$selected_subcomponent = ( ! empty( $selected_component ) )
? $this->get_selected_subcomponent()
: '';

// If we have a component or subcomponent, get its specs.
$specs = $this->get_specs( $selected_component, $selected_subcomponent );

/* phpcs:enable */

Expand Down Expand Up @@ -260,8 +282,11 @@ private function reset_json() {
return;
}

// Get the selected subcomponent.
$subcomponent = $this->get_selected_subcomponent();

// Get the specs for the component.
$specs = $this->get_specs( $component );
$specs = $this->get_specs( $component, $subcomponent );
if ( empty( $specs ) ) {
\Admin_Apple_Notice::error(
sprintf(
Expand Down Expand Up @@ -317,9 +342,12 @@ private function save_json() {
return;
}

// Get the specs for the component and theme.
// Get the selected subcomponent.
$subcomponent = $this->get_selected_subcomponent();

// Get the specs for the component or subcomponent.
$theme = sanitize_text_field( wp_unslash( $_POST['apple_news_theme'] ) );
$specs = $this->get_specs( $component, $theme );
$specs = $this->get_specs( $component, $subcomponent );
if ( empty( $specs ) ) {
\Admin_Apple_Notice::error(
sprintf(
Expand Down Expand Up @@ -368,18 +396,37 @@ private function save_json() {
/**
* Loads the JSON specs that can be customized for the component
*
* @param string $component The component to get specs for.
* @param string $component The component to get specs for.
* @param ?string $subcomponent The subcomponent to get specs for.
*
* @return array
* @access private
*/
private function get_specs( $component ) {
if ( empty( $component ) ) {
return [];
private function get_specs( $component, $subcomponent = null ) {
$component_class = $this->get_component_class( $component, $subcomponent );
return $component_class ? $component_class->get_specs() : [];
}

/**
* Given a component slug, returns the associated component class.
*
* @param string $component The component to get the class for.
* @param ?string $subcomponent The subcomponent to get the class for.
*
* @return ?Component
*/
private function get_component_class( $component, $subcomponent = null ) {
$classname = $this->namespace . $component;
if ( ! class_exists( $classname ) ) {
return null;
}

$classname = $this->namespace . $component;
$component_class = new $classname();
return $component_class->get_specs();
// Handle subcomponents.
if ( ! empty( $subcomponent ) ) {
$subcomponent_classname = $this->namespace . $subcomponent;
return class_exists( $subcomponent_classname ) ? new $subcomponent_classname( null, null, null, null, null, null, null, new $classname() ) : null;
}

return new $classname();
}

/**
Expand Down Expand Up @@ -414,6 +461,15 @@ public function get_selected_component() {
return $this->selected_component;
}

/**
* Gets the currently selected subcomponent.
*
* @return string
*/
public function get_selected_subcomponent() {
return $this->selected_subcomponent;
}

/**
* Checks for a valid selected theme.
*
Expand Down
21 changes: 20 additions & 1 deletion admin/partials/page-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
* phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
*
* @global array $all_themes
* @global bool $component_can_be_parent
* @global array $components
* @global string $selected_component
* @global string $selected_subcomponent
* @global string $selected_theme
* @global array $specs
* @global string $theme_admin_url
Expand Down Expand Up @@ -87,7 +89,7 @@
</div>
<?php if ( ! empty( $selected_theme ) ) : ?>
<div>
<label for="apple_news_theme">
<label for="apple_news_component">
<?php esc_html_e( 'Component', 'apple-news' ); ?>:
<select id="apple_news_component" name="apple_news_component">
<option value=""><?php esc_html_e( 'Select a component', 'apple-news' ); ?></option>
Expand All @@ -102,6 +104,23 @@
</div>
<?php endif; ?>

<?php if ( $component_can_be_parent ) : ?>
<div>
<label for="apple_news_subcomponent">
<?php esc_html_e( 'Subcomponent', 'apple-news' ); ?>:
<select id="apple_news_subcomponent" name="apple_news_subcomponent">
<option value=""><?php esc_html_e( 'None', 'apple-news' ); ?></option>
<?php foreach ( $components as $apple_component_key => $apple_component_name ) : ?>
<option value="<?php echo esc_attr( $apple_component_key ); ?>"
<?php selected( $apple_component_key, $selected_subcomponent ); ?>>
<?php echo esc_html( $apple_component_name ); ?>
</option>
<?php endforeach; ?>
</select>
</label>
</div>
<?php endif; ?>

<?php if ( ! empty( $specs ) ) : ?>
<?php
foreach ( $specs as $apple_spec ) :
Expand Down
4 changes: 4 additions & 0 deletions assets/js/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
e.preventDefault();
appleNewsJSONSubmit( $( this ), 'apple_news_get_json' );
});
$( '#apple_news_subcomponent' ).on( 'change', function( e ) {
e.preventDefault();
appleNewsJSONSubmit( $( this ), 'apple_news_get_json' );
});
$( '#apple_news_reset_json' ).on( 'click', function( e ) {
e.preventDefault();
appleNewsJSONSubmit( $( this ), 'apple_news_reset_json' );
Expand Down
36 changes: 22 additions & 14 deletions includes/apple-exporter/class-component-factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

namespace Apple_Exporter;

use Apple_Exporter\Components\Component;
use DOMElement;

/**
* This class in in charge of creating components. Manual component
* instantiation should be avoided, use this instead.
Expand Down Expand Up @@ -163,39 +166,44 @@ private static function register_component( $shortname, $classname ) {
/**
* Get a component.
*
* @param string $shortname The short name for the component type to use.
* @param string $html The HTML to be parsed by the component.
* @access public
* @return \Apple_Exporter\Components\Component A component class matching the shortname.
* @param string $shortname The short name for the component type to use.
* @param string $html The HTML to be parsed by the component.
* @param ?Component $parent If provided, treats this component as a subcomponent of this parent.
*
* @return Component A component class matching the shortname.
*/
public static function get_component( $shortname, $html ) {
public static function get_component( $shortname, $html, $parent = null ) {

$class = self::$components[ $shortname ];

if ( is_null( $class ) || ! class_exists( $class ) ) {
return null;
}

return new $class(
$component = new $class(
$html,
self::$workspace,
self::$settings,
self::$styles,
self::$layouts,
null,
self::$component_styles
self::$component_styles,
$parent
);

return $component;
}

/**
* Given a node, returns an array of all the components inside that node. If
* the node is a component itself, returns an array of only one element.
*
* @param \DOMElement $node The node to be examined.
* @access public
* @param DOMElement $node The node to be examined.
* @param ?Component $parent If provided, treats all components as subcomponents of this parent.
*
* @return array An array of components contained in the node.
*/
public static function get_components_from_node( $node ) {
public static function get_components_from_node( $node, $parent = null ) {
/* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */
$result = [];

Expand All @@ -210,25 +218,25 @@ public static function get_components_from_node( $node ) {

/**
* Did we match several components? If so, a hash is returned. Both the
* body and heading components can returns this, in the case they find
* body and heading components can return this, in the case they find
* non-markdown-able elements inside.
*/
if ( is_array( $matched_node ) ) {
foreach ( $matched_node as $base_component ) {
$result[] = self::get_component( $base_component['name'], $base_component['value'] );
$result[] = self::get_component( $base_component['name'], $base_component['value'], $parent );
}
return $result;
}

// We matched a single node.
$html = $node->ownerDocument->saveXML( $matched_node );
$result[] = self::get_component( $shortname, $html );
$result[] = self::get_component( $shortname, $html, $parent );
return $result;
}
// Nothing found. Maybe it's a container element?
if ( $node->hasChildNodes() ) {
foreach ( $node->childNodes as $child ) {
$result = array_merge( $result, self::get_components_from_node( $child, $node ) );
$result = array_merge( $result, self::get_components_from_node( $child, $parent ) );
}
// Remove all nulls from the array.
$result = array_filter( $result );
Expand Down
52 changes: 39 additions & 13 deletions includes/apple-exporter/class-component-spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,31 @@ class Component_Spec {
*/
public $spec;

/**
* The parent component name, if any.
*
* @since 2.5.0
* @var ?string
*/
public $parent;

/**
* Initializes the object with the name, label and the spec.
*
* @param string $component The component name.
* @param string $name The spec name.
* @param string $label The human-readable label for the spec.
* @param array $spec The spec definition.
* @param string $component The component name.
* @param string $name The spec name.
* @param string $label The human-readable label for the spec.
* @param array $spec The spec definition.
* @param ?string $parent The parent component name.
*
* @access public
*/
public function __construct( $component, $name, $label, $spec ) {
public function __construct( $component, $name, $label, $spec, $parent = null ) {
$this->component = $component;
$this->name = $name;
$this->label = $label;
$this->spec = $spec;
$this->parent = $parent;
}

/**
Expand Down Expand Up @@ -311,9 +321,15 @@ public function save( $spec, $theme_name = '' ) {
$theme_settings['json_templates'] = [];
}

// Try to load the custom JSON into the theme.
// Set the JSON template for this component spec.
$component_key = $this->key_from_name( $this->component );
$theme_settings['json_templates'][ $component_key ][ $this->name ] = $json;
if ( $this->parent ) {
$theme_settings['json_templates'][ $this->parent ]['subcomponents'][ $component_key ][ $this->name ] = $json;
} else {
$theme_settings['json_templates'][ $component_key ][ $this->name ] = $json;
}

// Try to load the custom JSON into the theme.
if ( ! $theme->load( $theme_settings ) ) {
Admin_Apple_Notice::error(
sprintf(
Expand Down Expand Up @@ -364,16 +380,21 @@ public function delete( $theme_name = '' ) {
return false;
}

// Determine if this spec override is defined in the theme.
// Remove the JSON template for this component spec or fail if it doesn't exist.
$component_key = $this->key_from_name( $this->component );
$theme_settings = $theme->all_settings();
if ( ! isset( $theme_settings['json_templates'][ $component_key ][ $this->name ] ) ) {
return false;
if ( $this->parent ) {
if ( ! isset( $theme_settings['json_templates'][ $this->parent ]['subcomponents'][ $component_key ][ $this->name ] ) ) {
return false;
}
unset( $theme_settings['json_templates'][ $this->parent ]['subcomponents'][ $component_key ][ $this->name ] );
} else {
if ( ! isset( $theme_settings['json_templates'][ $component_key ][ $this->name ] ) ) {
return false;
}
unset( $theme_settings['json_templates'][ $component_key ][ $this->name ] );
}

// Remove this spec from the theme.
unset( $theme_settings['json_templates'][ $component_key ][ $this->name ] );

// If there are no more overrides for this component, remove it.
if ( empty( $theme_settings['json_templates'][ $component_key ] ) ) {
unset( $theme_settings['json_templates'][ $component_key ] );
Expand Down Expand Up @@ -449,6 +470,11 @@ public function get_override( $theme_name = '' ) {
return null;
}

// Determine if there is a subcomponent override in the theme.
if ( ! empty( $json_templates[ $this->parent ]['subcomponents'][ $this->key_from_name( $this->component ) ][ $this->name ] ) ) {
return $json_templates[ $this->parent ]['subcomponents'][ $this->key_from_name( $this->component ) ][ $this->name ];
}

// Determine if there is an override in the theme.
$component = $this->key_from_name( $this->component );
if ( ! empty( $json_templates[ $component ][ $this->name ] ) ) {
Expand Down
Loading

0 comments on commit b5f8bdc

Please sign in to comment.