# Multiple Authors Per Impact Article - Requirements

## Overview

Currently each `impact_article` node supports a single author via `field_article_author` (entity reference to `authors` taxonomy, cardinality 1) and a single `field_article_bio_override` (text_long, cardinality 1). We need to support multiple authors per article (up to 5), each with their own optional bio override.

## Approach

Change the cardinality of both `field_article_author` and `field_article_bio_override` from 1 to 5. Then use a form alter to visually group Author N + Bio Override N together on the inline entity form.

This avoids introducing a paragraph type and avoids the risk of 3-level IEF nesting (issue form -> article IEF -> paragraph -> author IEF), which is known to be unreliable in Drupal 9.

**Delta pairing convention:** `field_article_author` delta 0 pairs with `field_article_bio_override` delta 0, delta 1 pairs with delta 1, etc.

---

## Environment & Drush

- **Drupal:** 9.5.3
- **Site:** PPS (`public_html/sites/pps/`)
- **Site URI for drush:** `dev.ppsapta.org`
- **Drush command pattern (CLI tool Bash environment):**
  ```
  PATH="/Applications/MAMP/bin/php/php7.4.33/bin:/Applications/MAMP/Library/bin/mysql57/bin:$PATH" /Users/mbowen/cloned/ah_multi/vendor/bin/drush --uri=dev.ppsapta.org {cmd}
  ```
  The CLI tool's Bash environment does not have PHP 7.4 or mysql in PATH by default. You must prepend them as shown above. The user's terminal has the correct PATH and can just run `drush --uri=dev.ppsapta.org {cmd}`.
- **Config management:** This module uses Features (`pps_impact.features.yml` with `required: true`). Config lives in `config/install/`. After making config changes via update hooks or the UI, export with:
  ```
  drush --uri=dev.ppsapta.org features:export pps_impact
  ```
  Then verify the updated YAML files in `config/install/`.

---

## Content Type & Field Context

### Authors Taxonomy (`authors` vocabulary)
Authors are taxonomy terms, not nodes. Each author term has:
- `name` (the author's name)
- `field_author_photo` (image)
- `field_author_bio` (text_long - the default bio)
- `field_author_title` (string)
- `field_author_email` (email)
- `field_author_linkedin`, `field_author_facebook`, `field_author_instagram` (link fields)

The `authors` vocabulary has an **inline** form mode (`taxonomy_term.authors.inline`) used by IEF, showing: name, email, photo, bio, linkedin, facebook, instagram. Config: `core.entity_form_display.taxonomy_term.authors.inline.yml`.

A new **X (formerly Twitter)** social link field (`field_author_x`) needs to be added, matching the pattern of the existing social link fields (link type, cardinality 1, same field_permissions setup). See "New: field_author_x" section below.

### impact_article Content Type
Key fields relevant to this change:
- `field_article_author` - entity_reference to `taxonomy_term` (bundle: `authors`), **currently cardinality 1**
  - Field storage: `field.storage.node.field_article_author.yml`
  - Field instance: `field.field.node.impact_article.field_article_author.yml`
  - On the `inline` form display: uses `inline_entity_form_complex` widget (allows creating new authors and selecting existing ones)
  - On the `default` view display: uses `entity_reference_entity_view`
- `field_article_bio_override` - text_long, **currently cardinality 1**
  - Field storage: `field.storage.node.field_article_bio_override.yml`
  - Field instance: `field.field.node.impact_article.field_article_bio_override.yml`
  - On the `inline` form display: `text_textarea` with 3 rows, placeholder "Optional: Override author bio"
  - Restricted to `plain_text` format via `allowed_formats`

### impact_issue Content Type
This is the **only form where impact_articles are created/edited**, via IEF. The issue form has three article reference fields:
- `field_featured_articles` (4 required for publish)
- `field_departments_articles` (3 required for publish)
- `field_inside_apta` (3 required for publish)

Each uses `inline_entity_form_complex` with `form_mode: inline` for the referenced `impact_article`.

### Form Displays
- `core.entity_form_display.node.impact_article.inline.yml` - The IEF form for articles. This is where `field_article_author` (weight 4) and `field_article_bio_override` (weight 5) appear adjacent. This is the form mode used when editing articles inside the issue form.
- `core.entity_form_display.node.impact_article.default.yml` - The standalone article form (not normally used for editing, but exists).

### View Displays
- `core.entity_view_display.node.impact_article.default.yml` - Shows `field_article_author` (weight 8) and `field_article_bio_override` (weight 9).
- `core.entity_view_display.node.impact_article.teaser.yml` - Teaser display.

---

## Files That Need Changes

### 1. New Config Files: `field_author_x` (X Social Link)

Create two new config files for the X social link field on the `authors` taxonomy, following the exact pattern of `field_author_linkedin`:

**`config/install/field.storage.taxonomy_term.field_author_x.yml`** (modeled on `field_author_linkedin` storage):
```yaml
langcode: en
status: true
dependencies:
  module:
    - field_permissions
    - link
    - taxonomy
third_party_settings:
  field_permissions:
    permission_type: public
id: taxonomy_term.field_author_x
field_name: field_author_x
entity_type: taxonomy_term
type: link
settings: {  }
module: link
locked: false
cardinality: 1
translatable: true
indexes: {  }
persist_with_no_fields: false
custom_storage: false
```

**`config/install/field.field.taxonomy_term.authors.field_author_x.yml`** (modeled on `field_author_linkedin` instance):
```yaml
langcode: en
status: true
dependencies:
  config:
    - field.storage.taxonomy_term.field_author_x
    - taxonomy.vocabulary.authors
  module:
    - link
id: taxonomy_term.authors.field_author_x
field_name: field_author_x
entity_type: taxonomy_term
bundle: authors
label: X
description: 'Url to the author''s X profile.'
required: false
translatable: false
default_value: {  }
default_value_callback: ''
settings:
  title: 0
  link_type: 16
field_type: link
```

Also update these existing config files to include the new field:
- **`core.entity_form_display.taxonomy_term.authors.inline.yml`** - Add `field_author_x` with `link_default` widget (same as other social links), weight it after instagram (weight 7)
- **`core.entity_form_display.taxonomy_term.authors.default.yml`** - Same addition for the default form display
- **`core.entity_view_display.taxonomy_term.authors.default.yml`** - Add `field_author_x` to the view display

After creating/updating all config files, import with `drush --uri=dev.ppsapta.org features:import pps_impact`.

### 2. Config YAML Updates + Features Revert for Cardinality Change

Instead of writing an update hook, update the YAML files directly in `config/install/` and then revert with Features:

1. Edit `field.storage.node.field_article_author.yml` - change `cardinality: 1` to `cardinality: 5`
2. Edit `field.storage.node.field_article_bio_override.yml` - change `cardinality: 1` to `cardinality: 5`
3. Revert features to import the updated config:
   ```
   drush --uri=dev.ppsapta.org features:import pps_impact
   ```
4. Clear cache:
   ```
   drush --uri=dev.ppsapta.org cr
   ```

**Note:** If the features revert fails (Drupal can block cardinality changes on fields with existing data via config import), fall back to writing an update hook (`pps_impact_update_9102`) that changes cardinality programmatically via the field storage config API, then re-export features afterward. The last existing update hook is `pps_impact_update_9101`.

### 3. `config/install/` - Additional Config YAML Files

Beyond the two field storage files updated in step 1, these may also need updates for widget/display settings (can be updated via the UI then re-exported with `drush --uri=dev.ppsapta.org features:export pps_impact`):
- `core.entity_form_display.node.impact_article.inline.yml` - Update `field_article_author` widget type if needed (IEF complex widget should work with multi-value), update `field_article_bio_override` widget
- `core.entity_form_display.node.impact_article.default.yml` - Same consideration
- `core.entity_view_display.node.impact_article.default.yml` - View display for multi-value rendering
- `core.entity_view_display.node.impact_article.teaser.yml` - If author appears here

### 4. `pps_impact.module` - Form Alter & Preprocess

#### Form Grouping (Primary approach: grouped fieldsets)

In the article's IEF form, group each Author + Bio Override pair into a visual fieldset. This is done by adding a `#process` or `#after_build` callback to the inline entity form via the existing `_pps_impact_alter_issue_form()` or `_pps_impact_configure_ief_status()` functions.

**Approach:**
- Add an `#after_build` callback on the IEF widget for each article field
- In the callback, for each delta (0-4), wrap `field_article_author[delta]` and `field_article_bio_override[delta]` in a fieldset labeled "Author 1", "Author 2", etc.

**Fallback approach (if fieldset grouping is too fragile with IEF):**
- Simply relabel the field widget items: "Author 1", "Author 2", etc. and "Bio Override 1", "Bio Override 2", etc. by altering `#title` on each delta's widget element.

#### Preprocess Updates

In `pps_impact_preprocess_node()` (around line 857), update the author variable logic:

**Current code:**
```php
$has_author = $node->hasField('field_article_author') && !$node->get('field_article_author')->isEmpty();
$variables['impact_has_author'] = $has_author;
$variables['impact_author'] = $has_author ? $node->get('field_article_author')->entity : NULL;
```

**New code should:**
- Set `$variables['impact_authors']` as an array of author entities (all deltas)
- Set `$variables['impact_author_bios']` as an array of bio override values (all deltas), keyed by same delta
- Keep `$variables['impact_has_author']` as a boolean (true if any author exists)
- Keep `$variables['impact_author']` pointing to the first author for backward compatibility

#### Validation

There is currently **no required validation on author fields** in the module's publish validation (`_pps_impact_validate_on_publish`). The field instance config has `required: false`. No changes needed for validation unless we want to require at least one author on publish.

### 5. Templates

#### `templates/inc/impact-author.html.twig`

**Current:** Renders a single author section with photo, name, social links, and bio (using `field_article_bio_override` with fallback to `author.field_author_bio`).

**New:** Loop over `impact_authors` array. For each author at index N, use `impact_author_bios[N]` if set, otherwise fall back to that author's `field_author_bio`. The heading should change from "About the Author" to "About the Authors" when there are multiple authors.

The existing markup structure must be preserved. Each author gets its own `impact-article__author-content` div inside the single `impact-article__author-section` wrapper:

```html
<section class="impact-article__author-section">
  <h2 class="impact-article__author-title">About the Author(s)</h2>
  <div class="impact-article__author-content">...</div> <!-- Author 1 -->
  <div class="impact-article__author-content">...</div> <!-- Author 2 -->
</section>
```

Also add the new X social link to the social icons block (alongside linkedin, facebook, instagram, email). The X link markup:

```html
{% if author.field_author_x.value %}
  <a href="{{ author.field_author_x.uri }}" target="_blank" class="impact-article__author-social-icon impact-article__author-social-icon--x" rel="noopener noreferrer">
    <span class="sr-only">X</span>
  </a>
{% endif %}
```

Note: the existing social links use `<i class="fa fa-...">` icons but X uses only a `<span class="sr-only">` — the visible icon will be handled via CSS by the themer targeting `.impact-article__author-social-icon--x`.

#### `templates/node--impact-article--featured.html.twig`

**Current (line 78-82):** Shows byline as `BY {{ node.field_article_author.entity.label }}`

**New:** Loop over `node.field_article_author` and join author names with commas. Example: `BY Author One, Author Two`

#### `templates/node--impact-article--standard.html.twig`

**Current (line 83-87):** Same single-author byline pattern.

**New:** Same multi-author join pattern.

#### `templates/node--impact-issue--full.html.twig`

Multiple locations reference `article.field_article_author.entity.label` for bylines:
- **Line 92-96:** Main featured article byline
- **Line 135-139:** Secondary featured articles byline
- **Line 197-201:** Departments byline (currently commented out)
- **Line 308-311:** Additional sections byline (also references `node.field_article_author` which appears to be a bug - should be `article.field_article_author`)

All of these need to be updated to loop over the multi-value field and join names.

### 6. `src/Plugin/views/field/ImpactArticleDetails.php`

**Current (line 59-73):** The `buildAuthorsText()` method already loops over `field_article_author` with `referencedEntities()` and joins with commas. **This already supports multiple authors - no change needed.**

---

## Testing Plan

1. **Import config changes:** `drush --uri=dev.ppsapta.org features:import pps_impact`
2. **Clear cache:** `drush --uri=dev.ppsapta.org cr`
3. **Verify cardinality:** Check that both fields now accept multiple values:
   ```
   drush --uri=dev.ppsapta.org php-eval "\$storage = \Drupal\field\Entity\FieldStorageConfig::loadByName('node', 'field_article_author'); echo \$storage->getCardinality();"
   ```
5. **Test form UI:**
   - Edit an existing impact_issue node
   - In an article's IEF, verify you can add multiple authors
   - Verify the form grouping (Author 1 + Bio Override 1, Author 2 + Bio Override 2, etc.)
   - Add a second author with a bio override, save
6. **Test display:**
   - View the impact_issue full page - verify bylines show multiple authors
   - View an impact_article full page - verify byline shows multiple authors
   - Verify the "About the Author(s)" section renders each author with correct bio
7. **Test backward compatibility:**
   - Existing articles with a single author should display exactly as before
   - The delta 0 author + bio override should render identically to the old single-value output

---

## Progress Tracker

- [ ] **New config files:** Create `field.storage.taxonomy_term.field_author_x.yml` and `field.field.taxonomy_term.authors.field_author_x.yml`
- [ ] **Update author form/view displays:** Add `field_author_x` to `taxonomy_term.authors.inline`, `taxonomy_term.authors.default` form displays, and `taxonomy_term.authors.default` view display
- [ ] **Update config YAML:** Change `cardinality: 1` to `cardinality: 5` in `field.storage.node.field_article_author.yml` and `field.storage.node.field_article_bio_override.yml`
- [ ] **Features revert:** Run `drush features:import pps_impact` and verify all config changes took effect (if cardinality change fails, fall back to an update hook)
- [ ] **Form grouping:** Add `#after_build` to group Author + Bio Override by delta in the IEF form (with fallback to relabeling if grouping is too fragile)
- [ ] **Preprocess:** Update `pps_impact_preprocess_node()` to provide `impact_authors` and `impact_author_bios` arrays
- [ ] **Template: impact-author.html.twig:** Update to loop over multiple authors with correct bio pairing; add X social link output
- [ ] **Template: impact-article--featured.html.twig:** Update byline for multiple authors
- [ ] **Template: impact-article--standard.html.twig:** Update byline for multiple authors
- [ ] **Template: impact-issue--full.html.twig:** Update all byline locations for multiple authors; fix the `node.field_article_author` bug on line 310 (should be `article.field_article_author`)
- [ ] **Test:** Full round-trip test (update hook -> form -> save -> display)
- [ ] **Test:** Backward compatibility with existing single-author articles
