Full Code of 10up/simple-podcasting for AI

develop f4aed4e51587 cached
100 files
323.6 KB
97.0k tokens
130 symbols
1 requests
Download .txt
Showing preview only (350K chars total). Download the full file or copy to clipboard to get everything.
Repository: 10up/simple-podcasting
Branch: develop
Commit: f4aed4e51587
Files: 100
Total size: 323.6 KB

Directory structure:
gitextract_alta88pg/

├── .distignore
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│   ├── CODEOWNERS
│   └── workflows/
│       ├── build-release-zip.yml
│       ├── close-stale-issues.yml
│       ├── cypress.yml
│       ├── dependency-review.yml
│       ├── php-compatibility.yml
│       ├── phpcs.yml
│       ├── phpunit.yml
│       ├── push-asset-readme-update.yml
│       ├── push-deploy.yml
│       ├── repo-automator.yml
│       └── wordpress-version-checker.yml
├── .gitignore
├── .husky/
│   ├── .gitignore
│   └── pre-commit
├── .nvmrc
├── .phpcs.xml.dist
├── .prettierrc
├── .wordpress-org/
│   └── blueprints/
│       └── blueprint.json
├── .wordpress-version-checker.json
├── .wp-env.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CREDITS.md
├── LICENSE.md
├── README.md
├── assets/
│   ├── css/
│   │   ├── podcasting-edit-term.css
│   │   ├── podcasting-editor-screen.css
│   │   ├── podcasting-onboarding.scss
│   │   └── podcasting-transcript.css
│   └── js/
│       ├── blocks/
│       │   ├── latest-episode/
│       │   │   ├── index.js
│       │   │   └── index.scss
│       │   ├── podcast/
│       │   │   ├── index.js
│       │   │   └── index.scss
│       │   └── podcast-platforms/
│       │       ├── edit.js
│       │       ├── index.js
│       │       └── index.scss
│       ├── blocks.js
│       ├── create-podcast-show.js
│       ├── deprecated.js
│       ├── edit.js
│       ├── onboarding.js
│       ├── podcasting-edit-post.js
│       ├── podcasting-edit-term.js
│       └── transforms.js
├── composer.json
├── includes/
│   ├── admin/
│   │   ├── create-podcast-component.php
│   │   ├── onboarding.php
│   │   └── views/
│   │       ├── onboarding-header.php
│   │       ├── onboarding-page-one.php
│   │       └── onboarding-page-two.php
│   ├── block-patterns.php
│   ├── blocks/
│   │   ├── podcast/
│   │   │   └── markup.php
│   │   └── podcast-transcript/
│   │       ├── cite.js
│   │       ├── edit.js
│   │       ├── formats.js
│   │       ├── index.js
│   │       ├── markup.php
│   │       ├── styles.css
│   │       └── time.js
│   ├── blocks.php
│   ├── create-podcast.php
│   ├── customize-feed.php
│   ├── datatypes.php
│   ├── helpers.php
│   ├── post-meta-box.php
│   ├── rest-external-url.php
│   ├── transcripts.php
│   └── upgrade.php
├── package.json
├── phpunit.xml.dist
├── readme.txt
├── simple-podcasting.php
├── templates/
│   └── transcript.php
├── tests/
│   ├── bin/
│   │   └── set-wp-config.js
│   ├── cypress/
│   │   ├── .eslintrc
│   │   ├── config.config.js
│   │   ├── fixtures/
│   │   │   └── example.json
│   │   ├── integration/
│   │   │   ├── admin.test.js
│   │   │   ├── block.test.js
│   │   │   ├── onboarding.test.js
│   │   │   ├── podcast-setting-panel.test.js
│   │   │   └── taxonomy.test.js
│   │   ├── plugins/
│   │   │   └── index.js
│   │   ├── support/
│   │   │   ├── functions.js
│   │   │   └── index.js
│   │   └── tsconfig.json
│   └── unit/
│       ├── bootstrap.php
│       ├── test-blocks.php
│       ├── test-customize-feed.php
│       ├── test-datatypes.php
│       ├── test-helpers.php
│       ├── test-rest-external-url.php
│       └── test-transcript.php
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .distignore
================================================
# Directories
/.git
/.github
/.husky
/.wordpress-org
/assets
/gulp-tasks
/node_modules
/tests
/vendor

# Files
.*
/CHANGELOG.md
/CODE_OF_CONDUCT.md
/composer.json
/composer.lock
/CONTRIBUTING.md
/CREDITS.md
/gulpfile.babel.js
/LICENSE.md
/package.json
/package-lock.json
/phpunit.xml.dist
/README.md
/webpack.config.js


================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab

[{*.json,*.yml,.babelrc,.bowerrc,.postcssrc}]
indent_style = space
indent_size = 2

[*.txt,wp-config-sample.php]
end_of_line = crlf


================================================
FILE: .eslintignore
================================================
assets/js/frontend/vendor/*.js


================================================
FILE: .eslintrc
================================================
{
	"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
	"ignorePatterns": ["**/vendor/**"]
}


================================================
FILE: .github/CODEOWNERS
================================================
# These owners will be the default owners for everything in the repo. Unless a later match takes precedence, @10up/open-source-practice, as primary maintainers will be requested for review when someone opens a Pull Request.
*                   @10up/open-source-practice

# GitHub and WordPress.org specifics
/.github/           @jeffpaul
/.wordpress-org/    @jeffpaul
CODE_OF_CONDUCT.md  @jeffpaul
LICENSE.md          @jeffpaul


================================================
FILE: .github/workflows/build-release-zip.yml
================================================
name: Build release zip

permissions:
  contents: read

on:
  workflow_dispatch:
  workflow_call:
  push:
   branches:
    - trunk

jobs:
  build:
    name: Build release zip
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Cache node_modules
        id: cache-node-modules
        uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
        env:
          cache-name: cache-node-modules
        with:
          path: node_modules
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}

      - name: Setup node version and npm cache
        uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
        with:
          node-version-file: '.nvmrc'
          cache: 'npm'

      - name: Install Node dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm ci --no-optional

      - name: Build plugin
        run: npm run build

      - name: Generate ZIP file
        uses: 10up/action-wordpress-plugin-build-zip@b9e621e1261ccf51592b6f3943e4dc4518fca0d1 # v1.0.2


================================================
FILE: .github/workflows/close-stale-issues.yml
================================================
name: 'Close stale issues'

# **What it does**: Closes issues where the original author doesn't respond to a request for information.
# **Why we have it**: To remove the need for maintainers to remember to check back on issues periodically to see if contributors have responded.

on:
  schedule:
    # Schedule for every day at 1:30am UTC
    - cron: '30 1 * * *'

permissions:
  issues: write

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
        with:
          days-before-stale: 7
          days-before-close: 7
          stale-issue-message: >
            It has been 7 days since more information was requested from you in this issue and we have not heard back. This issue is now marked as stale and will be closed in 7 days, but if you have more information to add then please comment and the issue will stay open.
          close-issue-message: >
            This issue has been automatically closed because there has been no response
            to our request for more information. With only the
            information that is currently in the issue, we don't have enough information
            to take action. Please reach out if you have or find the answers we need so
            that we can investigate further. See [this blog post on bug reports and the
            importance of repro steps](https://www.lee-dohm.com/2015/01/04/writing-good-bug-reports/)
            for more information about the kind of information that may be helpful.
          stale-issue-label: 'stale'
          close-issue-reason: 'not_planned'
          any-of-labels: 'needs:feedback'
          remove-stale-when-updated: true


================================================
FILE: .github/workflows/cypress.yml
================================================
name: E2E Test

permissions:
  contents: read
  pull-requests: write

on:
  push:
    branches:
      - trunk
      - develop
  pull_request:
    branches:
      - develop

jobs:
  build:
    uses: 10up/simple-podcasting/.github/workflows/build-release-zip.yml@develop

  cypress:
    needs: build
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        core:
          - {name: 'WP latest', version: 'latest'}
          - {name: 'WP trunk', version: 'WordPress/WordPress#master'}
          - {name: 'WP minimum', version: 'WordPress/WordPress#6.6'}

    steps:
    - name: Checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

    - name: Download build zip
      uses: actions/download-artifact@b14cf4c92620c250e1c074ab0a5800e37df86765 # v4.2.0
      with:
        name: ${{ github.event.repository.name }}
        path: ${{ github.event.repository.name }}

    - name: Display structure of downloaded files
      run: ls -R
      working-directory: ${{ github.event.repository.name }}

    - name: Cache node_modules
      id: cache-node-modules
      uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
      env:
        cache-name: cache-node-modules
      with:
        path: |
          node_modules
          ~/.cache
          ~/.npm
        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}

    - name: Install dependencies
      run: npm install

    - name: Set the core version and plugins config
      run: ./tests/bin/set-wp-config.js --core=${{ matrix.core.version }} --plugins=./${{ github.event.repository.name }}

    - name: Set up WP environment
      run: npm run wp-env start
      continue-on-error: ${{ matrix.core.name == 'WP trunk' }}

    - name: Test
      run: npm run cypress:run
      continue-on-error: ${{ matrix.core.name == 'WP trunk' }}

    - name: Update summary
      if: always()
      run: |
          npx mochawesome-merge ./tests/cypress/reports/*.json -o tests/cypress/reports/mochawesome.json
          rm -rf ./tests/cypress/reports/mochawesome-*.json
          npx mochawesome-json-to-md -p ./tests/cypress/reports/mochawesome.json -o ./tests/cypress/reports/mochawesome.md
          npx mochawesome-report-generator tests/cypress/reports/mochawesome.json -o tests/cypress/reports/
          cat ./tests/cypress/reports/mochawesome.md >> $GITHUB_STEP_SUMMARY

    - name: Make artifacts available
      uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
      if: failure()
      with:
        name: cypress-artifact
        retention-days: 2
        path: |
            ${{ github.workspace }}/tests/cypress/screenshots/
            ${{ github.workspace }}/tests/cypress/videos/
            ${{ github.workspace }}/tests/cypress/logs/
            ${{ github.workspace }}/tests/cypress/reports/


================================================
FILE: .github/workflows/dependency-review.yml
================================================
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Reqest, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
name: 'Dependency Review'
on: [pull_request]

permissions:
  contents: read

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout Repository'
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Dependency Review
        uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3
        with:
          license-check: true
          vulnerability-check: false
          config-file: 10up/.github/.github/dependency-review-config.yml@trunk


================================================
FILE: .github/workflows/php-compatibility.yml
================================================
name: PHP Compatibility

permissions:
  contents: read

env:
  COMPOSER_VERSION: "2"
  COMPOSER_CACHE: "${{ github.workspace }}/.composer-cache"

on:
  push:
    branches:
      - develop
      - trunk
  pull_request:
    branches:
      - develop

jobs:
  php_compatibility:
    name: PHP ${{ matrix.php }}
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

    - name: Set standard 10up cache directories
      run: |
        composer config -g cache-dir "${{ env.COMPOSER_CACHE }}"

    - name: Prepare composer cache
      uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
      with:
        path: ${{ env.COMPOSER_CACHE }}
        key: composer-${{ env.COMPOSER_VERSION }}-${{ hashFiles('**/composer.lock') }}
        restore-keys: |
          composer-${{ env.COMPOSER_VERSION }}-

    - name: Set PHP version
      uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0
      with:
        php-version: '7.4'
        coverage: none
        tools: prestissimo, composer:v2

    - name: Install dependencies
      run: composer install

    - name: Check PHP Compatibility
      run: ./vendor/bin/phpcs -p simple-podcasting.php includes --standard=PHPCompatibilityWP --extensions=php --runtime-set testVersion 7.4-


================================================
FILE: .github/workflows/phpcs.yml
================================================
name: PHPCS

permissions:
  contents: read

on:
  push:
    branches:
      - develop
      - trunk
    paths:
      - "**.php"
  pull_request:
    branches:
      - develop
    paths:
      - "**.php"

jobs:
  phpcs:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

    - name: Set PHP version
      uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0
      with:
        php-version: '7.4'
        coverage: none
        tools: composer:v2

    - name: Install dependencies
      run: composer install

    - name: Test
      run: ./vendor/bin/phpcs --runtime-set testVersion 7.4 .


================================================
FILE: .github/workflows/phpunit.yml
================================================
name: Unit Tests

permissions:
  contents: read

env:
  COMPOSER_VERSION: "2"
  COMPOSER_CACHE: "${{ github.workspace }}/.composer-cache"

on:
  push:
    branches:
      - develop
      - trunk
  pull_request:
    branches:
      - develop

jobs:
  phpunit:
    name: ${{ matrix.php.name }}
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        php:
          - {name: 'PHP 7.4', version: '7.4'}
          - {name: 'PHP 8.1', version: '8.1'}

    steps:
    - name: Checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

    - name: Set standard 10up cache directories
      run: |
        composer config -g cache-dir "${{ env.COMPOSER_CACHE }}"

    - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
      with:
        node-version-file: '.nvmrc'

    - name: Prepare composer cache
      uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
      with:
        path: ${{ env.COMPOSER_CACHE }}
        key: composer-${{ env.COMPOSER_VERSION }}-${{ hashFiles('**/composer.lock') }}
        restore-keys: |
          composer-${{ env.COMPOSER_VERSION }}-

    - name: Set PHP version
      uses: shivammathur/setup-php@9e72090525849c5e82e596468b86eb55e9cc5401 # v2.32.0
      with:
        php-version: '${{ matrix.php.version }}'
        coverage: none
        tools: composer:v2

    - name: Install dependencies
      run: composer install && npm install

    - name: Build
      run: npm run build

    - name: Test
      run: ./vendor/bin/phpunit -v


================================================
FILE: .github/workflows/push-asset-readme-update.yml
================================================
name: Plugin asset/readme update

on:
  push:
    branches:
    - trunk

permissions:
  contents: read

jobs:
  trunk:
    name: Push to trunk
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

    - name: install node
      uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
      with:
        node-version-file: .nvmrc

    - name: Build
      run: |
        npm ci
        npm run build

    - name: WordPress.org plugin asset/readme update
      uses: 10up/action-wordpress-plugin-asset-update@2480306f6f693672726d08b5917ea114cb2825f7 # v2.2.0
      env:
        SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
        SVN_USERNAME: ${{ secrets.SVN_USERNAME }}


================================================
FILE: .github/workflows/push-deploy.yml
================================================
name: Deploy to WordPress.org

permissions:
  contents: write
  packages: read
  actions: write

on:
  release:
    types: [published]

jobs:
  tag:
    name: New release
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

    - name: install node
      uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
      with:
        node-version-file: .nvmrc

    - name: Build
      run: |
        npm ci
        npm run build
        npm run makepot

    - name: WordPress Plugin Deploy
      id: deploy
      uses: 10up/action-wordpress-plugin-deploy@54bd289b8525fd23a5c365ec369185f2966529c2 # v2.3.0
      with:
        generate-zip: true
      env:
        SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
        SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}

    - name: Upload release asset
      uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        files: ${{ github.workspace }}/${{ github.event.repository.name }}.zip


================================================
FILE: .github/workflows/repo-automator.yml
================================================
name: 'Repo Automator'

permissions:
  contents: read
  issues: write

on:
  issues:
    types:
      - opened
  push:
    branches:
      - develop
  pull_request:
    types:
      - opened
      - edited
      - synchronize
      - converted_to_draft
      - ready_for_review
    branches:
      - develop

jobs:
  Validate:
    runs-on: ubuntu-latest
    steps:
      - uses: 10up/action-repo-automator@280f5dc0b4ed1b5c50c816e08623bdefce55cdce # v2.1.3
        with:
          fail-label: needs:feedback
          pass-label: needs:code-review
          conflict-label: needs:refresh
          reviewers: |
            team:open-source-practice
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/wordpress-version-checker.yml
================================================
name: "WordPress version checker"

on:
  push:
    branches:
      - develop
      - trunk
  pull_request:
    branches:
      - develop
  schedule:
    - cron: '0 0 * * 1'

permissions:
  issues: write

jobs:
  wordpress-version-checker:
    runs-on: ubuntu-latest
    steps:
      - name: WordPress version checker
        uses: skaut/wordpress-version-checker@9d247334f5b30202cb9c1f4aee74c52f37399f69 # v2.2.3
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
node_modules
bower_components
languages
release
vendor
phpunit.xml
.idea
.phpunit.result.cache

# Project Files
dist
ruleset.xml

# Editors
*.esproj
*.tmproj
*.tmproject
tmtags
.*.sw[a-z]
*.un~
Session.vim
*.swp
*.csv

# Mac OSX
.DS_Store
._*
.Spotlight-V100
.Trashes

# Windows
Thumbs.db
Desktop.ini

# E2E testing
.wp-env.override.json
artifacts
tests/cypress/downloads
tests/cypress/screenshots
tests/cypress/videos
tests/cypress/reports


================================================
FILE: .husky/.gitignore
================================================
_


================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged


================================================
FILE: .nvmrc
================================================
v20


================================================
FILE: .phpcs.xml.dist
================================================
<?xml version="1.0"?>
<ruleset name="Simple Podcasting">
	<rule ref="10up-Default" />
	<file>.</file>
	<exclude-pattern>dist/</exclude-pattern>
	<exclude-pattern>vendor/</exclude-pattern>
	<exclude-pattern>tests/</exclude-pattern>
</ruleset>


================================================
FILE: .prettierrc
================================================
{
	"useTabs": true,
	"printWidth": 90,
	"tabWidth": 4,
	"singleQuote": true
}


================================================
FILE: .wordpress-org/blueprints/blueprint.json
================================================
{
	"$schema": "https://playground.wordpress.net/blueprint-schema.json",
	"landingPage": "\/wp-admin\/admin.php?page=simple-podcasting-onboarding&step=1",
	"preferredVersions": {
		"php": "7.4",
		"wp": "latest"
	},
	"phpExtensionBundles": ["kitchen-sink"],
	"features": {
		"networking": true
	},
	"steps": [
		{
			"step": "login",
			"username": "admin",
			"password": "password"
		},
		{
			"step": "installPlugin",
			"pluginZipFile": {
				"resource": "wordpress.org\/plugins",
				"slug": "simple-podcasting"
			},
			"options": {
				"activate": true
			}
		}
	]
}


================================================
FILE: .wordpress-version-checker.json
================================================
{
    "readme": "readme.txt",
    "channel": "rc"
}


================================================
FILE: .wp-env.json
================================================
{
  "plugins": ["."]
}


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file, per [the Keep a Changelog standard](http://keepachangelog.com/).

## [Unreleased] - TBD

## [1.9.1] - 2025-05-19
**Note that this release bumps the WordPress minimum version from 6.5 to 6.6.**

### Added
- Screenshots for all new features (props [@gabriel-glo](https://github.com/gabriel-glo), [@jeffpaul](https://github.com/jeffpaul), [@Sidsector9](https://github.com/Sidsector9), [@dkotter](https://github.com/dkotter) via [#310](https://github.com/10up/simple-podcasting/pull/310)).

### Changed
- Bump WordPress "tested up to" version to 6.8 (props [@jeffpaul](https://github.com/jeffpaul) via [#335](https://github.com/10up/simple-podcasting/pull/335), [#336](https://github.com/10up/simple-podcasting/pull/336)).
- Bump WordPress minimum from 6.5 to 6.6 (props [@jeffpaul](https://github.com/jeffpaul) via [#335](https://github.com/10up/simple-podcasting/pull/335), [#336](https://github.com/10up/simple-podcasting/pull/336)).

### Fixed
- Issue where podcast feed title unexpectedly adding site title (props [@kirtangajjar](https://github.com/kirtangajjar), [@peterwilsoncc](https://github.com/peterwilsoncc), [@dabowman](https://github.com/dabowman) via [#295](https://github.com/10up/simple-podcasting/pull/295)).

### Security
- Bump `@wordpress/scripts` from 27.9.0 to 30.6.0 (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#328](https://github.com/10up/simple-podcasting/pull/328)).
- Bump `cookie` from 0.4.2 to 0.7.1, `express` from 4.21.0 to 4.21.2, `@wordpress/e2e-test-utils-playwright` from 0.26.0 to 1.18.0, `serialize-javascript` from 6.0.0 to 6.0.2 and `mocha` from 10.4.0 to 11.1.0 (props [@dependabot](https://github.com/apps/dependabot), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#332](https://github.com/10up/simple-podcasting/pull/332)).
- Bump `axios` from 1.7.4 to 1.9.0 and `http-proxy-middleware` from 2.0.6 to 2.0.9 (props [@dependabot](https://github.com/apps/dependabot), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#338](https://github.com/10up/simple-podcasting/pull/338)).

### Developer
- Update all third-party actions our workflows rely on to use versions based on specific commit hashes (props [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter) via [#333](https://github.com/10up/simple-podcasting/pull/333)).
- Adjust `makepot` to only happen during deploy instead of during every prebuild (props [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter) via [#337](https://github.com/10up/simple-podcasting/pull/337)).

## [1.9.0] - 2024-11-18
**Note that this release bumps the WordPress minimum version from 5.7 to 6.5.**

### Added
- New options to the Podcast block to allow for more display customization (props [@barneyjeffries](https://github.com/barneyjeffries), [@Firestorm980](https://github.com/Firestorm980), [@mehidi258](https://github.com/mehidi258), [@jayedul](https://github.com/jayedul), [@Sidsector9](https://github.com/Sidsector9), [@peterwilsoncc](https://github.com/peterwilsoncc), [@faisal-alvi](https://github.com/faisal-alvi), [@gusaus](https://github.com/gusaus), [@jeffpaul](https://github.com/jeffpaul) via [#272](https://github.com/10up/simple-podcasting/pull/272)).

### Changed
- Update the rendering of the Podcast block to be more full featured and use all the newly added customization options (props [@barneyjeffries](https://github.com/barneyjeffries), [@Firestorm980](https://github.com/Firestorm980), [@mehidi258](https://github.com/mehidi258), [@jayedul](https://github.com/jayedul), [@Sidsector9](https://github.com/Sidsector9), [@peterwilsoncc](https://github.com/peterwilsoncc), [@faisal-alvi](https://github.com/faisal-alvi), [@gusaus](https://github.com/gusaus), [@sudar](https://github.com/sudar), [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter) via [#272](https://github.com/10up/simple-podcasting/pull/272), [#318](https://github.com/10up/simple-podcasting/pull/318), [#320](https://github.com/10up/simple-podcasting/pull/320), [#322](https://github.com/10up/simple-podcasting/pull/322)).
- Bump WordPress "tested up to" version to 6.7 (props [@qasumitbagthariya](https://github.com/qasumitbagthariya), [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter), [@sonali886](https://github.com/sonali886), [@godleman](https://github.com/godleman), [@mehul0810](https://github.com/mehul0810) via [#291](https://github.com/10up/simple-podcasting/pull/291), [#307](https://github.com/10up/simple-podcasting/pull/307), [#325](https://github.com/10up/simple-podcasting/pull/325), [#326](https://github.com/10up/simple-podcasting/pull/326)).
- Bump WordPress minimum from 5.7 to 6.5 (props [@qasumitbagthariya](https://github.com/qasumitbagthariya), [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter), [@sonali886](https://github.com/sonali886), [@godleman](https://github.com/godleman), [@mehul0810](https://github.com/mehul0810) via [#291](https://github.com/10up/simple-podcasting/pull/291), [#307](https://github.com/10up/simple-podcasting/pull/307), [#325](https://github.com/10up/simple-podcasting/pull/325), [#326](https://github.com/10up/simple-podcasting/pull/326)).
- Update how we import the `PluginDocumentSettingPanel` component to use the new `@wordpress/editor` package if it exists (props [@gabriel-glo](https://github.com/gabriel-glo), [@dkotter](https://github.com/dkotter) via [#309](https://github.com/10up/simple-podcasting/pull/309)).

### Security
- Bump `express` from 4.18.2 to 4.19.2, `follow-redirects` from 1.15.4 to 1.15.6, and `webpack-dev-middleware` from 5.3.3 to 5.3.4 (props [@dependabot](https://github.com/apps/dependabot), [@iamdharmesh](https://github.com/iamdharmesh) via [#290](https://github.com/10up/simple-podcasting/pull/290)).
- Bump `braces` from 3.0.2 to 3.0.3, `pac-resolver` from 7.0.0 to 7.0.1, `socks` from 2.7.1 to 2.8.3, `ws` from 7.5.9 to 7.5.10 and removes `ip` (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#297](https://github.com/10up/simple-podcasting/pull/297), [#306](https://github.com/10up/simple-podcasting/pull/306)).
- Bump `axios` from 1.7.2 to 1.7.4 (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#312](https://github.com/10up/simple-podcasting/pull/312)).
- Bump `webpack` from 5.91.0 to 5.94.0 (props [@dependabot](https://github.com/apps/dependabot), [@faisal-alvi](https://github.com/faisal-alvi) via [#315](https://github.com/10up/simple-podcasting/pull/315)).
- Bump `ws` from 7.5.10 to 8.18.0, `serve-static` from 1.15.0 to 1.16.2 and `express` from 4.19.2 to 4.21.0 (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#319](https://github.com/10up/simple-podcasting/pull/319)).

### Developer
- Clean up NPM dependencies and update node to v20 (props [@Sidsector9](https://github.com/Sidsector9), [@jeffpaul](https://github.com/jeffpaul), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#275](https://github.com/10up/simple-podcasting/pull/275)).
- Add "Testing" section to the `CONTRIBUTING.md` file (props [@kmgalanakis](https://github.com/kmgalanakis), [@jeffpaul](https://github.com/jeffpaul), [@iamdharmesh](https://github.com/iamdharmesh) via [#294](https://github.com/10up/simple-podcasting/pull/294)).
- Change support level from Active to Stable (props [@Sidsector9](https://github.com/Sidsector9), [@jeffpaul](https://github.com/jeffpaul), [@cadic](https://github.com/cadic) via [#217](https://github.com/10up/simple-podcasting/pull/217)).
- Switch from using `actions/upload-release-asset` to `softprops/action-gh-release` GitHub action (props [@Sidsector9](https://github.com/Sidsector9), [@jeffpaul](https://github.com/jeffpaul) via [#308](https://github.com/10up/simple-podcasting/pull/308)).
- Update repo badges, add WordPress Playground badge (props [@jeffpaul](https://github.com/jeffpaul), [@faisal-alvi](https://github.com/faisal-alvi), [@dkotter](https://github.com/dkotter) via [#311](https://github.com/10up/simple-podcasting/pull/311), [#317](https://github.com/10up/simple-podcasting/pull/317), [#321](https://github.com/10up/simple-podcasting/pull/321)).

## [1.8.0] - 2024-04-03
### Added
- "Latest Podcast Episode" query block variation (props [@jeffpaul](https://github.com/jeffpaul), [@cadic](https://github.com/cadic), [@barneyjeffries](https://github.com/barneyjeffries), [@faisal-alvi](https://github.com/faisal-alvi) via [#266](https://github.com/10up/simple-podcasting/pull/266)).
- Ability to add Unique Cover Art for Episodes (props [@jamesburgos](https://github.com/jamesburgos), [@jeffpaul](https://github.com/jeffpaul), [@zamanq](https://github.com/zamanq), [@iamdharmesh](https://github.com/iamdharmesh) via [#273](https://github.com/10up/simple-podcasting/pull/273)).
- `simple_podcasting_feed_title` filter hook to modify feed title (props [@martinburch](https://github.com/martinburch), [@psorensen](https://github.com/psorensen), [@dkotter](https://github.com/dkotter) via [#279](https://github.com/10up/simple-podcasting/pull/279)).

### Fixed
- Incorrect feed title (props [@martinburch](https://github.com/martinburch), [@psorensen](https://github.com/psorensen), [@dkotter](https://github.com/dkotter) via [#279](https://github.com/10up/simple-podcasting/pull/279)).
- Fatal error in WordPress 5.8 and earlier (props [@peterwilsoncc](https://github.com/peterwilsoncc), [@Sidsector9](https://github.com/Sidsector9) via [#277](https://github.com/10up/simple-podcasting/pull/277)).

### Changed
- Disabled auto sync pull requests with target branch (props [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#281](https://github.com/10up/simple-podcasting/pull/281)).
- Removed `PULL_REQUEST_TEMPLATE.md` template (props [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#286](https://github.com/10up/simple-podcasting/pull/286)).
- Replaced [lee-dohm/no-response](https://github.com/lee-dohm/no-response) with [actions/stale](https://github.com/actions/stale) to help with closing no-response/stale issues (props [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter) via [#287](https://github.com/10up/simple-podcasting/pull/287)).
- Upgrade the download-artifact from v3 to v4 (props [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#285](https://github.com/10up/simple-podcasting/pull/285)).

### Security
- Bumps `ip` from `1.1.8` to `1.1.9` (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#278](https://github.com/10up/simple-podcasting/pull/278)).

## [1.7.0] - 2024-01-16
### Added
- Ability to add a transcript to a podcast episode by utilizing a new Podcast Transcript block. This block is added by clicking the `Add Transcript` button that will now show in the sidebar panel of the Podcast block (props [@nateconley](https://github.com/nateconley), [@peterwilsoncc](https://github.com/peterwilsoncc), [@sksaju](https://github.com/sksaju), [@kirtangajjar](https://github.com/kirtangajjar) via [#221](https://github.com/10up/simple-podcasting/pull/221)).
- Support for the WordPress.org plugin preview (props [@dkotter](https://github.com/dkotter), [@jeffpaul](https://github.com/jeffpaul) via [#265](https://github.com/10up/simple-podcasting/pull/265)).

### Fixed
- Ensure we show all Podcasting terms in the Block Editor sidebar (props [@dkotter](https://github.com/dkotter), [@channchetra](https://github.com/channchetra), [@Sidsector9](https://github.com/Sidsector9) via [#268](https://github.com/10up/simple-podcasting/pull/268)).

### Security
- Bump `axios` from 0.25.0 to 1.6.2 and `@wordpress/scripts` from 26.9.0 to 26.18.0 (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#263](https://github.com/10up/simple-podcasting/pull/263)).
- Bump `follow-redirects` from 1.15.3 to 1.15.4 (props [@dependabot](https://github.com/apps/dependabot), [@dkotter](https://github.com/dkotter) via [#269](https://github.com/10up/simple-podcasting/pull/269)).

## [1.6.1] - 2023-11-21
### Added
- Repo Automator GitHub Action (props [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#253](https://github.com/10up/simple-podcasting/pull/253)).

### Changed
- Bump WordPress "tested up to" version to 6.4 (props [@qasumitbagthariya](https://github.com/qasumitbagthariya), [@jeffpaul](https://github.com/jeffpaul) via [#259](https://github.com/10up/simple-podcasting/pull/259), [#260](https://github.com/10up/simple-podcasting/pull/260)).
- Ensure end-to-end tests work on Cypress v13 and bump `cypress` from 11.2.0 to 13.2.0, `@10up/cypress-wp-utils` from 0.1.0 to 0.2.0, `@wordpress/env` from 5.4.0 to 8.7.0, `cypress-localstorage-commands` from 2.2.2 to 2.2.4 and `cypress-mochawesome-reporter` from 3.4.0 to 3.6.0 (props [@iamdharmesh](https://github.com/iamdharmesh), [@Sidsector9](https://github.com/Sidsector9) via [#254](https://github.com/10up/simple-podcasting/pull/254)).

### Security
- Bump `postcss` from 8.4.27 to 8.4.31 (props [@dependabot](https://github.com/apps/dependabot), [@faisal-alvi](https://github.com/faisal-alvi) via [#256](https://github.com/10up/simple-podcasting/pull/256)).
- Bump `@babel/traverse` from 7.22.8 to 7.23.2 (props [@dependabot](https://github.com/apps/dependabot), [@Sidsector9](https://github.com/Sidsector9) via [#257](https://github.com/10up/simple-podcasting/pull/257)).

## [1.6.0] - 2023-08-31
### Added
- Ability to create a Podcast from within the Block Editor (props [@Sidsector9](https://github.com/Sidsector9), [@iamdharmesh](https://github.com/iamdharmesh) via [#232](https://github.com/10up/simple-podcasting/pull/232)).
- New Podcast Platforms block that allows you to display icons and links to multiple podcast platforms (props [@Sidsector9](https://github.com/Sidsector9), [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#241](https://github.com/10up/simple-podcasting/pull/241)).
- Check for minimum required PHP version before loading the plugin (props [@kmgalanakis](https://github.com/kmgalanakis), [@dkotter](https://github.com/dkotter) via [#248](https://github.com/10up/simple-podcasting/pull/248)).

### Changed
- Rename `TAXONOMY_NAME` constant to `PODCASTING_TAXONOMY_NAME` (props [@jayedul](https://github.com/jayedul), [@peterwilsoncc](https://github.com/peterwilsoncc), [@dkotter](https://github.com/dkotter) via [#238](https://github.com/10up/simple-podcasting/pull/238)).
- Bump WordPress "tested up to" version to 6.3 (props [@dkotter](https://github.com/dkotter) via [#248](https://github.com/10up/simple-podcasting/pull/248)).

### Fixed
- Resolved a PHP warning when creating a new podcast (props [@kmgalanakis](https://github.com/kmgalanakis), [@iamdharmesh](https://github.com/iamdharmesh) via [#247](https://github.com/10up/simple-podcasting/pull/247)).

### Security
- Bump `word-wrap` from 1.2.3 to 1.2.4 (props [@dependabot](https://github.com/apps/dependabot), [@iamdharmesh](https://github.com/iamdharmesh) via [#243](https://github.com/10up/simple-podcasting/pull/243)).

## [1.5.0] - 2023-06-29
### Added
- Post Grid Block to display a grid of episode posts (props [@mehul0810](https://github.com/mehul0810), [@cadic](https://github.com/cadic), [@nateconley](https://github.com/nateconley), [@dkotter](https://github.com/dkotter), [@jeffpaul](https://github.com/jeffpaul), [@ajmaurya99](https://github.com/ajmaurya99), [@nickolas-kola](https://github.com/nickolas-kola), [@achchu93](https://github.com/achchu93) via [#214](https://github.com/10up/simple-podcasting/pull/214)).
- Mochawesome reporter added for Cypress end-to-end test report (props [@jayedul](https://github.com/jayedul), [@iamdharmesh](https://github.com/iamdharmesh) via [#236](https://github.com/10up/simple-podcasting/pull/236)).

### Changed
- Mark any required fields when adding/editing a podcast feed (props [@mehul0810](https://github.com/mehul0810), [@cadic](https://github.com/cadic), [@nateconley](https://github.com/nateconley), [@jeffpaul](https://github.com/jeffpaul), [@Spoygg](https://github.com/Spoygg), [@ggutenberg](https://github.com/ggutenberg), [@peterwilsoncc](https://github.com/peterwilsoncc), [@Sidsector9](https://github.com/Sidsector9), [@ravinderk](https://github.com/ravinderk), [@faisal-alvi](https://github.com/faisal-alvi), [@helen](https://github.com/helen) via [#216](https://github.com/10up/simple-podcasting/pull/216)).
- Bumped WordPress "tested up to" version 6.2 (props [@jayedul](https://github.com/jayedul), [@peterwilsoncc](https://github.com/peterwilsoncc), [@jeffpaul](https://github.com/jeffpaul) via [#230](https://github.com/10up/simple-podcasting/pull/230)).
- Run end-to-end tests on the zip generated by the "Build Release ZIP" GitHub Action (props [@jayedul](https://github.com/jayedul), [@Sidsector9](https://github.com/Sidsector9), [@iamdharmesh](https://github.com/iamdharmesh) via [#227](https://github.com/10up/simple-podcasting/pull/227)).
- GitHub Action `uses` updates (props [@Sidsector9](https://github.com/Sidsector9), [@iamdharmesh](https://github.com/iamdharmesh) via [#234](https://github.com/10up/simple-podcasting/pull/234)).
- Updated Dependency Review GitHub Action (props [@jeffpaul](https://github.com/jeffpaul), [@Sidsector9](https://github.com/Sidsector9) via [#237](https://github.com/10up/simple-podcasting/pull/237)).

### Removed
- Deprecated `<itunes:summary>` tag (props [@ggutenberg](https://github.com/ggutenberg), [@Sidsector9](https://github.com/Sidsector9), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul) via [#223](https://github.com/10up/simple-podcasting/pull/223)).
- Unnecessary term meta registration on "init" (props [@kmgalanakis](https://github.com/kmgalanakis), [@faisal-alvi](https://github.com/faisal-alvi), [@cadic](https://github.com/cadic) via [#225](https://github.com/10up/simple-podcasting/pull/225)).

### Fixed
- Deprecation notices for `strpos` and `str_replace` on PHP >= 8.1 (props [@bmarshall511](https://github.com/bmarshall511), [@Sidsector9](https://github.com/Sidsector9), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#239](https://github.com/10up/simple-podcasting/pull/239)).

### Security
- Bump `simple-git` from 3.15.1 to 3.16.0 (props [@dependabot](https://github.com/apps/dependabot), [@cadic](https://github.com/cadic) via [#215](https://github.com/10up/simple-podcasting/pull/215)).
- Bump `http-cache-semantics` from 4.1.0 to 4.1.1 (props [@dependabot](https://github.com/apps/dependabot), [@cadic](https://github.com/cadic) via [#219](https://github.com/10up/simple-podcasting/pull/219)).
- Bump `@sideway/formula` from 3.0.0 to 3.0.1 (props [@dependabot](https://github.com/apps/dependabot), [@cadic](https://github.com/cadic) via [#220](https://github.com/10up/simple-podcasting/pull/220)).
- Bump `webpack` from 5.75.0 to 5.76.1 (props [@dependabot](https://github.com/apps/dependabot), [@faisal-alvi](https://github.com/faisal-alvi) via [#222](https://github.com/10up/simple-podcasting/pull/222)).

## [1.4.0] - 2023-01-23
### Added
- New podcast onboarding flow (props [@Sidsector9](https://github.com/Sidsector9), [@cadic](https://github.com/cadic), [@iamdharmesh](https://github.com/iamdharmesh), [@helen](https://github.com/helen), [@jeffpaul](https://github.com/jeffpaul), [@Nicolas-knight](https://github.com/Nicolas-knight), [@jnetek](https://github.com/jnetek) via [#193](https://github.com/10up/simple-podcasting/pull/193)).
- Description field to RSS feed (props [@supersmo](https://github.com/supersmo), [@cadic](https://github.com/cadic) via [#204](https://github.com/10up/simple-podcasting/pull/204)).
- Build pre-release zip GitHub Action (props [@Sidsector9](https://github.com/Sidsector9), [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter), [@faisal-alvi](https://github.com/faisal-alvi), [@vikrampm1](https://github.com/vikrampm1) via [#199](https://github.com/10up/simple-podcasting/pull/199)).

### Changed
- Bump Wordpress "tested up to" to 6.1 (props [@jayedul](https://github.com/jayedul), [@dkotter](https://github.com/dkotter) via [#201](https://github.com/10up/simple-podcasting/pull/201)).
- Cypress integration migrated to 11+ (props [@jayedul](https://github.com/jayedul), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul) via [#205](https://github.com/10up/simple-podcasting/pull/205)).
- Updated docs to add podcast feed to Pocket Casts (props [@jeffpaul](https://github.com/jeffpaul), [@Sidsector9](https://github.com/Sidsector9), [@cadic](https://github.com/cadic) via [#192](https://github.com/10up/simple-podcasting/pull/192)).

### Fixed
- Spotify not accepting feeds with empty `<description>` field (props [@supersmo](https://github.com/supersmo), [@cadic](https://github.com/cadic) via [#204](https://github.com/10up/simple-podcasting/pull/204)).

### Security
- Bump `json5` from 1.0.1 to 1.0.2 (props [@dependabot[bot]](https://github.com/apps/dependabot), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul) via [#212](https://github.com/10up/simple-podcasting/pull/212)).
- Bump `loader-utils` from 2.0.2 to 2.0.4 (props [@dependabot[bot]](https://github.com/apps/dependabot), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul) via [#195](https://github.com/10up/simple-podcasting/pull/195), [#198](https://github.com/10up/simple-podcasting/pull/198)).
- Bump `simple-git` from 3.14.1 to 3.15.1 (props [@dependabot[bot]](https://github.com/apps/dependabot), [@jeffpaul](https://github.com/jeffpaul) via [#202](https://github.com/10up/simple-podcasting/pull/202)).

## [1.3.0] - 2022-10-18
**Note that this version bumps the minimum PHP version from 7.0 to 7.4 and the minimum WordPress version from 4.6 to 5.7.**

### Added
- Podcasts Taxonomy term(s) added in block settings (props [@helen](https://github.com/helen), [@jeffpaul](https://github.com/jeffpaul), [@faisal-alvi](https://github.com/faisal-alvi), [@peterwilsoncc](https://github.com/peterwilsoncc), [@cadic](https://github.com/cadic) via [#183](https://github.com/10up/simple-podcasting/pull/183)).
- Type of show setting for the podcast (props [@cadic](https://github.com/cadic), [@faisal-alvi](https://github.com/faisal-alvi), [@jeffpaul](https://github.com/jeffpaul) via [#188](https://github.com/10up/simple-podcasting/pull/188)).

### Changed
- Podcasting Categories and Sub-Categories (props [@zamanq](https://github.com/zamanq), [@jeffpaul](https://github.com/jeffpaul), [@dkotter](https://github.com/dkotter), [@cadic](https://github.com/cadic), [@dchucks](https://github.com/dchucks) via [#179](https://github.com/10up/simple-podcasting/pull/179)).
- Bumped minimum PHP version required from 7.0 to 7.4 (props [@peterwilsoncc](https://github.com/peterwilsoncc), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul), [@vikrampm1](https://github.com/vikrampm1) via [#184](https://github.com/10up/simple-podcasting/pull/184)).
- Bumped minimum WordPress version required from 4.6 to 5.7 (props [@peterwilsoncc](https://github.com/peterwilsoncc), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul), [@vikrampm1](https://github.com/vikrampm1) via [#184](https://github.com/10up/simple-podcasting/pull/184)).
- Upgrade dependencies (props [@cadic](https://github.com/cadic), [@faisal-alvi](https://github.com/faisal-alvi) via [#187](https://github.com/10up/simple-podcasting/pull/187)).

### Fixed
- Saving podcast enclosure with Classic Editor (props [@cadic](https://github.com/cadic), [@faisal-alvi](https://github.com/faisal-alvi) via [#186](https://github.com/10up/simple-podcasting/pull/186)).

### Security
- Bump `got` from 10.7.0 to 11.8.5 (props [@faisal-alvi](https://github.com/faisal-alvi), [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#185](https://github.com/10up/simple-podcasting/pull/185)).
- Bump `@wordpress/env` from 4.5.0 to 5.2.0 (props [@faisal-alvi](https://github.com/faisal-alvi), [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#185](https://github.com/10up/simple-podcasting/pull/185)).

## [1.2.4] - 2022-07-27
### Added
- Season number, episode number and episode type attributes can now be stored with a Podcast (props [@zamanq](https://github.com/zamanq), [@dchucks](https://github.com/dchucks), [@cadic](https://github.com/cadic) via [#175](https://github.com/10up/simple-podcasting/pull/175)).

### Changed
- Bump WordPress version "tested up to" 6.0 (props [@cadic](https://github.com/cadic) via [#171](https://github.com/10up/simple-podcasting/issues/171)).

### Fixed
- Incorrect Language value in the Feed (props [@zamanq](https://github.com/zamanq), [@dchucks](https://github.com/dchucks), [@cadic](https://github.com/cadic) via [#176](https://github.com/10up/simple-podcasting/pull/176)).

### Security
- Bump `terser` from 5.12.1 to 5.14.2 (props [@dependabot](https://github.com/apps/dependabot) via [#180](https://github.com/10up/simple-podcasting/pull/180)).

## [1.2.3] - 2022-04-28
### Added
- Compatibility tests against PHP 7 and 8 (props [@cadic](https://github.com/cadic), [@dkotter](https://github.com/dkotter), [@jeffpaul](https://github.com/jeffpaul) via [#150](https://github.com/10up/simple-podcasting/pull/150)).
- Default Pull Request Reviewers via CODEOWNERS file (props [@jeffpaul](https://github.com/jeffpaul), [@cadic](https://github.com/cadic) via [#156](https://github.com/10up/simple-podcasting/pull/156)).
- Dependency security scanning (props [@jeffpaul](https://github.com/jeffpaul) via [#168](https://github.com/10up/simple-podcasting/pull/168)).

### Changed
- Unit tests against PHP 8 (props [@cadic](https://github.com/cadic), [@dkotter](https://github.com/dkotter), [@jeffpaul](https://github.com/jeffpaul) via [#150](https://github.com/10up/simple-podcasting/pull/150)).
- Bump required PHP 7.0 (props [@cadic](https://github.com/cadic), [@dkotter](https://github.com/dkotter), [@jeffpaul](https://github.com/jeffpaul) via [#150](https://github.com/10up/simple-podcasting/pull/150)).
- Replaced custom commands with @10up/cypress-wp-utils in end-to-end tests (props [@dinhtungdu](https://github.com/dinhtungdu), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul) via [#162](https://github.com/10up/simple-podcasting/pull/162)).

### Fixed
- Missing `<enclosure>` in feed item (props [@davexpression](https://github.com/davexpression), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul) via [#147](https://github.com/10up/simple-podcasting/pull/147)).
- Failing Cypress test on WP Minimum (props [@dinhtungdu](https://github.com/dinhtungdu), [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul) via [#164](https://github.com/10up/simple-podcasting/pull/164)).
- Updated badges in readme (props [@cadic](https://github.com/cadic), [@jeffpaul](https://github.com/jeffpaul) via [#167](https://github.com/10up/simple-podcasting/pull/167)).

### Security
- Upgraded node dependencies (props [@cadic](https://github.com/cadic), [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#158](https://github.com/10up/simple-podcasting/pull/158) and [#163](https://github.com/10up/simple-podcasting/pull/163)).
- Bump async from 2.6.3 to 2.6.4 (props [@dependabot](https://github.com/apps/dependabot) via [#166](https://github.com/10up/simple-podcasting/pull/166)).
- Bump node-forge from 1.2.1 to 1.3.0 (props [@dependabot](https://github.com/apps/dependabot) via [#160](https://github.com/10up/simple-podcasting/pull/160)).
- Bump minimist from 1.2.5 to 1.2.6 (props [@dependabot](https://github.com/apps/dependabot) via [#159](https://github.com/10up/simple-podcasting/pull/159)).

## [1.2.2] - 2022-03-01
### Added
- Filter `simple_podcasting_feed_item` to modify RSS feed item data before output (props [@cadic](https://github.com/cadic), [@iamdharmesh](https://github.com/iamdharmesh), [@jeffpaul](https://github.com/jeffpaul) via [#144](https://github.com/10up/simple-podcasting/pull/144)).
- Unit tests (props [@cadic](https://github.com/cadic) via [#142](https://github.com/10up/simple-podcasting/pull/142), [@dkotter](https://github.com/dkotter), [@jeffpaul](https://github.com/jeffpaul)).
- GitHub action job to run PHPCS (props [@cadic](https://github.com/cadic), [@dkotter](https://github.com/dkotter) via [#136](https://github.com/10up/simple-podcasting/pull/136)).
- Auto-create pot file in languages folder during the build process (props [@dkotter](https://github.com/dkotter), [@cadic](https://github.com/cadic) via [#131](https://github.com/10up/simple-podcasting/pull/131)).

### Changed
- Bump WordPress "tested up to" version 5.9 (props [@sudip-10up](https://github.com/sudip-10up), [@cadic](https://github.com/cadic), [@peterwilsoncc](https://github.com/peterwilsoncc) via [#140](https://github.com/10up/simple-podcasting/pull/140)).

### Fixed
- End-to-end tests with WordPress 5.9 element IDs (props[@cadic](https://github.com/cadic), [@felipeelia](https://github.com/felipeelia), [@dinhtungdu](https://github.com/dinhtungdu) via [#146](https://github.com/10up/simple-podcasting/pull/146)).
- Podcast feed link output on Edit Podcast screen (props [@mehidi258](https://github.com/mehidi258), [@jeffpaul](https://github.com/jeffpaul), [@cadic](https://github.com/cadic) via [#139](https://github.com/10up/simple-podcasting/pull/139)).
- Bug fix for `is_feed` being called too early (props [@tomjn](https://github.com/tomjn), [@jeffpaul](https://github.com/jeffpaul) via [#135](https://github.com/10up/simple-podcasting/pull/135)).
- Missing and incorrect text-domain (props [@dkotter](https://github.com/dkotter), [@cadic](https://github.com/cadic) via [#131](https://github.com/10up/simple-podcasting/pull/131)).

### Security
- Bump `nanoid` from 3.1.25 to 3.2.0 (props [@dependabot](https://github.com/apps/dependabot) via [#143](https://github.com/10up/simple-podcasting/pull/143)).

## [1.2.1] - 2021-12-16
### Added
- Filter `simple_podcasting_episodes_per_page` to override default of 250 episodes per podcast feed (props [@pabamato](https://github.com/pabamato), [@dinhtungdu](https://github.com/dinhtungdu), [@monomo111](https://github.com/monomo111), [@jeffpaul](https://github.com/jeffpaul), [@jakemgold](https://github.com/jakemgold) via [#109](https://github.com/10up/simple-podcasting/pull/109)).
- End-to-end testing using Cypress and `wp-env` (props [@dinhtungdu](https://github.com/dinhtungdu), [@markjaquith](https://github.com/markjaquith), [@youknowriad](https://github.com/youknowriad), [@helen](https://github.com/helen) via [#115](https://github.com/10up/simple-podcasting/pull/115), [#117](https://github.com/10up/simple-podcasting/pull/117)).
- Issue management automation via GitHub Actions (props [@jeffpaul](https://github.com/jeffpaul) via [#119](https://github.com/10up/simple-podcasting/pull/119)).
- Pull request template (props [@jeffpaul](https://github.com/jeffpaul), [@dinhtungdu](https://github.com/dinhtungdu) via [#125](https://github.com/10up/simple-podcasting/pull/125)).

### Changed
- Default number of episodes in RSS feeds increased from 10 to 250 (props [@pabamato](https://github.com/pabamato), [@dinhtungdu](https://github.com/dinhtungdu), [@monomo111](https://github.com/monomo111), [@jeffpaul](https://github.com/jeffpaul), [@jakemgold](https://github.com/jakemgold) via [#109](https://github.com/10up/simple-podcasting/pull/109)).
- Use `@wordpress/scripts` as the build tool (props [@dinhtungdu](https://github.com/dinhtungdu) via [#114](https://github.com/10up/simple-podcasting/pull/114)).
- Bump WordPress version “tested up to” 5.8.1 (props [David Chabbi](https://www.linkedin.com/in/david-chabbi-985719b4/), [@jeffpaul](https://github.com/jeffpaul), [@pabamato](https://github.com/pabamato) via  [#106](https://github.com/10up/simple-podcasting/pull/106), [#110](https://github.com/10up/simple-podcasting/pull/110), [#124](https://github.com/10up/simple-podcasting/pull/124)).
- Documentation updates (props [@meszarosrob](https://github.com/meszarosrob), [@dinhtungdu](https://github.com/dinhtungdu) via [#101](https://github.com/10up/simple-podcasting/pull/101)).

### Fixed
- 'podcast' block core dependency  (props [@pabamato](https://github.com/pabamato), [@dinhtungdu](https://github.com/dinhtungdu), [@monomo111](https://github.com/monomo111), [@jeffpaul](https://github.com/jeffpaul), [@jakemgold](https://github.com/jakemgold) via [#109](https://github.com/10up/simple-podcasting/pull/109)).
- Minimum WordPress version used by `wp-env` (props [@dinhtungdu](https://github.com/dinhtungdu) via [#122](https://github.com/10up/simple-podcasting/pull/122)).

## [1.2.0] - 2020-07-10
### Added
- Podcast image in the taxonomy list table view (props [@Firestorm980](https://github.com/Firestorm980), [@helen](https://github.com/helen) via [#87](https://github.com/10up/simple-podcasting/pull/87)).
- Ability for user to transform to/from the podcast and audio blocks (props [@Firestorm980](https://github.com/Firestorm980), [@helen](https://github.com/helen) via [#85](https://github.com/10up/simple-podcasting/pull/85)).
- Core `MediaReplaceFlow` to edit the podcast media (props [@Firestorm980](https://github.com/Firestorm980), [@helen](https://github.com/helen) via [#86](https://github.com/10up/simple-podcasting/pull/86)).

### Changed
- GitHub Actions from HCL to YAML workflow syntax (props [@helen](https://github.com/helen) via [#78](https://github.com/10up/simple-podcasting/pull/78)).
- Stop committing built files (props [@helen](https://github.com/helen) via [#95](https://github.com/10up/simple-podcasting/pull/95)).
- Documentation updates (props [@jeffpaul](https://github.com/jeffpaul), [@nhalstead](https://github.com/nhalstead) via [#76](https://github.com/10up/simple-podcasting/pull/76), [#79](https://github.com/10up/simple-podcasting/pull/79)).

### Fixed
- Using the upload or drag and drop instead of media library populates duration and mimetype (props [@Firestorm980](https://github.com/Firestorm980), [@helen](https://github.com/helen) via [#82](https://github.com/10up/simple-podcasting/pull/82)).
- Issue where it is possible to add non-audio files to the Podcast block (props [@mattheu](https://github.com/mattheu) via [#77](https://github.com/10up/simple-podcasting/pull/77)).
- Issue where React would throw an error relating to keys for list items (props [@Firestorm980](https://github.com/Firestorm980), [@helen](https://github.com/helen) via [#85](https://github.com/10up/simple-podcasting/pull/85)).
- Ensure podcast-related meta is deleted after block is removed. (props [@dinhtungdu](https://github.com/dinhtungdu) via [#96](https://github.com/10up/simple-podcasting/pull/96)).

## [1.1.1] - 2019-08-02
### Added
- GitHub Actions for WordPress.org plugin deploy (props [@helen](https://github.com/helen) via [#75](https://github.com/10up/simple-podcasting/pull/75)).

### Fixed
- Compatibility with WordPress 5.2 (props [@adamsilverstein](https://github.com/adamsilverstein) via [#68](https://github.com/10up/simple-podcasting/pull/68), [#70](https://github.com/10up/simple-podcasting/pull/70)).
- Corrected `10up/wp_mock` reference for Composer (props [@oscarssanchez](https://github.com/oscarssanchez) via [#69](https://github.com/10up/simple-podcasting/pull/69)).

## [1.1.0] - 2018-12-04
### Added
- Retrieve metadata for externally hosted audio files in the block editor.
- Specify email address for a given podcast.
- Set language for a given podcast.
- Developers: Add linting for coding standards.

### Changed
- Clearer language on the add new podcast form.

### Fixed
- Delete all associated meta when block is removed from a post.
- Restore all block editor functionality to align with Gutenberg/block changes.
- Fully clear add new form after creating a new podcast.

## [1.0.1] - 2018-07-02
### Fixed
- Properly output podcast categories and subcategories in the feed.
- Avoid a minified JS error when selecting a podcast image.
- Display podcast summary on edit form.

## [1.0.0] - 2018-06-29
- Initial plugin release.

[Unreleased]: https://github.com/10up/simple-podcasting/compare/trunk...develop
[1.9.1]: https://github.com/10up/simple-podcasting/compare/1.9.0...1.9.1
[1.9.0]: https://github.com/10up/simple-podcasting/compare/1.8.0...1.9.0
[1.8.0]: https://github.com/10up/simple-podcasting/compare/1.7.0...1.8.0
[1.7.0]: https://github.com/10up/simple-podcasting/compare/1.6.1...1.7.0
[1.6.1]: https://github.com/10up/simple-podcasting/compare/1.6.0...1.6.1
[1.6.0]: https://github.com/10up/simple-podcasting/compare/1.5.0...1.6.0
[1.5.0]: https://github.com/10up/simple-podcasting/compare/1.4.0...1.5.0
[1.4.0]: https://github.com/10up/simple-podcasting/compare/1.3.0...1.4.0
[1.3.0]: https://github.com/10up/simple-podcasting/compare/1.2.4...1.3.0
[1.2.4]: https://github.com/10up/simple-podcasting/compare/1.2.3-deploy...1.2.4
[1.2.3]: https://github.com/10up/simple-podcasting/compare/1.2.2...1.2.3-deploy
[1.2.2]: https://github.com/10up/simple-podcasting/compare/1.2.1...1.2.2
[1.2.1]: https://github.com/10up/simple-podcasting/compare/1.2.0...1.2.1
[1.2.0]: https://github.com/10up/simple-podcasting/compare/1.1.1...1.2.0
[1.1.1]: https://github.com/10up/simple-podcasting/compare/f8a958c...1.1.1
[1.1.0]: https://github.com/10up/simple-podcasting/compare/1.0.1...f8a958c
[1.0.1]: https://github.com/10up/simple-podcasting/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/10up/simple-podcasting/releases/tag/1.0.0


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
 advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
 address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
 professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at opensource@10up.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing and Maintaining

First, thank you for taking the time to contribute!

The following is a set of guidelines for contributors as well as information and instructions around our maintenance process. The two are closely tied together in terms of how we all work together and set expectations, so while you may not need to know everything in here to submit an issue or pull request, it's best to keep them in the same document.

## Ways to contribute

Contributing isn't just writing code - it's anything that improves the project. All contributions for Simple Podcasting are managed right here on GitHub. Here are some ways you can help:

### Reporting bugs

If you're running into an issue with the plugin, please take a look through [existing issues](https://github.com/10up/simple-podcasting/issues) and [open a new one](https://github.com/10up/simple-podcasting/issues/new) if needed. If you're able, include steps to reproduce, environment information, and screenshots/screencasts as relevant.

### Suggesting enhancements

New features and enhancements are also managed via [issues](https://github.com/10up/simple-podcasting/issues). As project owners, 10up sets the direction and roadmap and may not prioritize or decide to implement if outside of the main goals of the plugin.

### Pull requests

Pull requests represent a proposed solution to a specified problem. They should always reference an issue that describes the problem and contains discussion about the problem itself. Discussion on pull requests should be limited to the pull request itself, i.e. code review.

For more on how 10up writes and manages code, check out our [10up Engineering Best Practices](https://10up.github.io/Engineering-Best-Practices/).

### Testing

Helping to test an open source project and provide feedback on success or failure of those tests is also a helpful contribution.  You can find details on the Critical Flows and Test Cases in [this project's GitHub Wiki](https://github.com/10up/simple-podcasting/wiki/Critical-Flows-for-simple%E2%80%90podcasting) as well as details on our overall approach to [Critical Flows and Test Cases in our Open Source Best Practices](https://10up.github.io/Open-Source-Best-Practices/testing/#critial-flows).  Submitting the results of testing via our Critical Flows as a comment on a Pull Request of a specific feature or as an Issue when testing the entire project is the best approach for providing testing results.

## Maintenance process

### Triage

Issues and WordPress.org forum posts should be reviewed weekly and triaged as necessary. Not all tasks have to be done at once or by the same person. Triage tasks include:

* Responding to new WordPress.org forum posts and GitHub issues/PRs with an acknolwedgment and following up on existing open/unresolved items that have had movement in the previous week.
* Marking forum posts as resolved when corresponding issues are fixed or as not a support issue if not relevant.
* Creating GitHub issues for WordPress.org forum posts as necessary or linking to them from existing related issues.
* Applying labels and milestones to GitHub issues.

#### Issue labels

All issues should be labeled as bugs (`type:bug`), enhancements/feature requests (`type:enhancement`), or questions/support (`type:question`). Each issue should only be of one "type".

Bugs and enhancements that are closed without a related change should be labeled as `declined`, `duplicate`, or `invalid`. Invalid issues would be where a problem is not reproducible or opened in the wrong repo and should be relatively uncommon. These labels are all prefixed with `closed:`.

There are two other labels that are GitHub defaults with more global meaning we've kept: `good first issue` and `help wanted`.

### Review against WordPress updates

During weekly triage, the tested up to version should be compared against the latest versions of WordPress, both the new and classic editors, and the standalone Gutenberg plugin. If there's a newer version of either, the plugin should be re-tested using any automated tests as well as any manual tests indicated below, and the tested up to version bumped and committed to both GitHub and the WordPress.org repository.

### Release cycle

New releases are targeted based on number and severity of changes along with human availability. When a release is targeted, a due date will be assigned to the appropriate milestone.

### Release instructions

1. Branch: Starting from `develop`, cut a release branch named `release/X.Y.Z` for your changes.
2. Version bump: Bump the version number in `simple-podcasting.php`, `package-lock.json`, `package.json`, and `readme.txt` if it does not already reflect the version being released.  Update both the plugin "Version:" property and the plugin `PODCASTING_VERSION` constant in `simple-podcasting.php`.
3. Changelog: Add/update the changelog in both `CHANGELOG.md` and `readme.txt`.
4. Props: update `CREDITS.md` with any new contributors, confirm maintainers are accurate.
5. New files: Check to be sure any new files/paths that are unnecessary in the production version are included in `.distignore`.
6. Readme updates: Make any other readme changes as necessary. `README.md` is geared toward GitHub and `readme.txt` contains WordPress.org-specific content. The two are slightly different.
7. Merge: Make a non-fast-forward merge from your release branch to `develop` (or merge the pull request), then do the same for `develop` into `trunk`, ensuring you pull the most recent changes into `develop` first (`git checkout develop && git pull origin develop && git checkout trunk && git merge --no-ff develop`). `trunk` contains the latest stable release.
8. Push: Push your `trunk` branch to GitHub (e.g. `git push origin trunk`).
9. [Compare](https://github.com/10up/simple-podcasting/compare/trunk...develop) `trunk` to `develop` to ensure no additional changes were missed.
10. Test the pre-release ZIP locally by [downloading](https://github.com/10up/simple-podcasting/actions/workflows/build-release-zip.yml) it from the Build release zip action artifact and installing it locally. Ensure this zip has all the files we expect, that it installs and activates correctly and that all basic functionality is working.
11. Release: Create a [new release](https://github.com/10up/simple-podcasting/releases/new), naming the tag and the release with the new version number, and targeting the `trunk` branch.  Paste the changelog from `CHANGELOG.md` into the body of the release and include a link to the [closed issues on the milestone](https://github.com/10up/simple-podcasting/milestone/#?closed=1).
12. SVN: Wait for the [GitHub Action](https://github.com/10up/simple-podcasting/actions) to finish deploying to the WordPress.org repository. If all goes well, users with SVN commit access for that plugin will receive an emailed diff of changes.
13. Check WordPress.org: Ensure that the changes are live on [WordPress.org](https://wordpress.org/plugins/simple-podcasting/). This may take a few minutes.
14. Close the milestone: Edit the [milestone](https://github.com/10up/simple-podcasting/milestone/#) with release date (in the `Due date (optional)` field) and link to GitHub release (in the `Description` field), then close the milestone.
15. Punt incomplete items: If any open issues or PRs which were milestoned for `X.Y.Z` do not make it into the release, update their milestone to `X.Y.Z+1`, `X.Y+1.0`, `X+1.0.0`, or `Future Release`.


================================================
FILE: CREDITS.md
================================================
The following acknowledges the Maintainers for this repository, those who have Contributed to this repository (via bug reports, code, design, ideas, project management, translation, testing, etc.), and any Libraries utilized.

## Maintainers

The following individuals are responsible for curating the list of issues, responding to pull requests, and ensuring regular releases happen.

[Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul).

## Contributors

Thank you to all the people who have already contributed to this repository via bug reports, code, design, ideas, project management, translation, testing, etc.

[Adam Silverstein (@adamsilverstein)](https://github.com/adamsilverstein), [Helen Hou-Sandi (@helen)](https://github.com/helen), [Ryan Welcher (@ryanwelcher)](https://github.com/ryanwelcher), [David Chandra Purnama (@turtlepod)](https://github.com/turtlepod), [Oscar Sanchez S. (@oscarssanchez)](https://github.com/oscarssanchez), [Jon Christensen (@Firestorm980)](https://github.com/Firestorm980), [Jeffrey Paul (@jeffpaul)](https://github.com/jeffpaul), [Noah Halstead (@nhalstead)](https://github.com/nhalstead), [Matthew Haines-Young (@mattheu)](https://github.com/mattheu), [Tung Du (@dinhtungdu)](https://github.com/dinhtungdu), [David Chabbi](https://www.linkedin.com/in/david-chabbi-985719b4/), [Pablo Amato (@pabamato)](https://github.com/pabamato), [(@monomo111)](https://github.com/monomo111), [Jake Goldman (@jakemgold)](https://github.com/jakemgold), [Mark Jaquith (@markjaquith)](https://github.com/markjaquith), [Riad Benguella (@youknowriad)](https://github.com/youknowriad), [Mészáros Róbert (@meszarosrob)](https://github.com/meszarosrob), [Max Lyuchin (@cadic)](https://github.com/cadic), [Dharmesh Patel (@iamdharmesh)](https://github.com/iamdharmesh), [Darin Kotter (@dkotter)](https://github.com/dkotter), [Peter Wilson (@peterwilsoncc)](https://github.com/peterwilsoncc), [Felipe Elia (@felipeelia)](https://github.com/felipeelia), [Mehidi Hassan (@mehidi258)](https://github.com/mehidi258), [Tom J Nowell (@tomjn)](https://github.com/tomjn), [David Towoju (@davexpression)](https://github.com/davexpression), [Quamruz Zaman (@zamanq)](https://github.com/zamanq), [Debashish (@dchucks)](https://github.com/dchucks), [(@supersmo)](https://github.com/supersmo), [Jayedul Kabir (@jayedul)](https://github.com/jayedul), [Faisal Alvi (@faisal-alvi)](https://github.com/faisal-alvi), [Vikram Mopharty (@vikrampm1)](https://github.com/vikrampm1), [Siddharth Thevaril (@Sidsector9)](https://github.com/Sidsector9), [Nicolas Knight (@Nicolas-knight)](https://github.com/Nicolas-knight), [Jonathan Netek (@jnetek)](https://github.com/jnetek), [Mehul Gohil (@mehul0810)](https://github.com/mehul0810), [Nate Conley (@nateconley)](https://github.com/nateconley), [Ajay Maurya (@ajmaurya99)](https://github.com/ajmaurya99), [Nickolas Kola (@nickolas-kola)](https://github.com/nickolas-kola), [Ahamed Arshad Azmi (@achchu93)](https://github.com/achchu93), [Ivan Ivanić (@Spoygg)](https://github.com/Spoygg), [Garth Gutenberg (@ggutenberg)](https://github.com/ggutenberg), [Ravinder Kumar (@ravinderk)](https://github.com/ravinderk), [Konstantinos Galanakis (@kmgalanakis)](https://github.com/kmgalanakis), [Dependabot (@dependabot)](https://github.com/apps/dependabot), [Sumit Bagthariya (@qasumitbagthariya)](https://github.com/qasumitbagthariya), [Shazahan Kabir Saju (@sksaju)](https://github.com/sksaju), [Kirtan Gajjar (@kirtangajjar)](https://github.com/kirtangajjar), [Chetra Chann (@channchetra)](https://github.com/channchetra), [James Burgos (@jamesburgos)](https://github.com/jamesburgos), [Martin Burch (@martinburch)](https://github.com/martinburch), [Peter Sorensen (@psorensen)](https://github.com/psorensen), [Barney Jeffries (@barneyjeffries)](https://github.com/barneyjeffries), [Gus Austin (@gusaus)](https://github.com/gusaus), [Sonali Desai (@sonali886)](https://github.com/sonali886), [Gabriel Glogoški (@gabriel-glo)](https://github.com/gabriel-glo), [David Godleman (@godleman)](https://github.com/godleman), [Sudar Muthu (@sudar)](https://github.com/sudar), [David Bowman (@dabowman)](https://github.com/dabowman).

## Libraries

The following software libraries are utilized in this repository.

n/a.


================================================
FILE: LICENSE.md
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.


================================================
FILE: README.md
================================================
# Simple Podcasting for WordPress

![Simple Podcasting](https://github.com/10up/simple-podcasting/blob/develop/.wordpress-org/banner-1544x500.png)

[![Support Level](https://img.shields.io/badge/support-stable-blue.svg)](#support-level) ![Required PHP Version](https://img.shields.io/wordpress/plugin/required-php/simple-podcasting?label=Requires%20PHP) ![Required WP Version](https://img.shields.io/wordpress/plugin/wp-version/simple-podcasting?label=Requires%20WordPress) ![WordPress tested up to version](https://img.shields.io/wordpress/plugin/tested/simple-podcasting?label=WordPress) [![GPLv2 License](https://img.shields.io/github/license/10up/simple-podcasting.svg)](https://github.com/10up/simple-podcasting/blob/develop/LICENSE.md) [![Dependency Review](https://github.com/10up/simple-podcasting/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/10up/simple-podcasting/actions/workflows/dependency-review.yml) [![E2E Test](https://github.com/10up/simple-podcasting/actions/workflows/cypress.yml/badge.svg)](https://github.com/10up/simple-podcasting/actions/workflows/cypress.yml) [![Unit Tests](https://github.com/10up/simple-podcasting/actions/workflows/phpunit.yml/badge.svg)](https://github.com/10up/simple-podcasting/actions/workflows/phpunit.yml) [![PHPCS](https://github.com/10up/simple-podcasting/actions/workflows/phpcs.yml/badge.svg)](https://github.com/10up/simple-podcasting/actions/workflows/phpcs.yml) [![PHP Compatibility](https://github.com/10up/simple-podcasting/actions/workflows/php-compatibility.yml/badge.svg)](https://github.com/10up/simple-podcasting/actions/workflows/php-compatibility.yml) [![CodeQL](https://github.com/10up/simple-podcasting/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/10up/simple-podcasting/actions/workflows/github-code-scanning/codeql) [![WordPress Playground Demo](https://img.shields.io/wordpress/plugin/v/simple-podcasting?logo=wordpress&logoColor=FFFFFF&label=Playground%20Demo&labelColor=3858E9&color=3858E9)](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/10up/simple-podcasting/add/playground/.wordpress-org/blueprints/blueprint.json)

> Easily set up multiple podcast feeds using built-in WordPress posts. Includes a podcast block and podcast transcript block for the WordPress block editor (aka Gutenberg).

## Overview

Podcasting is a method to distribute audio messages through a feed to which listeners can subscribe. You can publish podcasts on your WordPress site and make them available for listeners in Apple Podcasts and through direct feed links for other podcasting apps by following these steps:

![Screenshot of podcast block](.wordpress-org/screenshot-1.png "Example of a podcast block in the new WordPress editor")

## Requirements

* PHP 7.4+
* [WordPress](http://wordpress.org) 6.6+
* RSS feeds must not be disabled

## Installation

1. Install the plugin via the plugin installer, either by searching for it or uploading a .zip file.
2. Activate the plugin.
3. Head to Posts → Podcasts and add at least one podcast.
4. Create a post and insert an audio embed (or a podcast block in the new WordPress editor) and select a Podcast feed to include it in.

## Create your podcast

From the WordPress Admin, go to Podcasts.
To create a podcast, complete all of the "Add New Podcast" fields and click "Add New Podcast".

 * Name: this title appears in Apple Podcasts and any other podcast apps.
 * Slug: this is the URL-friendly version of the Name field.
 * Subtitle: the subtitle also appears in Apple Podcasts and any other podcast apps.
 * Artist / Author name: the artist or producer of the work.
 * Podcast email: a contact email address for your podcast.
 * Summary: Apple Podcasts displays this summary when browsing through podcasts.
 * Copyright / License information: copyright information viewable in Apple Podcasts or other podcast apps.
 * Mark as explicit: mark Yes if podcast contains adult language or adult themes.
 * Language: the main language spoken in the podcast.
 * Cover image: add the URL for the cover art to appear in Apple Podcasts and other podcast apps. Click "Select Image" and choose an image from the Media Library. Note that podcast cover images must be between 1400 x 1400 and 3000 x 3000 pixels in JPG or PNG formats to work on Apple Podcasts.
 * Keywords: add terms to help your podcast show up in search results on Apple Podcasts and other podcast apps.
 * Categories: these allow your podcast to show up for those browsing Apple Podcasts or other podcast apps by category.

Repeat for each podcast you would like to create.

## Add content to your podcast

 * Create a new post and assign it to one or more Podcasts using the panel labeled Podcasts.
 * Upload or embed an audio file into this post using any of the usual WordPress methods. If using the new block-based WordPress editor (sometimes referred to as Gutenberg), insert a Podcast block. Only one Podcast block can be inserted per post.
 * For more advanced settings, use the Podcasting meta box to mark explicit content or closed captioning available, season number, episode number, episode type, add a transcript and to optionally specify one media item in the post if you have more than one in your post. In the block-based editor, these are the block settings that appear in the sidebar when the podcast block is selected.
 * Transcript: If desired, an optional transcript can be added from the settings of the Podcast block. This will add a Podcast Transcript block, allowing you to add a transcript consisting of time codes, citations, and paragrah text that can be embedded in the post, linked to an external plain HTML file, or linked in a special `<podcast:transcript>` XML element.

## Submit your podcast feed to Apple Podcasts

* Each podcast has a unique feed URL you can find on the Podcasts page. This is the URL you will submit to Apple.
* Ensure you test feeds before submitting them, see https://help.apple.com/itc/podcasts_connect/#/itcac471c970.
* Once the validator passes, submit your podcast. Podcasts submitted to Apple Podcasts do not become immediately available for subscription by others. They are submitted for review by Apple staff, see https://help.apple.com/itc/podcasts_connect/#/itcd88ea40b9

Podcast setup | Podcast in block editor | Podcast feed
------------- | ----------------- | ------------
[![Podcast setup](.wordpress-org/screenshot-3.png)](.wordpress-org/screenshot-3.png) | [![Podcast in editor](.wordpress-org/screenshot-1.png)](.wordpress-org/screenshot-1.png) | [![Podcast feed](.wordpress-org/screenshot-4.png)](.wordpress-org/screenshot-4.png)

Podcast Platforms block | Podcast Grid pattern | Podcast Transcript block
------------- | ----------------- | ------------
[![Podcast Platforms block](.wordpress-org/screenshot-2.png)](.wordpress-org/screenshot-2.png) | [![Podcast Grid pattern](.wordpress-org/screenshot-5.png)](.wordpress-org/screenshot-5.png) | [![Podcast Transcript block](.wordpress-org/screenshot-6.png)](.wordpress-org/screenshot-6.png)

## Submit your podcast feed to Pocket Casts

* Validate your feeds at [Cast Feed Validator](https://www.castfeedvalidator.com/) before submitting them.
* Submit the podcast feed to https://pocketcasts.com/submit/

## Control how many episodes are listed on the feed

If you want to adjust the default number of episodes included in a podcast RSS feed, then utilize the following to do so...

```php
<?php

add_filter( 'simple_podcasting_episodes_per_page', 'podcasting_feed_episodes_per_page' );

/**
 * Filter how many items are displayed on the feed
 * Default is 250
 *
 * @param int $qty Items count.
 * @return string
 */
function podcasting_feed_episodes_per_page( $qty ) {
	return 300;
}
```

## Customize the RSS feed title

The `<title>` element of the RSS feed can be adjusted using the `simple_podcasting_feed_title` filter.

```php
<?php

add_filter( 'simple_podcasting_feed_title', 'podcasting_feed_update_feed_title', 10, 2 );

/**
 * Filter the name of the of the feed channel
 *
 * @param $output Output to be modified.
 * @param $term WP_Term object representing the podcast
 * @return string
 */
function podcasting_feed_update_feed_title( $output, $term ) {
	$term_name = $term->name;

	return '10up Presents: ' . $term_name;
}
```

## Customize RSS feed

If you want to modify RSS feed items output, there is a filter for that:

```php
<?php

function podcasting_feed_item_filter( $feed_item = array(), $post_id = null, $term_id = null ) {
	if ( 42 === $post_id ) {
		$feed_item['keywords'] = 'one,two,three';
	}
	return $feed_item;
}
add_filter( 'simple_podcasting_feed_item', 'podcasting_feed_item_filter', 10, 3 );
```

## Frequently Asked Questions

### How do I get my podcast featured on Pocket Casts?

The Featured section of Pocket Casts is human-curated. To ensure that all podcasts have an equal opportunity at being featured, selections are made on the basis of merit.

If you’d like to suggest your podcast for a featured spot, reach out to curation@pocketcasts.com.

For more information, [read more](https://pocketcasts.com/podcast-producers/).

### How do I submit private and paid podcast feeds?

Follow this documentation to submit [private and paid podcast feeds](https://support.pocketcasts.com/article/password-protected-podcasts-2/)

### Where do I report security bugs found in this plugin?

Please report security bugs found in the source code of the Simple Podcasting plugin through the [Patchstack Vulnerability Disclosure  Program](https://patchstack.com/database/vdp/0d49ba54-688e-484d-9411-4716696aa79b).  The Patchstack team will assist you with verification, CVE assignment, and notify the developers of this plugin.

## Support Level

**Stable:** 10up is not planning to develop any new features for this, but will still respond to bug reports and security concerns. We welcome PRs, but any that include new features should be small and easy to integrate and should not include breaking changes. We otherwise intend to keep this tested up to the most recent version of WordPress.

## Changelog

A complete listing of all notable changes to Simple Podcasting for WordPress are documented in [CHANGELOG.md](https://github.com/10up/simple-podcasting/blob/develop/CHANGELOG.md).

## Contributing

Please read [CODE_OF_CONDUCT.md](https://github.com/10up/simple-podcasting/blob/develop/CODE_OF_CONDUCT.md) for details on our code of conduct, [CONTRIBUTING.md](https://github.com/10up/simple-podcasting/blob/develop/CONTRIBUTING.md) for details on the process for submitting pull requests to us, and [CREDITS.md](https://github.com/10up/simple-podcasting/blob/develop/CREDITS.md) for a listing of maintainers of, contributors to, and libraries used by Simple Podcasting for WordPress.

## Like what you see?

<a href="http://10up.com/contact/"><img src="https://github.com/10up/.github/blob/trunk/profile/10up-github-banner.jpg" width="850" alt="Work with the 10up WordPress Practice at Fueled"></a>


================================================
FILE: assets/css/podcasting-edit-term.css
================================================
.taxonomy-podcasting_podcasts .term-parent-wrap {
	display: none;
}
.podcast-image-thumbnail {
	max-width: 300px;
	max-height: 200px;
}

.column-podcasting_image img {
	height: auto;
	max-width: 100%;
	width: 75px;
}

.simple_podcasting__platforms img {
	display: block;
	max-width: 42px;
	margin: 0 auto;
	height: auto;
}

.simple_podcasting__platforms th {
	padding: 15px 10px;
	text-align: center;
}

.simple_podcasting__platforms td {
	vertical-align: middle;
	padding: 15px 10px;
}

.simple_podcasting__platforms-url {
	min-width: 220px;
}

.simple_podcasting__platforms-icon {
	transition: background-color 0.2s ease-out;
}

.simple_podcasting__platforms-icon--darken-bg {
	background-color: rgb(28 52 59 / 22%);
	transition: background-color 0.2s ease-in;
}


================================================
FILE: assets/css/podcasting-editor-screen.css
================================================
.components-input-control,
.components-base-control {
	width: 100%;
}
.cover-art-container {
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
}
.cover-art-container button {
	margin: 10px 0;
}


================================================
FILE: assets/css/podcasting-onboarding.scss
================================================
* {
	box-sizing: border-box;
}

.admin_page_simple-podcasting-onboarding #wpcontent {
	padding-left: 0;
}

#simple-podcasting {
	&__onboarding-header {
		width: 100%;
		height: 79px;
		padding: 0 1.75rem;

		display: flex;
		justify-content: space-between;
		align-items: center;

		background-color: #fff;
		border-bottom: 1px solid #c0c0c1;

		& > * {
			flex-grow: 1;
			flex-basis: 0;
		}
	}

	&__branding {
		display: flex;
		align-items: center;
	}

	&__header-title {
		text-align: center;
		font-family: 'Roboto';
		font-style: normal;
		font-weight: 700;
		font-size: 17.8983px;
	}

	&__logo {
		max-width: 56px;
		height: auto;

		img {
			display: block;
			width: 100%;
		}
	}

	&__plugin-name {
		margin-left: 11px;
		font-family: 'Roboto';
		font-style: normal;
		font-weight: 700;
		font-size: 17.8983px;
		line-height: 20px;
	}

	&__header-controls {
		.simple-podcasting__btn {
			float: right;
		}
	}

	&__page-title {
		margin-bottom: 30px;
		font-family: 'Roboto';
		font-style: normal;
		font-weight: 700;
		font-size: 22px;
		line-height: 24px;
	}

	&__upload-cover-image {
		margin-top: 13px;
		margin-bottom: 14px;
	}

	&__create-a-new-post-button {
		padding: 10px 40px;
		width: 240px;

		display: block;
		text-align: center;
		text-decoration: none;

		color: #fff !important;
	}

	&__cover-image-preview {
		img {
			display: block;
			max-width: 256px;
		}
	}
}

.simple-podcasting {
	// Body.
	&__onboarding-body {
		margin: 0 auto;
		margin-top: 68px;
		padding: 0 1.75rem;

		&--step-1 {
			max-width: 524px;
		}

		&--step-2 {
			display: flex;
			max-width: 1038px;
		}
	}

	&__setting {
		margin-bottom: 30px;

		input,
		textarea {
			width: 100%;
			padding: 11px 7px 10px 13px;
			background: #FFFFFF;
			border: 1px solid #828282;
			border-radius: 5px;
		}

		select {
			padding: 11px 7px 10px 13px;
			width: 320px;
			background: #FFFFFF;
			border: 1px solid #828282;
			border-radius: 5px;
		}
	}

	&__setting-label {
		display: block;
		font-family: 'Roboto';
		font-style: normal;
		font-weight: 700;
		font-size: 16px;
		line-height: 24px;
		margin-bottom: 4px;
	}

	&__setting-description {
		font-family: 'Roboto';
		font-style: normal;
		font-weight: 400;
		font-size: 14px;
		line-height: 24px;
		color: #828282;
		margin-top: 4px;
	}

	&__panel {
		font-family: 'Roboto';
		font-style: normal;
		font-weight: 400;
		font-size: 16px;
		line-height: 24px;
		flex-grow: 1;
		flex-basis: 0;

		p {
			font-family: 'Roboto';
			font-style: normal;
			font-weight: 400;
			font-size: 16px;
			line-height: 24px;
		}

		&--left {
			max-width: 483px;

			a {
				color: #000;
				font-weight: 700;
			}
		}
	}

	&__podcast-block-preview {
		width: 468px;
		float: right;
		box-shadow: 0px 0px 26px 8px rgba(0, 0, 0, 0.05);

		img {
			display: block;
			width: 100%;
			height: auto;
		}
	}

	&__step-2-controls {
		display: flex;
		align-items: center;
		justify-content: space-between;

		margin-top: 64px;

		a {
			letter-spacing: 1px;
			text-decoration-line: underline;
			color: #4F4F4F !important;
		}
	}
}

.simple-podcasting__btn {
	box-shadow: none;
	border: 0;
	outline: 0;
	border-radius: 3px;
	padding: 12px 40px;

	font-family: 'Roboto';
	font-style: normal;
	font-weight: 700;
	font-size: 14px;
	letter-spacing: 1px;

	text-decoration: none;

	cursor: pointer;

	&--ghost {
		border: 1px solid #000000;
		background-color: rgba(0, 0, 0, 0);
		color: #000;
	}

	&--black {
		background: #4F4F4F;
		border-radius: 3px;
		color: #FFFFFF;
	}
}


================================================
FILE: assets/css/podcasting-transcript.css
================================================
.wp-block-podcasting-podcast-transcript cite,
.wp-block-podcasting-podcast-transcript time {
	display: block;
}


================================================
FILE: assets/js/blocks/latest-episode/index.js
================================================
import './index.scss';


================================================
FILE: assets/js/blocks/latest-episode/index.scss
================================================
.podcasting-latest-episode {
	display: flex;
	flex-direction: column;
	justify-content: end;
	min-height: 20rem;
	overflow: hidden;
	position: relative;

	& .wp-block-post-featured-image {
		height: 100%;
		object-fit: fill;
		object-position: center;
		position: absolute;
		width: 100%;

		&::after {
			background-color: rgb(0 0 0 / 75%);
			content: "";
			display: block;
			height: 100%;
			left: 0;
			position: absolute;
			top: 0;
			width: 100%;
		}
	}
}

.podcasting-latest-episode__content {
	color: #fff;
	padding: 3rem;
	position: relative;
	z-index: 1;

	@media (min-width: 768px) {
		padding: 3rem;
	}

	& .wp-block-post-excerpt,
	& .wp-block-post-excerpt__more-text {
		margin-top: 0.25rem;
	}

	& .wp-block-post-excerpt__more-link {
		color: #fff;
	}
}

.editor-styles-wrapper .wp-block-post-content .podcasting-latest-episode__content .wp-block-post-excerpt__more-link:where(:not(.wp-element-button)) {
	color: #fff;
}


================================================
FILE: assets/js/blocks/podcast/index.js
================================================
import './index.scss';


================================================
FILE: assets/js/blocks/podcast/index.scss
================================================
.wp-block-podcasting-podcast-outer {
    border: 1px solid #707070;
    border-radius: 4px;
    padding: 20px;
}

.wp-block-podcasting-podcast__container {
    margin-bottom: 10px;

    @media (min-width: 768px) {
        display: flex;
    }
}

.wp-block-podcasting-podcast__show-art {
    margin-bottom: 20px;

    @media (min-width: 768px) {
        flex-basis: 100px;
        margin-bottom: 0;
        margin-right: 20px;
    }
}

.wp-block-podcasting-podcast__image {
    aspect-ratio: 1/1;
    height: auto;
    position: relative;

    & img {
        display: block;
        height: 100%;
        object-fit: cover;
        width: 100%;
    }
}

.wp-block-podcasting-podcast__show-title {
    margin: 0;
}

.wp-block-podcasting-podcast__show-details {
    color: #575757;
    font-size: 0.875rem;
    text-transform: uppercase;

    & span {
        display: block;
        margin-right: 6px;

        @media (min-width: 768px) {
            display: inline;
        }

        &::after {

            @media (min-width: 768px) {
                content: '/';
                margin-left: 6px;
            }
        }

        &:last-child {
            margin-right: 0;

            &::after {
                display: none;
            }
        }
    }
}

.wp-block-podcasting-podcast__caption {
    margin-bottom: 10px;
}

.wp-block-podcasting-podcast {
    margin: 0;

    & audio {
        display: block;
        width: 100%;
    }
}


================================================
FILE: assets/js/blocks/podcast-platforms/edit.js
================================================
import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { useState, useEffect } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { __ } from '@wordpress/i18n';
import { useDebounce } from 'use-debounce';
import {
	Panel,
	PanelBody,
	PanelRow,
	RangeControl,
	SearchControl,
	__experimentalItemGroup as ItemGroup,
	__experimentalItem as Item,
	BaseControl,
	Button,
	ButtonGroup,
	Icon
} from '@wordpress/components';


function Edit( props ) {
	const {
		setAttributes,
		isSelected,
		attributes: {
			showId,
			iconSize,
			align,
		},
	} = props;

	/** State for the search text for the show name. Defaults to empty string. */
	const [ searchText, setSearchText ] = useState( '' );

	/** Debounced search text so that we don't trigger useEffect() for every character change. */
	const [ debouncedSearchText ] = useDebounce( searchText, 300 );

	/** Indicates when the ajax search for podcasts is completed. */
	const [ isSearchCompleted, setIsSearchCompleted ] = useState( false );

	/** State for search results matched by the search text. Defaults to array. */
	const [ searchResults, setSearchResults ] = useState( [] );

	/** State for the icon theme. Defaults to `color`. */
	const [ iconTheme, setIconTheme ] = useState( 'color' );

	/** State for platforms returned for a specific show. Defaults to array. */
	const [ platforms, setPlatforms ] = useState( [] );

	/**
	 * Hits the `/wp/v2/search` endpoint to search for
	 * podcast show by name.
	 */
	useEffect( () => {
		const searchPodcastShow = async () => {
			setIsSearchCompleted( false );

			if ( ! searchText.length ) {
				setSearchResults( [] );
				return;
			}

			/** Query object required by `/wp/v2/search` to search for a term by name. */
			const queryObject = {
				search: searchText,
				type: 'term',
				subtype: 'podcasting_podcasts'
			};

			/** Converts an object to query-string. */
			const queryString = new URLSearchParams( queryObject ).toString();

			/** Returns the results of the search. */
			const searchResults = await apiFetch( {
				path: `/wp/v2/search?${ queryString }`,
			} );

			if ( ! searchResults.length ) {
				setIsSearchCompleted( true );
			}

			setSearchResults( searchResults );
			setIsSearchCompleted( true );
		};

		searchPodcastShow();
	}, [ debouncedSearchText ] );

	/**
	 * Fetches the podcasting platforms for a show whenever
	 * showId updates.
	 */
	useEffect( () => {
		if ( ! showId ) {
			return;
		}

		/**
		 * Responsible to fetch platforms for a show by show ID.
		 * @returns void
		 */
		const fetchPlatforms = async () => {
			const result = await apiFetch( {
				url: `${ ajaxurl }?show_id=${ showId }&action=get_podcast_platforms`,
			} );

			if ( ! result.success ) {
				setPlatforms( [] );
				return;
			}

			const {
				data: { platforms, theme }
			} = result;

			setPlatforms( platforms );
			setIconTheme( theme );
		};

		fetchPlatforms();
	}, [ showId ] );

	/**
	 * Handler to set the attribute showId.
	 *
	 * @param {Int} termId The show ID.
	 * @returns void
	 */
	const onShowSelect = ( termId ) => {
		setAttributes( { showId: termId } );
		setSearchResults( [] );
		setIsSearchCompleted( false );
	};

	/**
	 * Handler to set size of the icon.
	 *
	 * @param {Int} size The icon size in `px`
	 */
	const setIconSize = ( size ) => {
		setAttributes( { iconSize: size } );
	};

	/**
	 * Sets the HTML attributes for the root element.
	 */
	const blockProps = useBlockProps( {
		className: isSelected ? 'simple-podcasting__podcast-platforms simple-podcasting__podcast-platforms--selected' : 'simple-podcasting__podcast-platforms',
	} );

	const platformSlugs = Object.keys( platforms );

	return (
		<>
			<InspectorControls>
				<Panel header={ __( 'Customization Controls', 'simple-podacsting' ) }>
					<PanelBody>
						<BaseControl label={ __( 'Icon size', 'simple-podcasting' ) }/>
						<PanelRow>
							<RangeControl
								min={ 16 }
								max={ 96 }
								step={ 16 }
								value={ iconSize }
								onChange={ setIconSize }
							/>
						</PanelRow>
						<BaseControl label={ __( 'Alignment', 'simple-podcasting' ) }/>
						<PanelRow>
							<ButtonGroup>
								<Button
									isPressed={ align === 'left' }
									variant='ternary'
									icon={ <Icon icon='align-left' /> }
									onClick={ () => setAttributes( { align: 'left' } ) }
								/>
								<Button
									isPressed={ align === 'center' }
									variant='ternary'
									icon={ <Icon icon='align-center' /> }
									onClick={ () => setAttributes( { align: 'center' } ) }
								/>
								<Button
									isPressed={ align === 'right' }
									variant='ternary'
									icon={ <Icon icon='align-right' /> }
									onClick={ () => setAttributes( { align: 'right' } ) }
								/>
							</ButtonGroup>
						</PanelRow>
					</PanelBody>
				</Panel>
			</InspectorControls>
			<div { ...blockProps }>
				{
					platformSlugs.length ? (
						<div className={ `simple-podcasting__podcasting-platform-list simple-podcasting__podcasting-platform-list--${ align }` }>
							{
								platformSlugs.map( ( platform, index ) => {
									return (
										<span key={ index } className='simple-podcasting__podcasting-platform-list-item'>
											<a href={ platforms[ platform ] } target="_blank">
												<img className={ `simple-pocasting__icon-size--${ iconSize }` } src={ `${ podcastingPlatformVars.podcastingUrl }dist/images/icons/${ platform }/${ iconTheme }-100.png` } />
											</a>
										</span>
									);
								} )
							}
						</div>
					) : (
						<div className={ `simple-podcasting__podcasting-platform-list` }>
							<p>{ __( 'No platforms are set for this podcast.', 'simple-podcasting' ) }</p>
						</div>
					)
				}
				{
					isSelected || ! showId ? (
						<div className='simple-podcasting__podcasting-search-controls'>
							<SearchControl
								placeholder={ __( 'Search a Podcast Show', 'simple-podcasting' ) }
								onChange={ ( searchText ) => setSearchText( searchText ) }
								value={ searchText }
							/>
							{
								searchResults.length ?
								(
									<div className='simple-podcasting__podcasting-search-results'>
										<ItemGroup
											isSeparated
										>
											{
												searchResults.map( ( result ) => (
													<Item
														key={ result.id }
														className='simple-podcasting__podcast-search-results'
														onClick={ () => onShowSelect( result.id ) }
													>
														{ result.title }
													</Item>
												) )
											}
										</ItemGroup>
									</div>
								) : (
									! searchResults.length && isSearchCompleted ? (
										<div className='simple-podcasting__podcasting-search-results'>
											<ItemGroup
												isSeparated
											>
												<Item>
													{ __( 'No results found.' , 'simple-podcasting') }
												</Item>
											</ItemGroup>
										</div>
									) : null
								)
							}
						</div>
					) : null
				}
			</div>
		</>
	)
}

export default Edit;

================================================
FILE: assets/js/blocks/podcast-platforms/index.js
================================================
/**
 * Internal block libraries
 */
import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';

import Edit from './edit';
import './index.scss';

/**
 * Register Podcast Platforms block
 */
export default registerBlockType(
	'podcasting/podcast-platforms',
	{
		title: __( 'Podcast Platforms', 'simple-podcasting' ),
		description: __( 'Displays the list of platforms where the selected show is available.', 'simple-podcasting' ),
		category: 'common',
		icon: 'microphone',
		supports: {
			multiple: false,
		},
		attributes: {
			showId: {
				type: 'number',
				default: 0,
			},
			iconSize: {
				type: 'number',
				default: 48,
			},
			align: {
				type: 'string',
				default: 'center',
			}
		},

		edit: Edit,

		save: () => null,
	},
);


================================================
FILE: assets/js/blocks/podcast-platforms/index.scss
================================================
.wp-block {
	.simple-podcasting__podcasting-platform-list {
		margin-bottom: 3rem;
	}

	.simple-podcasting__podcast-platforms {
		padding: 2.5rem 1rem 1rem 1rem;
	}
}

.simple-podcasting {
	&__podcast-platforms {
		padding: 1rem 0 1rem 0;
	}

	&__podcast-search-results {
		cursor: pointer;
	}

	&__select-show-popover {
		max-width: 400px;
		width: 100%;

		.components-popover__content {
			width: 100%;
			padding: 1rem;
		}
	}

	&__podcasting-platform-list {
		display: flex;
		flex-flow: row wrap;

		&--left {
			justify-content: start;
		}

		&--center {
			justify-content: center;
		}

		&--right {
			justify-content: end;
		}

		a {
			text-decoration: none !important;
		}

		img {
			display: block;

			@for $i from 1 through 6 {
				&.simple-pocasting__icon-size--#{$i * 16} {
					max-width: #{$i * 16}px;
				}
			}
		}
	}

	&__podcasting-platform-list-item {
		display: flex;
		align-self: center;
		padding: 0.75rem;

		@media screen and ( max-width: 480px ) {
			justify-content: center;
			width: 25%;
		}
	}

	&__podcasting-search-controls {
		position: relative;

		input[type="search"] {
			font-size: 1rem;
		}
	}

	&__podcasting-search-results {
		position: absolute;
		border: 1px solid;
		background-color: #fff;
		min-width: 200px;
		z-index: 10;
	}
}


================================================
FILE: assets/js/blocks.js
================================================
/**
 * Internal block libraries
 */
import { __ } from '@wordpress/i18n';
import { registerBlockType, registerBlockVariation } from '@wordpress/blocks';

// Split the Edit component out.
import Edit from './edit';
import transforms from './transforms';
import deprecated from './deprecated';
import '../css/podcasting-editor-screen.css';

/**
 * Register example block
 */
export default registerBlockType(
	'podcasting/podcast',
	{
		title: __( 'Podcast', 'simple-podcasting' ),
		description: __( 'Insert a podcast episode into a post. To add it to a podcast feed, select a podcast in document settings.', 'simple-podcasting' ),
		category: 'common',
		icon: 'microphone',
		supports: {
			multiple: false,
		},
		attributes: {
			id: {
				type: 'number',
			},
			src: {
				type: 'string',
				source: 'attribute',
				selector: 'audio',
				attribute: 'src',
			},
			url: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_url',
			},
			filesize: {
				type: 'number',
				source: 'meta',
				meta: 'podcast_filesize',
			},
			duration: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_duration',
			},
			mime: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_mime',
			},
			caption: {
				type: 'array',
				source: 'children',
				selector: 'figcaption',
			},
			captioned: {
				type: 'boolean',
				source: 'meta',
				meta: 'podcast_captioned',
				default: false,
			},
			explicit: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_explicit',
			},
			enclosure: {
				type: 'string',
				source: 'meta',
				meta: 'enclosure',
			},
			seasonNumber: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_season_number',
			},
			episodeNumber: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_episode_number',
			},
			episodeType: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_episode_type',
			},
			displayDuration: {
				type: 'boolean',
				default: false,
			},
			displayShowTitle: {
				type: 'boolean',
				default: false,
			},
			displayEpisodeTitle: {
				type: 'boolean',
				default: false,
			},
			displayArt: {
				type: 'boolean',
				default: false,
			},
			displayExplicitBadge: {
				type: 'boolean',
				default: false,
			},
			displaySeasonNumber: {
				type: 'boolean',
				default: false,
			},
			displayEpisodeNumber: {
				type: 'boolean',
				default: false,
			},
			displayEpisodeType: {
				type: 'boolean',
				default: false,
			}
		},
		transforms,

		edit: Edit,

		save: props => {
			const {
				id,
				src,
				caption
			} = props.attributes;

			return (
				<figure className={ id ? `podcast-${ id }` : null }>
					{ caption && caption.length > 0 && <figcaption className="wp-block-podcasting-podcast__caption">{ caption }</figcaption> }
					<audio controls="controls" src={ src } />
				</figure>
			);
		},
		deprecated,
	},
);

const VARIATION_NAME = 'podcasting/latest-episode';

registerBlockVariation('core/query', {
	name: VARIATION_NAME,
	title: 'Latest Podcast Episode',
	description: 'Displays the latest podcast episode.',
	isActive: ['simple-podcasting'],
	icon: 'microphone',
	attributes: {
		namespace: VARIATION_NAME,
		query: {
			postType: 'post',
			podcastingQuery: 'not_empty',
		},
	},
	allowedControls: [ ],
	scope: [ 'inserter' ],
	innerBlocks: [
		[
			'core/post-template',
			{},
			[ [
				'core/group',
				{ className: 'podcasting-latest-episode' },
				[
					[ 'core/post-featured-image' ],
					[ 'core/group', { className: 'podcasting-latest-episode__content' }, [
						[ 'core/post-title' ], [ 'core/post-date' ], [ 'core/post-excerpt' ]
					] ],
				]
			] ],
		],
		[ 'core/query-no-results' ],
	],
});


================================================
FILE: assets/js/create-podcast-show.js
================================================
import { registerPlugin } from "@wordpress/plugins";
import { store as editPostStore } from '@wordpress/edit-post';
import { __ } from "@wordpress/i18n";
import {
	Button,
	Modal,
	TextControl,
	SelectControl,
	TextareaControl,
	BaseControl,
	Flex,
	FlexItem,
	CheckboxControl,
} from "@wordpress/components";
import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
import { useState, useEffect } from "@wordpress/element";
import { useSelect, dispatch } from "@wordpress/data";

// Once WordPress 6.6 becomes our minimum, change this back to `import { PluginDocumentSettingPanel } from '@wordpress/editor';`.
const PluginDocumentSettingPanel = wp.editor?.PluginDocumentSettingPanel ?? ( wp.editPost?.PluginDocumentSettingPanel ?? wp.editSite?.PluginDocumentSettingPanel );

// Due to unsupported versions of React, we're importing stores from the
// `wp` namespace instead of @wordpress NPM packages for the following.
const { store: editorStore } = wp.editor;
const { store: coreDataStore } = wp.coreData;
const DEFAULT_QUERY = {
	per_page: -1,
	orderby: 'name',
	order: 'asc',
	_fields: 'id,name,parent',
	context: 'view',
};

const CreatePodcastShowModal = ( { isModalOpen, closeModal } ) => {
	const [ showName, setShowName ] = useState( '' );
	const [ artistName, setArtistName ] = useState( '' );
	const [ showCategory, setShowCategory ] = useState( '' );
	const [ summary, setSummary ] = useState( '' );
	const [ coverId, setCoverId ] = useState( 0 );
	const [ coverUrl, setCoverUrl ] = useState( '' );
	const [ ajaxInprogress, setAjaxInProgress ] = useState( false );
	const [ isPodcastCreated, setIsPodcastCreated ] = useState( false );

	const modalStyle = {
		maxWidth: '645px',
		width: '100%'
	};

	const fieldStyle = {
		marginBottom: '26px',
	};

	const createShow = async () => {
		setAjaxInProgress( true );

		try {
			const podcast = await wp.data.dispatch( coreDataStore ).saveEntityRecord(
				'taxonomy',
				'podcasting_podcasts',
				{
					name: showName,
					meta: {
						podcasting_summary: summary,
						podcasting_category_1: showCategory,
						podcasting_image: coverId,
						podcasting_image_url: coverUrl,
						podcasting_talent_name: artistName,
					}
				}
			);

			if ( podcast ) {
				setIsPodcastCreated( true );
			}
		} catch ( error ) {
			setAjaxInProgress( false );
		}

		setAjaxInProgress( false );
	};

	if ( ! isModalOpen ) {
		return false;
	}

	const categoriesOptions = Object.keys( podcastingShowPluginVars.categories ).map( key => ( { value: key, label: podcastingShowPluginVars.categories[key] } ) );

	return (
		<Modal
			title={ isPodcastCreated ? __( 'Podcast created!', 'simple-podcasting' ) : __( 'Add New Podcast', 'simple-podcasting' ) }
			style={ modalStyle }
			onRequestClose={ ( event ) => {
				const selectImageBtn = event.target.closest( '.podcasting__select-image-btn' );

				if ( selectImageBtn ) {
					return;
				}
				closeModal();
			} }
		>
			{
				isPodcastCreated ? (
					<>
						<Button
							variant="link"
							text={ __( 'Add another Podcast', 'simple-podcasting' ) }
							onClick={ () => {
								setIsPodcastCreated( false );
								setShowName( '' );
								setShowCategory( '' );
								setSummary( '' );
								setCoverId( 0 );
								setCoverUrl( '' );
							} }
						/>
					</>
				) : (
					<>
						<div className="podcasting__modal-field-row" style={ fieldStyle }>
							<TextControl
								className="podcasting__modal-name-field"
								label={ __( 'Podcast name*', 'simple-podcasting' ) }
								help={ __( 'This is the name that listeners will see when searching or subscribing.', 'simple-podcasting' ) }
								value={ showName }
								onChange={ ( val ) => setShowName( val ) }
								required
							/>
						</div>

						<div className="podcasting__modal-field-row" style={ fieldStyle }>
							<TextControl
								className="podcasting__modal-artist-field"
								label={ __( 'Artist name*', 'simple-podcasting' ) }
								help={ __( 'Who’s the artist or author of your podcast show that listeners will see?', 'simple-podcasting' ) }
								value={ artistName }
								onChange={ ( val ) => setArtistName( val ) }
								required
							/>
						</div>

						<div className="podcasting__modal-field-row" style={ fieldStyle }>
							<SelectControl
								className="podcasting__modal-category-field"
								label={ __( 'Category*', 'simple-podcasting' ) }
								help={ __( 'Select the category listeners will use to discover your show when browsing  podcatchers. You can also add subcategories later.', 'simple-podcasting' ) }
								options={ categoriesOptions }
								value={ showCategory }
								onChange={ ( val ) => setShowCategory( val ) }
								required
							/>
						</div>

						<div className="podcasting__modal-field-row" style={ fieldStyle }>
							<TextareaControl
								className="podcasting__modal-summary-field"
								label={ __( 'Summary*', 'simple-podcasting' ) }
								help={ __( 'Briefly describe to your listeners what your show is about. (No HTML please.)', 'simple-podcasting' ) }
								rows={ 6 }
								value={ summary }
								onChange={ ( val ) => setSummary( val ) }
								required
							/>
						</div>

						<div className="podcasting__modal-field-row" style={ fieldStyle }>
							<MediaUploadCheck>
								<MediaUpload
									onSelect={ ( media ) => {
										setCoverId( media.id );
										setCoverUrl( media.url );
									} }
									allowedTypes={ [ 'image' ] }
									value={ coverId }
									render={ ( { open } ) => (
										<>
											<BaseControl label={ __( 'Cover Image*', 'simple-podcasting' ) } />
											<Flex justify="normal">
												<FlexItem>
													<Button
														className="podcasting__select-image-btn"
														variant="secondary"
														text={ coverId ? __( 'Replace Image', 'simple-podcasting' ) : __( 'Select Image', 'simple-podcasting' ) }
														onClick={ open }
													/>
												</FlexItem>
												{
													coverId ? (
														<FlexItem>
															<Button
																className="podcasting__remove-image-btn"
																variant="secondary"
																text={ __( 'Remove', 'simple-podcasting' ) }
																isDestructive
																onClick={ () => {
																	setCoverId( 0 );
																	setCoverUrl( '' );
																} }
															/>
														</FlexItem>
													) : null
												}
											</Flex>
											{
												coverId ? (
													<div className="podcasting-cover-preview" style={ {
														maxWidth: '256px',
														marginTop: '1rem',
													} }>
														<img src={ coverUrl } style={ { width: '100%' } } />
													</div>
												) : null
											}
											<BaseControl help={ __( 'Square images are required to properly display within podcatcher apps.Minimum size: 1400 px x 1400 px. Maximum size: 2048 px x 2048 px.', 'simple-podcasting' ) } />
										</>
									) }
								/>
							</MediaUploadCheck>
						</div>

						<Flex justify="normal" gap={ 9 }>
							<FlexItem>
								<Button
									className="podcasting__create-podcast-btn"
									variant="primary"
									text={ __( 'Create Podcast', 'simple-podcasting' ) }
									disabled={ ! showName || '' === showCategory || ! artistName || ! summary || ! coverId }
									onClick={ createShow }
									isBusy={ ajaxInprogress }
								/>
							</FlexItem>
							<FlexItem>
								<Button
									variant="link"
									text={ __( 'Cancel', 'simple-podcasting' ) }
									onClick={ closeModal }
								/>
							</FlexItem>
						</Flex>
					</>
				)
			}
		</Modal>
	);
};

const CreatePodcastShowPlugin = () => {
	const { allPodcasts, attachedPodcasts, currentPostId } = useSelect( ( select ) => {
		const { getEntityRecords } = select( coreDataStore );
		const { getCurrentPostId } = select( editorStore );

		return {
			allPodcasts: getEntityRecords( 'taxonomy', 'podcasting_podcasts', DEFAULT_QUERY ) || [],
			attachedPodcasts: getEntityRecords( 'taxonomy', 'podcasting_podcasts', { post: getCurrentPostId() } ) || [],
			currentPostId: getCurrentPostId(),
		}
	} );

	// Remove the default 'Podcast' taxonomy panel.
	useEffect( () => {
		dispatch( editPostStore ).removeEditorPanel( 'taxonomy-panel-podcasting_podcasts' );
	}, [] );

	const [ isModalOpen, setIsModalOpen ] = useState( false );
	const openModal = () => setIsModalOpen( true );
	const closeModal = () => setIsModalOpen( false );
	const attachedPodcastIds = useSelect( ( select ) => {
		return select( 'core/editor' ).getEditedPostAttribute( 'podcasting_podcasts' );
	} );

	/**
	 * Attaches the podcast term to the current post if selected.
	 *
	 * @param {Boolean} isChecked If the podcast term checkbox is checked.
	 * @param {Integer} podcastId The podcast term ID.
	 */
	function attachPodcastToPost( isChecked, podcastId ) {
		let updatedAttachedPodcastIds = [ ...attachedPodcastIds, podcastId ];

		if ( isChecked ) {
			updatedAttachedPodcastIds = [ ...attachedPodcastIds, podcastId ];
		} else {
			updatedAttachedPodcastIds = attachedPodcastIds.filter( ( currentPodcastId ) => currentPodcastId !== podcastId );
		}

		dispatch( coreDataStore ).editEntityRecord(
			'postType',
			'post',
			currentPostId,
			{
				podcasting_podcasts: updatedAttachedPodcastIds,
			}
		)
	}

	return (
		<>
			<PluginDocumentSettingPanel
				title={ __( 'Podcasts', 'simple-podcasting' ) }
				className='podcasting__podcast-list'
			>
				{
					allPodcasts.map( ( item, index ) => {
						return (
							<CheckboxControl
								className="podcasting__podcast-list-item"
								key={ index }
								label={ item.name }
								onChange={ ( isChecked ) => attachPodcastToPost( isChecked, item.id ) }
								checked={ attachedPodcastIds.includes( item.id ) }
							/>
						)
					} )
				}
				<Button
					variant="link"
					text={ __( 'Add New Podcast', 'simple-podcasting' ) }
					onClick={ openModal }
					style={ { marginTop: '12px' } }
					className="podcasting__add-new-podcast"
				/>
			</PluginDocumentSettingPanel>
			<CreatePodcastShowModal
				isModalOpen={ isModalOpen }
				openModal={ openModal }
				closeModal={ closeModal }
			/>
		</>
	)
};

registerPlugin( 'podcasting-create-podcast-show', {
	render: CreatePodcastShowPlugin
} );


================================================
FILE: assets/js/deprecated.js
================================================
export default [
	{
		attributes: {
			id: {
				type: 'number',
			},
			src: {
				type: 'string',
				source: 'attribute',
				selector: 'audio',
				attribute: 'src',
			},
			url: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_url',
			},
			filesize: {
				type: 'number',
				source: 'meta',
				meta: 'podcast_filesize',
			},
			duration: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_duration',
			},
			mime: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_mime',
			},
			caption: {
				type: 'array',
				source: 'children',
				selector: 'figcaption',
			},
			captioned: {
				type: 'boolean',
				source: 'meta',
				meta: 'podcast_captioned',
				default: false,
			},
			explicit: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_explicit',
				default: 'no',
			},
			enclosure: {
				type: 'string',
				source: 'meta',
				meta: 'enclosure',
			},
			seasonNumber: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_season_number',
			},
			episodeNumber: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_episode_number',
			},
			episodeType: {
				type: 'string',
				source: 'meta',
				meta: 'podcast_episode_type',
			}
		},
		supports: {
			multiple: false,
		},
		save: props => {
			const {
				id,
				src,
				caption
			} = props.attributes;

			return (
				<figure className={ id ? `podcast-${ id }` : null }>
					<audio controls="controls" src={ src } />
					{ caption && caption.length > 0 && <figcaption>{ caption }</figcaption> }
				</figure>
			);
		},
	},
];


================================================
FILE: assets/js/edit.js
================================================
const { __ } = wp.i18n;
const {
	BlockControls,
	InspectorControls,
	MediaPlaceholder,
	MediaReplaceFlow,
	MediaUpload,
	MediaUploadCheck,
	RichText,
} = wp.blockEditor;
const {
	ToggleControl,
	PanelBody,
	PanelRow,
	SelectControl,
	TextControl,
	RadioControl,
} = wp.components;
const { Fragment } = wp.element;

const { apiFetch } = wp;
const ALLOWED_MEDIA_TYPES = ['audio'];
const { select } = wp.data;

import { Button } from '@wordpress/components';
import { useState, useEffect } from '@wordpress/element';
import { dispatch, useSelect, useDispatch } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';

function useFeaturedImage() {
    const featuredImageId = useSelect((select) => select('core/editor').getEditedPostAttribute('featured_media'), []);
    const { editPost } = useDispatch('core/editor');

    const featuredImageUrl = useSelect((select) => {
        const { getMedia } = select('core');
        const image = getMedia(featuredImageId);
        return image?.source_url;
    }, [featuredImageId]);

    const setFeaturedImage = (imageId) => {
        editPost({ featured_media: imageId });
    };

	const removeFeaturedImage = () => {
		editPost({ featured_media: 0 });
	};

    return { featuredImageUrl, setFeaturedImage, removeFeaturedImage, featuredImageId };
}

function Edit( props ) {
	const {
		className,
		setAttributes,
		isSelected,
		attributes,
		featuredImageUrl,
		setFeaturedImage,
		removeFeaturedImage,
		featuredImageId
	} = props;

	const {
		caption,
		explicit,
		displayDuration,
		displayShowTitle,
		displayEpisodeTitle,
		displayArt,
		displayExplicitBadge,
		displaySeasonNumber,
		displayEpisodeNumber,
		displayEpisodeType
	} = attributes;

	const duration = attributes.duration || '';
	const captioned = attributes.captioned || '';
	const seasonNumber = attributes.seasonNumber || '';
	const episodeNumber = attributes.episodeNumber || '';
	const episodeType = attributes.episodeType || '';

	const [ src, setSrc ] = useState( props.attributes.src );

	const postTitle = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( 'title' ) );

	const onSelectAttachment = (attachment) => {
		// Upload and Media Library return different attachment objects.
		// Therefore, we need to check the existence of some entries.
		let mime, filesize, duration;

		if (attachment.mime) {
			mime = attachment.mime;
		} else if (attachment.mime_type) {
			mime = attachment.mime_type;
		}

		if (attachment.filesizeInBytes) {
			filesize = attachment.filesizeInBytes;
		} else if (
			attachment.media_details &&
			attachment.media_details.filesize
		) {
			filesize = attachment.media_details.filesize;
		}

		if (attachment.fileLength) {
			duration = attachment.fileLength;
		} else if (
			attachment.media_details &&
			attachment.media_details.length_formatted
		) {
			duration = attachment.media_details.length_formatted;
		}

		setAttributes({
			id: attachment.id,
			src: attachment.url,
			url: attachment.url,
			mime,
			filesize,
			duration,
			caption: attachment.title,
			enclosure: attachment.url + '\n' + filesize + '\n' + mime,
		});
		setSrc( attachment.url );
	};

	const onSelectURL = (newSrc) => {
		if (newSrc !== src) {
			apiFetch({
				path: `simple-podcasting/v1/external-url/?url=${newSrc}`,
			})
				.then((res) => {
					if (res.success) {
						const { mime, filesize, duration } = res.data;
						setAttributes({
							src: newSrc,
							url: newSrc,
							id: null,
							mime,
							filesize,
							duration,
							displayDurationValue: duration,
							caption: '',
						});
					}
				})
				.catch((err) => {
					// eslint-disable-next-line no-console
					console.error(err);
				});

			setSrc( newSrc );
		}
	};

	const controls = (
		<BlockControls key="controls">
			{src ? (
				<MediaReplaceFlow
					mediaURL={attributes.src}
					allowedTypes={ALLOWED_MEDIA_TYPES}
					accept="audio/*"
					onSelect={onSelectAttachment}
					onSelectURL={onSelectURL}
				/>
			) : null}
		</BlockControls>
	);

	const showId = useSelect( ( __select ) => {
		const attachedPodcastIds = __select( 'core/editor' ).getEditedPostAttribute( 'podcasting_podcasts' );
		return attachedPodcastIds ? attachedPodcastIds[0] : null;
	} );
	const show = useSelect( ( __select ) => {
		return __select('core').getEntityRecords('taxonomy', 'podcasting_podcasts', {
			include: [ showId ],
		});
	} );

	const showName = show && show[0] ? show[0]?.name : null;
	const showImage = show && show[0] ? show[0]?.meta?.podcasting_image_url : null;

	const onUpdateImage = (image) => {
		setFeaturedImage(image.id);
	};

	return (
		<Fragment>
			{controls}
			<InspectorControls>
				<PanelBody
					title={__('Podcast Settings', 'simple-podcasting')}
					className="simple-podcast-settings"
				>
					<PanelRow>
						<ToggleControl
							id="podcast-captioned-form-toggle"
							label={__(
								'Closed Captioned',
								'simple-podcasting'
							)}
							checked={captioned}
							onChange={() => setAttributes({ captioned: !captioned})}
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={__(
								'Display Listen Time',
								'simple-podcasting'
							)}
							checked={displayDuration}
							onChange={() => setAttributes({ displayDuration: !displayDuration})}
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={__(
								'Display Show Title',
								'simple-podcasting'
							)}
							checked={displayShowTitle}
							onChange={() => setAttributes({ displayShowTitle: !displayShowTitle})}
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={__(
								'Display Episode Title',
								'simple-podcasting'
							)}
							checked={displayEpisodeTitle}
							onChange={() => setAttributes({ displayEpisodeTitle: !displayEpisodeTitle})}
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={__(
								'Display Show Art',
								'simple-podcasting'
							)}
							checked={displayArt}
							onChange={() => setAttributes({ displayArt: !displayArt})}
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={__(
								'Display Explicit Badge',
								'simple-podcasting'
							)}
							checked={displayExplicitBadge}
							onChange={() => setAttributes({ displayExplicitBadge: !displayExplicitBadge})}
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={__(
								'Display Season Number',
								'simple-podcasting'
							)}
							checked={displaySeasonNumber}
							onChange={() => setAttributes({ displaySeasonNumber: !displaySeasonNumber})}
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={__(
								'Display Episode Number',
								'simple-podcasting'
							)}
							checked={displayEpisodeNumber}
							onChange={() => setAttributes({ displayEpisodeNumber: !displayEpisodeNumber})}
							help={
								! displayEpisodeTitle
								&& __( 'The "Display Episode Title" setting should be enabled for this to display the episode number.', 'simple-podcasting' )
							}
						/>
					</PanelRow>
					<PanelRow>
						<ToggleControl
							label={__(
								'Display Episode Type',
								'simple-podcasting'
							)}
							checked={displayEpisodeType}
							onChange={() => setAttributes({ displayEpisodeType: !displayEpisodeType})}
						/>
					</PanelRow>
					<PanelRow>
						<SelectControl
							label={__(
								'Explicit Content',
								'simple-podcasting'
							)}
							value={explicit}
							options={[
								{
									value: 'no',
									label: __('No', 'simple-podcasting'),
								},
								{
									value: 'yes',
									label: __('Yes', 'simple-podcasting'),
								},
								{
									value: 'clean',
									label: __('Clean', 'simple-podcasting'),
								},
							]}
							onChange={(explicit) =>
								setAttributes({ explicit })
							}
						/>
					</PanelRow>
					<PanelRow>
						<TextControl
							label={__(
								'Length (MM:SS)',
								'simple-podcasting'
							)}
							value={duration}
							onChange={(duration) =>
								setAttributes({ duration })
							}
						/>
					</PanelRow>
					<PanelRow>
						<TextControl
							label={__('Season Number', 'simple-podcasting')}
							value={seasonNumber}
							onChange={(seasonNumber) =>
								setAttributes({ seasonNumber })
							}
						/>
					</PanelRow>
					<PanelRow>
						<TextControl
							label={__(
								'Episode Number',
								'simple-podcasting'
							)}
							value={episodeNumber}
							onChange={(episodeNumber) =>
								setAttributes({ episodeNumber })
							}
						/>
					</PanelRow>
					<PanelRow>
						<RadioControl
							label={__('Episode Type', 'simple-podcasting')}
							selected={episodeType}
							options={[
								{
									label: __('None', 'simple-podcasting'),
									value: 'none',
								},
								{
									label: __('Full', 'simple-podcasting'),
									value: 'full',
								},
								{
									label: __(
										'Trailer',
										'simple-podcasting'
									),
									value: 'trailer',
								},
								{
									label: __('Bonus', 'simple-podcasting'),
									value: 'bonus',
								},
							]}
							onChange={(episodeType) =>
								setAttributes({ episodeType })
							}
						/>
					</PanelRow>
					<PanelRow>
						<Button
							variant="secondary"
							onClick={() =>
								dispatch('core/block-editor').insertBlocks(
									createBlock(
										'podcasting/podcast-transcript'
									)
								)
							}
						>
							{__('Add Transcript', 'simple-podcasting')}
						</Button>
					</PanelRow>
					<h3 style={{marginTop: '20px'}}>{__('Cover Image', 'simple-podcasting')}</h3>
					<p>{__('The featured image of the current post is used as the episode cover art. Please select a featured image to set it.', 'simple-podcasting')}</p>
					<PanelRow className="cover-art-container">
						{featuredImageUrl && (
							<img src={featuredImageUrl} alt="Cover Image" />
						)}

						<MediaUploadCheck>
							<MediaUpload
								onSelect={onUpdateImage}
								allowedTypes={['image']}
								render={({ open }) => (
									<Button isSecondary onClick={open}>
										{featuredImageUrl ?
										__('Replace Cover Art', 'simple-podcasting')
										:
										__('Select Cover Art', 'simple-podcasting')
										}
									</Button>
								)}
								value={featuredImageId}
							/>
						</MediaUploadCheck>
						{featuredImageUrl && (
							<Button isLink isDestructive onClick={removeFeaturedImage}>{__('Delete Cover Art', 'simple-podcasting')}</Button>
						)}
					</PanelRow>
				</PanelBody>
			</InspectorControls>
			<div className="wp-block-podcasting-podcast-outer">
				{src ? (
					<>
						<div className="wp-block-podcasting-podcast__container">
							{displayArt && (featuredImageUrl || showImage) && (
								<div className="wp-block-podcasting-podcast__show-art">
									<div className="wp-block-podcasting-podcast__image">
										<img
											src={featuredImageUrl ? featuredImageUrl : showImage}
											alt={showName}
										/>
									</div>
								</div>
							)}

							<div className="wp-block-podcasting-podcast__details">

								{displayEpisodeTitle && postTitle && (
									<h3 className="wp-block-podcasting-podcast__show-title">
										{displayEpisodeNumber && episodeNumber && (
											<span>
												{episodeNumber}.
											</span>
										)}
										{postTitle}
									</h3>
								)}

								<div className="wp-block-podcasting-podcast__show-details">
									{displayShowTitle && (
										<span className="wp-block-podcasting-podcast__title">
											{showName}
										</span>
									)}
									{displaySeasonNumber && seasonNumber && (
										<span className="wp-block-podcasting-podcast__season">
											{__(
												'Season: ',
												'simple-podcasting'
											)}
											{seasonNumber}
										</span>
									)}
									{displayEpisodeNumber && episodeNumber && (
										<span className="wp-block-podcasting-podcast__episode">
											{__('Episode: ', 'simple-podcasting')}
											{episodeNumber}
										</span>
									)}
								</div>

								<div className="wp-block-podcasting-podcast__show-details">
									{displayDuration && duration && (
										<span className="wp-block-podcasting-podcast__duration">
											{__('Listen Time: ', 'simple-podcasting')}
											{duration}
										</span>
									)}
									{displayEpisodeType && (episodeType !== 'none') && (
										<span className="wp-block-podcasting-podcast__episode-type">
											{__(
												'Episode type: ',
												'simple-podcasting'
											)}
											{episodeType}
										</span>
									)}
									{displayExplicitBadge && (
										<span className="wp-block-podcasting-podcast__explicit-badge">
											{__(
												'Explicit: ',
												'simple-podcasting'
											)}
											{explicit}
										</span>
									)}
								</div>
							</div>
						</div>

						<figure key="audio" className={className}>
							{((caption && caption.length) || !!isSelected) && (
								<RichText
									tagName="figcaption"
									placeholder={__(
										'Write caption…',
										'simple-podcasting'
									)}
									className="wp-block-podcasting-podcast__caption"
									value={caption}
									onChange={(value) =>
										setAttributes({ caption: value })
									}
									isSelected={isSelected}
								/>
							)}
							<audio controls="controls" src={src} />
						</figure>
					</>
				) : (
					<MediaPlaceholder
						icon="microphone"
						labels={{
							title: __('Podcast', 'simple-podcasting'),
							name: __(
								'a podcast episode',
								'simple-podcasting'
							),
						}}
						className={className}
						onSelect={onSelectAttachment}
						onSelectURL={onSelectURL}
						accept="audio/*"
						allowedTypes={ALLOWED_MEDIA_TYPES}
						value={attributes}
					/>
				)}
			</div>
		</Fragment>
	);
}

function PodcastBlockWithHooks(props) {
    const featuredImageProp = useFeaturedImage();

    return <Edit {...props} {...featuredImageProp} />;
}

export default PodcastBlockWithHooks;


================================================
FILE: assets/js/onboarding.js
================================================
import '../css/podcasting-onboarding.scss';

( function( $ ) {
	$( function() {
		const selectImageBtn = $( '#simple-podcasting__upload-cover-image' );
		const coverImage = $( 'input[name="podcast-cover-image-id"]' );
		const coverImagePreview = $( '#simple-podcasting__cover-image-preview' );
		let uploader_frame = null;

		/** Upload image button handler */
		selectImageBtn.on( 'click', function() {
			uploader_frame = wp.media( {
				multiple: false,
				library: {
					type: 'image'
				}
			} ).on( 'select', function() {
				const { id, url } = uploader_frame.state().get( 'selection' ).first().toJSON();
				coverImagePreview.html( `<img src="${ url }" />` )
				coverImage.val( id );
			} );

			uploader_frame.open();
		} );
	} )
} )( jQuery )

================================================
FILE: assets/js/podcasting-edit-post.js
================================================
/*global jQuery */
jQuery( document ).ready( function( $ ) {
	$( '#podcasting-enclosure-button' ).click( function( e ) {
		e.preventDefault();

		var $this = $( this ),
			$input = $( 'input#podcasting-enclosure-url' ),
			mediaUploader;

		// If the uploader object has already been created, reopen the dialog.
		if ( mediaUploader ) {
			mediaUploader.open();
			return;
		}

		// eslint-disable-next-line camelcase
		mediaUploader = wp.media.frames.file_frame = wp.media( {
			title: $this.data( 'modalTitle' ),
			button: {
				text: $this.data( 'modalButton' )
			},
			library: {
				type: 'audio'
			},
			multiple: false
		});

		mediaUploader.off( 'select' );
		mediaUploader.on( 'select', function() {
			var attachment = mediaUploader.state().get('selection').first();

			$input.val( attachment.get('url') );
		});

		mediaUploader.open();
	} );
} );


================================================
FILE: assets/js/podcasting-edit-term.js
================================================
/*global jQuery, validateForm*/
import '../css/podcasting-edit-term.css';

jQuery( document ).ready( function( $ ) {

	// Clear Image Field.
	function clearImageField( el ) {
		var $link   = $( el ),
			$wrapper  = $link.parents( '.media-wrapper' ),
			$button   = $wrapper.find( '.podcasting-media-button' ),
			$hidden   = $( document.getElementById( $button.data( 'slug' ) ) ),
			$existing = $wrapper.find( '.podasting-existing-image' ),
			$upload   = $wrapper.find( '.podcasting-upload-image' );

		// Update the display.
		$upload.removeClass('hidden');
		$existing.addClass('hidden');
		$hidden.val( '' );
	}

	// When the term add button is clicked, reset the dropdown fields.
	$( '#submit' ).click( function() {

		var $form = $( 'form#addtag' );

		if ( ! validateForm( $form ) ) {
			return;
		}

		// Add a brief delay to allow the form to submit.
		setTimeout( function() {
			$( '.fm-select select' ).val( 'None' );
			clearImageField( '.podcast-media-remove' );
			$( '#podcasting_category_1,#podcasting_category_2,#podcasting_category_3' ).val( '' );
			window.scrollTo(0,0);
		}, 500 );
	} );

	var mediaUploader;

	// Handle media upload buttons.
	$( 'input.podcasting-media-button' ).on( 'click', function( e ) {
		e.preventDefault();

		var $button   = $( e.currentTarget ),
			$hidden   = $( document.getElementById( $button.data( 'slug' ) ) ),
			$wrapper  = $button.parents( '.media-wrapper' ),
			$image    = $wrapper.find( 'img' ),
			$existing = $wrapper.find( '.podasting-existing-image' ),
			$upload   = $wrapper.find( '.podcasting-upload-image' );

		// If the uploader object has already been created, reopen the dialog.
		if (mediaUploader) {
			mediaUploader.open();
			return;
		}
		// Extend the wp.media object.
		// eslint-disable-next-line camelcase
		mediaUploader = wp.media.frames.file_frame = wp.media( {
			title: $button.data( 'choose' ),
			button: {
				text: $button.data( 'update' )
			},
			multiple: false
		});

		// When a file is selected, grab the URL and set it as the text field's value.
		mediaUploader.off( 'select' );
		mediaUploader.on( 'select', function() {
			var attachment = mediaUploader.state().get('selection').first();

			// Set the hidden field value.
			$hidden.val( attachment.get('id') );

			// Update the display.
			$upload.addClass('hidden');
			$existing.removeClass('hidden');
			$image.attr( 'src', attachment.get('url') );

		});

		// Open the uploader dialog
		mediaUploader.open();
	});

	// Handle media remove buttons.
	$( '.podcast-media-remove' ).on( 'click', function( e ) {
		e.preventDefault();
		clearImageField( e.currentTarget );
	} );

	const iconThemeRadioEl = $( 'input[name="podcasting_icon_theme"]' );
	const iconWrappers = $( '.simple_podcasting__platforms-icon' );

	iconThemeRadioEl.on( 'change', function() {
		const current = $( this );
		const selected = current.val();

		if ( 'white' === selected ) {
			iconWrappers.addClass( 'simple_podcasting__platforms-icon--darken-bg' );
		} else {
			iconWrappers.removeClass( 'simple_podcasting__platforms-icon--darken-bg' );
		}

		iconWrappers.each( ( index, icon ) => {
			const imgEl = $( icon ).find( 'img' );
			const platform = imgEl.data( 'platform' );
			imgEl.attr( 'src', `${ podcastingEditPostVars.iconUrl }/${ platform }/${ selected }-100.png` );
		});
	} );
} );



================================================
FILE: assets/js/transforms.js
================================================
/**
 * WordPress dependencies
 */
const { select } = wp.data;
const { createBlock } = wp.blocks;

/**
 * Transforms
 */
const transforms = {
	from: [
		{
			type: 'block',
			blocks: [ 'core/audio' ],
			transform: ( attributes ) => {
				return createBlock( 'podcasting/podcast', {
					id: attributes.id,
					src: attributes.src
				} );
			},
		},
	],
	to: [
		{
			type: 'block',
			blocks: [ 'core/audio' ],
			isMatch: ( { id } ) => {
				if ( ! id ) {
					return false;
				}
				const { getMedia } = select( 'core' );
				const media = getMedia( id );
				return !! media && media.mime_type.includes( 'audio' );
			},
			transform: ( attributes ) => {
				return createBlock( 'core/audio', {
					src: attributes.src,
					id: attributes.id
				} );
			},
		},
	],

};

export default transforms;


================================================
FILE: composer.json
================================================
{
  "name": "10up/simple-podcasting",
  "description": "A simple podcasting solution for WordPress. ",
  "homepage": "https://github.com/10up/simple-podcasting",
  "license": "GPL-2.0-or-later",
  "authors": [
    {
      "name": "10up",
      "email": "opensource@10up.com",
      "homepage": "https://10up.com"
    }
  ],
  "support": {
    "issues": "https://github.com/10up/simple-podcasting/issues"
  },
  "require": {
    "php": ">=7.3"
  },
  "require-dev": {
    "10up/phpcs-composer": "^3.0",
    "10up/wp_mock": "^0.4.2",
    "phpunit/phpunit": "^9.5",
    "phpcompatibility/php-compatibility": "dev-develop as 9.99.99"
  },
  "config": {
    "allow-plugins": {
      "dealerdirect/phpcodesniffer-composer-installer": true
    }
  }
}


================================================
FILE: includes/admin/create-podcast-component.php
================================================
<?php
/**
 * Defines class to handle creation of a podcast
 * from with the Post editor.
 *
 * @package tenup_podcasting
 */

namespace tenup_podcasting\admin;

/**
 * Adds methods to create a podcast using the Gutenberg inspector component.
 */
class Create_Podcast_Component {
	/**
	 * Constructor method.
	 */
	public function __construct() {
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
	}

	/**
	 * Admin enqueue scripts.
	 */
	public function admin_enqueue_scripts() {
		$screen = get_current_screen();

		if ( ! ( $screen && 'post' === $screen->id ) ) {
			return;
		}

		wp_enqueue_script(
			'podcasting_create_podcast_show_plugin',
			PODCASTING_URL . 'dist/create-podcast-show.js',
			array(),
			PODCASTING_VERSION,
			true
		);

		wp_localize_script(
			'podcasting_create_podcast_show_plugin',
			'podcastingShowPluginVars',
			array(
				'categories' => \tenup_podcasting\get_podcasting_categories_options(),
			)
		);
	}
}

new Create_Podcast_Component();


================================================
FILE: includes/admin/onboarding.php
================================================
<?php
/**
 * Registers and renders the onboarding setup wizard.
 *
 * @package tenup_podcasting
 */

namespace tenup_podcasting\admin;

/**
 * Adds methods required for handling the onboarding wizard.
 */
class Onboarding {
	/**
	 * Indicates onboarding is in progress.
	 *
	 * @var string
	 */
	const STATUS_IN_PROGRESS = 'in-progress';

	/**
	 * Indicates onboarding is complete.
	 *
	 * @var string
	 */
	const STATUS_COMPLETED = 'completed';

	/**
	 * Holds the object for Create_Podcast.
	 *
	 * @var \tenup_podcasting\Create_Podcast
	 */
	protected $create_podcast;

	/**
	 * Constructor
	 */
	public function __construct() {
		$this->create_podcast = new \tenup_podcasting\Create_Podcast();

		add_action( 'admin_menu', array( $this, 'register_onoarding_page' ) );
		add_action( 'admin_init', array( $this, 'onboarding_action_handler' ) );
	}

	/**
	 * Registers a hidden sub menu page for the onboarding wizard.
	 */
	public function register_onoarding_page() {
		add_submenu_page(
			'admin.php',
			esc_html__( 'Simple Podcasting Onboarding' ),
			'',
			'manage_options',
			'simple-podcasting-onboarding',
			array( $this, 'render_page_contents' )
		);

		if ( 'no' === get_option( 'simple_podcasting_onboarding', '' ) ) {
			update_option( 'simple_podcasting_onboarding', self::STATUS_IN_PROGRESS );
			wp_safe_redirect( admin_url( 'admin.php?page=simple-podcasting-onboarding&step=1' ) );
			die();
		}
	}

	/**
	 * Renders the page content for the onboarding wizard.
	 */
	public function render_page_contents() {
		$step = filter_input( INPUT_GET, 'step', FILTER_VALIDATE_INT );

		if ( ! $step ) {
			$step = 1;
		}

		require_once 'views/onboarding-header.php';

		switch ( $step ) {
			case 1:
				require_once 'views/onboarding-page-one.php';
				break;

			case 2:
				require_once 'views/onboarding-page-two.php';
				break;

			default:
				break;
		}
	}

	/**
	 * Onboarding data saving handler.
	 */
	public function onboarding_action_handler() {
		if ( ! $this->create_podcast->verify_nonce() ) {
			return;
		}

		$this->create_podcast->sanitize_podcast_fields();

		$is_sanitized = $this->create_podcast->save_podcast_fields();

		if ( is_wp_error( $is_sanitized ) ) {
			$error_message = $is_sanitized->get_error_message();

			add_action(
				'admin_notices',
				function () use ( $error_message ) {
					if ( empty( $error_message ) ) {
						return;
					}
					?>
					<div class="notice notice-error is-dismissible">
						<p><?php echo wp_kses_post( $error_message ); ?></p>
					</div>
					<?php
				}
			);

			return;
		}

		if ( self::STATUS_IN_PROGRESS === get_option( 'simple_podcasting_onboarding', '' ) ) {
			wp_safe_redirect( admin_url( 'admin.php?page=simple-podcasting-onboarding&step=2' ) );
			die;
		}
	}
}

$onbarding_status = get_option( 'simple_podcasting_onboarding', '' );

if ( Onboarding::STATUS_COMPLETED !== $onbarding_status ) {
	new Onboarding();
}


================================================
FILE: includes/admin/views/onboarding-header.php
================================================
<?php
/**
 * Header template for onboarding: Step - 1.
 *
 * @package tenup_podcasting
 */

?>
<div id="simple-podcasting__onboarding-header">
	<div id="simple-podcasting__branding">
		<div id="simple-podcasting__logo">
			<img src="https://ps.w.org/simple-podcasting/assets/icon-128x128.png" />
		</div>
		<div id="simple-podcasting__plugin-name">
			<?php esc_html_e( 'Simple Podcasting', 'simple-podcasting' ); ?>
		</div>
	</div>

	<div id="simple-podcasting__header-title">
		<?php esc_html_e( 'Get Started With Podcasting', 'simple-podcasting' ); ?>
	</div>

	<div id="simple-podcasting__header-controls">
		<a href="<?php echo esc_url( admin_url( 'plugins.php' ) ); ?>" class="simple-podcasting__btn simple-podcasting__btn--ghost" id="simple-podcasting-btn__skip-onboarding"><?php esc_html_e( 'Skip Setup', 'simple-podcasting' ); ?></a>
	</div>
</div>


================================================
FILE: includes/admin/views/onboarding-page-one.php
================================================
<?php
/**
 * Body template for onboarding: Step - 1.
 *
 * @package tenup_podcasting
 */

?>
<form method="POST" action="">
	<div class="simple-podcasting__onboarding-body simple-podcasting__onboarding-body--step-1">
		<div id="simple-podcasting__page-title">
			<?php esc_html_e( 'Create your first podcast show', 'simple-podcasting' ); ?>
		</div>

		<!-- Podcast name -->
		<div class="simple-podcasting__setting">
			<label class="simple-podcasting__setting-label" for="simple-podcasting-podcast-name"><?php esc_html_e( 'Podcast name*', 'simple-podcasting' ); ?></label>
			<div class="simple-podcasting__setting-input"><input name="podcast-name" id="simple-podcasting-podcast-name" aria-describedby="simple-podcasting__podcast-name-description" required type="text" /></div>
			<div class="simple-podcasting__setting-description" id="simple-podcasting__podcast-name-description"><?php esc_html_e( 'What’s the title of your podcast show that listeners will see?', 'simple-podcasting' ); ?></div>
		</div>

		<!-- Podcast artist/author -->
		<div class="simple-podcasting__setting">
			<label class="simple-podcasting__setting-label" for="simple-podcasting-podcast-artist"><?php esc_html_e( 'Podcast Artist / Author name*', 'simple-podcasting' ); ?></label>
			<div class="simple-podcasting__setting-input"><input name="podcast-artist" id="simple-podcasting-podcast-artist" aria-describedby="simple-podcasting__podcast-artist-description" required type="text" /></div>
			<div class="simple-podcasting__setting-description" id="simple-podcasting__podcast-artist-description"><?php esc_html_e( 'Who’s the artist or author of your podcast show that listeners will see?', 'simple-podcasting' ); ?></div>
		</div>

		<!-- Podcast description -->
		<div class="simple-podcasting__setting">
			<label class="simple-podcasting__setting-label" for="simple-podcasting-podcast-description"><?php esc_html_e( 'Podcast summary*', 'simple-podcasting' ); ?></label>
			<div class="simple-podcasting__setting-input"><textarea name="podcast-description" id="simple-podcasting-podcast-description" aria-describedby="simple-podcasting__podcast-description-description" rows="5" required></textarea></div>
			<div class="simple-podcasting__setting-description" id="simple-podcasting__podcast-description-description"><?php esc_html_e( 'Briefly describe to your listeners what your podcast is about. (No HTML please)', 'simple-podcasting' ); ?></div>
		</div>

		<!-- Cover image -->
		<div class="simple-podcasting__setting">
			<input type="hidden" name="podcast-cover-image-id" id="simple-podcasting-podcast-cover-image-id" aria-describedby="simple-podcasting__cover-image-description" value="" required>
			<label class="simple-podcasting__setting-label" for="simple-podcasting-podcast-cover-image-id"><?php esc_html_e( 'Cover image*', 'simple-podcasting' ); ?></label>
			<div id="simple-podcasting__cover-image-preview"></div>
			<button type="button" class="simple-podcasting__btn simple-podcasting__btn--ghost" id="simple-podcasting__upload-cover-image"><?php esc_html_e( 'Select image', 'simple-podcasting' ); ?></button>
			<div class="simple-podcasting__setting-description" id="simple-podcasting__cover-image-description"><?php esc_html_e( 'Minimum size: 1400px x 1400 px — maximum size: 2048px x 2048px. Make sure the image is square so it will properly display within podcatcher apps.', 'simple-podcasting' ); ?></div>
		</div>

		<!-- Podcast category -->
		<div class="simple-podcasting__setting">
			<label class="simple-podcasting__setting-label" for="simple-podcasting-podcast-category"><?php esc_html_e( 'Podcast category*', 'simple-podcasting' ); ?></label>
			<select name="podcast-category" id="simple-podcasting-podcast-category" aria-describedby="simple-podcasting__podcast-category-description" required>
				<?php foreach ( \tenup_podcasting\get_podcasting_categories_options() as $option_value => $option_label ) : ?>
					<option value="<?php echo esc_attr( $option_value ); ?>"><?php echo esc_html( $option_label ); ?></option>
				<?php endforeach; ?>
			</select>
			<div class="simple-podcasting__setting-description" id="simple-podcasting__podcast-category-description"><?php esc_html_e( 'What’s the category the listeners will use to discover your podcast under when browsing  podcatchers?', 'simple-podcasting' ); ?></div>
		</div>

		<!-- Create button -->
		<button class="simple-podcasting__btn simple-podcasting__btn--black" id="simple-podcasting__create-podcast-button"><?php esc_html_e( 'Create', 'simple-podcasting' ); ?></button>
		<?php wp_nonce_field( 'simple-podcasting-create-show-action', 'simple-podcasting-create-show-nonce-field' ); ?>
	</div>
</form>


================================================
FILE: includes/admin/views/onboarding-page-two.php
================================================
<?php
/**
 * Body template for onboarding: Step - 2.
 *
 * @package tenup_podcasting
 */

use tenup_podcasting\admin\Onboarding;

?>
<div class="simple-podcasting__onboarding-body simple-podcasting__onboarding-body--step-2">
	<div class="simple-podcasting__panel simple-podcasting__panel--left">
		<div id="simple-podcasting__page-title">
			<?php esc_html_e( 'Well done!', 'simple-podcasting' ); ?>
		</div>

		<p>
			<?php
			printf(
				/* translators: %s podcast term edit page. */
				__( 'You can always edit show details <a href="%s">here</a>.', 'simple-podcasting' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 
				esc_url( admin_url( 'edit-tags.php?taxonomy=podcasting_podcasts&podcasts=true' ) )
			);
			?>
		</p>
		<p><?php esc_html_e( 'Now let’s create your show’s first episode:', 'simple-podcasting' ); ?></p>
		<ol>
			<li><?php esc_html_e( 'Create a new post', 'simple-podcasting' ); ?></li>
			<li><?php esc_html_e( 'Assign the post to a show', 'simple-podcasting' ); ?></li>
			<li><?php esc_html_e( 'Insert a podcast block with an audio file into the content', 'simple-podcasting' ); ?></li>
		</ol>
		<p><?php esc_html_e( 'You can then submit the feed URL to podcatchers. The feed will automatically update each time you add a new episode.', 'simple-podcasting' ); ?></p>
		<p><?php esc_html_e( "Let's get started!", 'simple-podcasting' ); ?></p>

		<div class="simple-podcasting__step-2-controls">
			<a href="<?php echo esc_url( admin_url( 'post-new.php' ) ); ?>" class="simple-podcasting__btn simple-podcasting__btn--black" id="simple-podcasting__create-a-new-post-button"><?php esc_html_e( 'Create a new Post', 'simple-podcasting' ); ?></a>
			<a href="<?php echo esc_url( admin_url( 'edit-tags.php?taxonomy=podcasting_podcasts&podcasts=true' ) ); ?>"><?php esc_html_e( 'Create another Show', 'simple-podcasting' ); ?></a>
		</div>
	</div>

	<div class="simple-podcasting__panel simple-podcasting__panel--right">
		<div class="simple-podcasting__podcast-block-preview">
			<img
				srcset="<?php echo esc_url( PODCASTING_URL . 'dist/assets/images/podcast-block-preview.png' ); ?>, <?php echo esc_url( PODCASTING_URL . 'dist/assets/images/podcast-block-preview@2x.png' ); ?> 2x"
				src="<?php echo esc_url( PODCASTING_URL . 'dist/assets/images/podcast-block-preview.png' ); ?>"
			/>
		</div>
	</div>
	<?php update_option( 'simple_podcasting_onboarding', Onboarding::STATUS_COMPLETED ); ?>
</div>


================================================
FILE: includes/block-patterns.php
================================================
<?php
/**
 * Register and enqueue all things block patterns.
 *
 * @package tenup_podcasting
 */

namespace tenup_podcasting\block_patterns;

/**
 * Register block and its assets.
 */
function init() {
	$podcast_terms = get_terms(
		[
			'taxonomy' => PODCASTING_TAXONOMY_NAME,
			'fields'   => 'ids',
		]
	);

	if ( empty( $podcast_terms ) ) {
		return;
	}

	register_block_pattern(
		'podcasting/podcast-grid',
		array(
			'title'       => __( 'Podcast Grid', 'simple-podcasting' ),
			'description' => _x( 'Podcast Grid', 'This block pattern is used to display podcast in a grid structure.', 'simple-podcasting' ),
			'categories'  => [ 'query' ],
			'content'     => '<!-- wp:query {"query":{"perPage":3,"taxQuery":{"podcasting_podcasts":[' . implode( ',', $podcast_terms ) . ']},"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"flex","columns":3}} -->
				<div class="wp-block-query">
					<!-- wp:post-template -->
						<!-- wp:cover {"useFeaturedImage":true,"dimRatio":50,"className":"alignfull"} -->
							<div class="wp-block-cover alignfull">
								<span aria-hidden="true" class="wp-block-cover__background has-background-dim"></span>
								<div class="wp-block-cover__inner-container">
									<!-- wp:post-title {"isLink":true,"style":{"typography":{"fontSize":"1.6rem"},"elements":{"link":{"color":{"text":"var:preset|color|white"}}}},"textColor":"white"} /-->
									<!-- wp:post-terms {"term":"' . esc_js( PODCASTING_TAXONOMY_NAME ) . '","style":{"typography":{"fontSize":"2rem"},"elements":{"link":{"color":{"text":"var:preset|color|white"}}}},"textColor":"white"} /-->
									<!-- wp:post-date {"displayType":"modified","style":{"typography":{"fontSize":"0.8rem"},"spacing":{"margin":{"top":"0px","right":"0px","bottom":"0px","left":"0px"}},"elements":{"link":{"color":{"text":"var:preset|color|white"}}}},"textColor":"white"} /-->
								</div>
							</div>
						<!-- /wp:cover -->
					<!-- /wp:post-template -->
				</div>
			<!-- /wp:query -->',
		)
	);
}
add_action( 'init', __NAMESPACE__ . '\init' );


================================================
FILE: includes/blocks/podcast/markup.php
================================================
<?php
/**
 * Podcast markup
 *
 * @package tenup_podcasting
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Block content.
 * @var WP_Block $block      Block instance.
 * @var array    $context    Block context.
 */

$attributes = wp_parse_args(
	$attributes ?? [],
	[
		'id'                   => null,
		'caption'              => '',
		'displayDuration'      => false,
		'displayShowTitle'     => false,
		'displayEpisodeTitle'  => false,
		'displayArt'           => false,
		'displayExplicitBadge' => false,
		'displaySeasonNumber'  => false,
		'displayEpisodeNumber' => false,
		'displayEpisodeType'   => false,
	]
);

if ( ! $attributes['id'] ) {
	return;
}

$post_id        = get_the_id();
$podcast_shows  = get_the_terms( $post_id, 'podcasting_podcasts' );
$podcast_show   = $podcast_shows ? $podcast_shows[0] : '';
$show_name      = $podcast_show ? $podcast_show->name : '';
$src            = get_post_meta( $post_id, 'src', true );
$duration       = get_post_meta( $post_id, 'podcast_duration', true );
$explicit       = get_post_meta( $post_id, 'podcast_explicit', true );
$episode_type   = get_post_meta( $post_id, 'podcast_episode_type', true );
$episode_number = get_post_meta( $post_id, 'podcast_episode_number', true );
$season_number  = get_post_meta( $post_id, 'podcast_season_number', true );
if ( is_a( $podcast_show, 'WP_Term' ) ) {
	$term_image_id = get_term_meta( $podcast_show->term_id, 'podcasting_image', true );
} else {
	$term_image_id = '';
}

?>
<div class="wp-block-podcasting-podcast-outer">
	<div class="wp-block-podcasting-podcast__container">
		<?php if ( $attributes['displayArt'] && ( has_post_thumbnail() || ! empty( $term_image_id ) ) ) : ?>
			<div class="wp-block-podcasting-podcast__show-art">
				<div class="wp-block-podcasting-podcast__image">
					<?php
					if ( has_post_thumbnail() ) {
						the_post_thumbnail( 'medium' );
					} elseif ( $podcast_show instanceof \WP_Term ) {
						echo wp_get_attachment_image( $term_image_id, 'medium' );
					}
					?>
				</div>
			</div>
		<?php endif; ?>

		<div class="wp-block-podcasting-podcast__details">
			<?php if ( $attributes['displayEpisodeTitle'] ) : ?>
				<h3 class="wp-block-podcasting-podcast__show-title">
					<?php if ( $attributes['displayEpisodeNumber'] && ! empty( $episode_number ) ) : ?>
						<span>
							<?php echo esc_html( $episode_number ); ?>.
						</span>
					<?php endif; ?>
					<?php the_title(); ?>
				</h3>
			<?php endif; ?>
			<div class="wp-block-podcasting-podcast__show-details">
				<?php if ( $attributes['displayShowTitle'] && ! empty( $show_name ) ) : ?>
					<span class="wp-block-podcasting-podcast__title">
						<?php echo esc_html( $show_name ); ?>
					</span>
				<?php endif; ?>
				<?php if ( $attributes['displaySeasonNumber'] && ! empty( $season_number ) ) : ?>
					<span class="wp-block-podcasting-podcast__season">
						<?php esc_html_e( 'Season: ', 'simple-podcasting' ); ?>
						<?php echo esc_html( $season_number ); ?>
					</span>
				<?php endif; ?>
				<?php if ( $attributes['displayEpisodeNumber'] && ! empty( $episode_number ) ) : ?>
					<span class="wp-block-podcasting-podcast__episode">
						<?php esc_html_e( 'Episode: ', 'simple-podcasting' ); ?>
						<?php echo esc_html( $episode_number ); ?>
					</span>
				<?php endif; ?>
			</div>
			<div class="wp-block-podcasting-podcast__show-details">
				<?php if ( $attributes['displayDuration'] && ! empty( $duration ) ) : ?>
					<span class="wp-block-podcasting-podcast__duration">
						<?php esc_html_e( 'Listen Time: ', 'simple-podcasting' ); ?>
						<?php echo esc_html( $duration ); ?>
					</span>
				<?php endif; ?>
				<?php if ( $attributes['displayEpisodeType'] && ! empty( $episode_type ) && 'none' !== $episode_type ) : ?>
					<span class="wp-block-podcasting-podcast__episode-type">
						<?php esc_html_e( 'Episode type: ', 'simple-podcasting' ); ?>
						<?php echo esc_html( $episode_type ); ?>
					</span>
				<?php endif; ?>
				<?php if ( $attributes['displayExplicitBadge'] && ! empty( $explicit ) ) : ?>
					<span class="wp-block-podcasting-podcast__explicit-badge">
						<?php esc_html_e( 'Explicit: ', 'simple-podcasting' ); ?>
						<?php echo esc_html( $explicit ); ?>
					</span>
				<?php endif; ?>
			</div>
		</div>
	</div>
	<?php echo wp_kses_post( $content ); ?>
</div>


================================================
FILE: includes/blocks/podcast-transcript/cite.js
================================================
import { registerBlockType, createBlock } from '@wordpress/blocks';
import { useBlockProps, RichText } from '@wordpress/block-editor';

const Edit = ({
	attributes,
	attributes: { text },
	setAttributes,
	clientId,
	onReplace,
}) => {
	const blockProps = useBlockProps();
	return (
		<RichText
			tagName="cite"
			value={text}
			onChange={(content) => setAttributes({ text: content })}
			allowedFormats={[]}
			withoutInteractiveFormatting
			onSplit={(value, isOriginal) => {
				let block;

				if (isOriginal || value) {
					block = createBlock('podcasting/podcast-transcript-cite', {
						...attributes,
						content: value,
					});
				} else {
					block = createBlock('core/paragraph');
				}

				if (isOriginal) {
					block.clientId = clientId;
				}

				return block;
			}}
			onReplace={onReplace}
			{...blockProps}
		/>
	);
};

registerBlockType('podcasting/podcast-transcript-cite', {
	edit: Edit,
	save: ({ attributes: { text } }) => <cite>{text}</cite>,
});


================================================
FILE: includes/blocks/podcast-transcript/edit.js
================================================
import { useBlockProps, RichText, InnerBlocks } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import {
	RadioControl,
	Card,
	CardBody,
	Placeholder,
} from '@wordpress/components';
import { useSelect, withSelect } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
import { useEntityProp } from '@wordpress/core-data';
import { serialize } from '@wordpress/blocks';

const Edit = withSelect((select, { clientId }) => {
	return {
		innerBlocks: select('core/block-editor').getBlocksByClientId(clientId),
	};
})(({ attributes, setAttributes, isSelected, innerBlocks, clientId }) => {
	const blockProps = useBlockProps({});

	const postType = useSelect(
		(select) => select('core/editor').getCurrentPostType(),
		[]
	);

	const [meta, setMeta] = useEntityProp('postType', postType, 'meta');

	useEffect(() => {
		if (innerBlocks.length) {
			setMeta({
				...meta,
				podcast_transcript: serialize(innerBlocks[0].innerBlocks),
			});
		}
	}, [innerBlocks]);

	const isInnerBlockSelected = useSelect((select) =>
		select('core/block-editor').hasSelectedInnerBlock(clientId)
	);

	console.log(isInnerBlockSelected);

	const { display, linkText } = attributes;
	return (
		<section {...blockProps}>
			{(isSelected || isInnerBlockSelected) && (
				<>
					<Card>
						<CardBody>
							<RadioControl
								label={__(
									'Transcript Display',
									'simple-podcasting'
								)}
								selected={display}
								options={[
									{
										label: __(
											'Display Transcript on Post',
											'simple-podcasting'
										),
										value: 'post',
									},
									{
										label: __(
											'Display Link to Transcript',
											'simple-podcasting'
										),
										value: 'link',
									},
									{
										label: __(
											'Do not display - only show link in RSS feed',
											'simple-podcasting'
										),
										value: 'none',
									},
								]}
								onChange={(value) =>
									setAttributes({ display: value })
								}
							/>
						</CardBody>
					</Card>
					<br />
				</>
			)}

			{display === 'none' && !isSelected && (
				<Placeholder
					icon="microphone"
					label={__('Podcast Transcript', 'simple-podcasting')}
				/>
			)}

			{display === 'link' && (
				<RichText
					tagName="a"
					value={linkText}
					onChange={(content) => setAttributes({ linkText: content })}
					placeholder={__('Transcript Link', 'simple-podcasting')}
					allowedFormats={[]}
				/>
			)}

			{(isSelected || isInnerBlockSelected || display === 'post') && (
				<>
					<section>
						<InnerBlocks
							allowedBlocks={[
								'core/paragraph',
								'podcasting/podcast-transcript-cite',
								'podcasting/podcast-transcript-time',
							]}
						/>
					</section>
				</>
			)}
		</section>
	);
});

export default Edit;


================================================
FILE: includes/blocks/podcast-transcript/formats.js
================================================
import { registerFormatType, toggleFormat } from '@wordpress/rich-text';
import { BlockControls } from '@wordpress/block-editor';
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';

const Cite = ({ isActive, onChange, value }) => {
	const selectedBlock = useSelect((select) => {
		return select('core/block-editor').getSelectedBlock();
	}, []);

	if (
		selectedBlock &&
		selectedBlock.name !== 'podcasting/podcast-transcript'
	) {
		return null;
	}

	return (
		<BlockControls>
			<ToolbarGroup>
				<ToolbarButton
					icon="admin-users"
					title={__('Speaker Citation', 'simple-podcasting')}
					onClick={() => {
						onChange(
							toggleFormat(value, {
								type: 'podcasting/transcript-cite',
							})
						);
					}}
					isActive={isActive}
				/>
			</ToolbarGroup>
		</BlockControls>
	);
};

registerFormatType('podcasting/transcript-cite', {
	title: __('Cite', 'simple-podcasting'),
	tagName: 'cite',
	className: null,
	edit: Cite,
});

const Time = ({ isActive, onChange, value }) => {
	const selectedBlock = useSelect((select) => {
		return select('core/block-editor').getSelectedBlock();
	}, []);

	if (
		selectedBlock &&
		selectedBlock.name !== 'podcasting/podcast-transcript'
	) {
		return null;
	}

	return (
		<BlockControls>
			<ToolbarGroup>
				<ToolbarButton
					icon="clock"
					title={__('Timestamp', 'simple-podcasting')}
					onClick={() => {
						onChange(
							toggleFormat(value, {
								type: 'podcasting/transcript-time',
							})
						);
					}}
					isActive={isActive}
				/>
			</ToolbarGroup>
		</BlockControls>
	);
};

registerFormatType('podcasting/transcript-time', {
	title: __('Time', 'simple-podcasting'),
	tagName: 'time',
	className: null,
	edit: Time,
});


================================================
FILE: includes/blocks/podcast-transcript/index.js
================================================
import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks } from '@wordpress/block-editor';
import './styles.css';

import Edit from './edit';

import './cite';
import './time';

registerBlockType('podcasting/podcast-transcript', {
	edit: Edit,
	save: () => <InnerBlocks.Content />,
});


================================================
FILE: includes/blocks/podcast-transcript/markup.php
================================================
<?php
/**
 * Podcast Transcript markup
 *
 * @package tenup_podcasting
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Block content.
 * @var WP_Block $block      Block instance.
 * @var array    $context    Block context.
 */

use function tenup_podcasting\transcripts\get_transcript_link_from_post;

if ( 'none' !== $attributes['display'] ) : ?>
<div <?php echo get_block_wrapper_attributes(); // phpcs:ignore ?>>
	<?php
	switch ( $attributes['display'] ) {
		case 'post':
			echo wp_kses_post(
				do_blocks(
					get_post_meta( get_the_ID(), 'podcast_transcript', true )
				)
			);
			break;
		case 'link':
			printf(
				'<p><a href="%s">%s</a></p>',
				esc_url( get_transcript_link_from_post( get_post() ) ),
				esc_html( $attributes['linkText'] )
			);
			break;
	}
	?>
</div>
<?php endif; ?>


================================================
FILE: includes/blocks/podcast-transcript/styles.css
================================================
.wp-block-podcasting-podcast-transcript cite,
.wp-block-podcasting-podcast-transcript time {
	display: block;
}


================================================
FILE: includes/blocks/podcast-transcript/time.js
================================================
import { registerBlockType, createBlock } from '@wordpress/blocks';
import { useBlockProps, RichText } from '@wordpress/block-editor';

const Edit = ({
	attributes,
	attributes: { text },
	setAttributes,
	clientId,
	onReplace,
}) => {
	const blockProps = useBlockProps();
	return (
		<RichText
			tagName="time"
			value={text}
			onChange={(content) => setAttributes({ text: content })}
			allowedFormats={[]}
			withoutInteractiveFormatting
			onSplit={(value, isOriginal) => {
				let block;

				if (isOriginal || value) {
					block = createBlock('podcasting/podcast-transcript-time', {
						...attributes,
						content: value,
					});
				} else {
					block = createBlock('core/paragraph');
				}

				if (isOriginal) {
					block.clientId = clientId;
				}

				return block;
			}}
			onReplace={onReplace}
			{...blockProps}
		/>
	);
};

registerBlockType('podcasting/podcast-transcript-time', {
	edit: Edit,
	save: ({ attributes: { text } }) => <time>{text}</time>,
});


================================================
FILE: includes/blocks.php
================================================
<?php
/**
 * Register and enqueue all things block-related.
 *
 * @package tenup_podcasting
 */

namespace tenup_podcasting\block;

/**
 * Register block and its assets.
 */
function init() {
	$block_asset = require PODCASTING_PATH . 'dist/blocks.asset.php';
	wp_register_script(
		'podcasting-block-editor',
		PODCASTING_URL . 'dist/blocks.js',
		$block_asset['dependencies'],
		$block_asset['version'],
		true
	);

	wp_register_style(
		'podcasting-block-editor',
		PODCASTING_URL . 'dist/blocks.css',
		array(),
		$block_asset['version'],
		'all'
	);

	register_block_type(
		'podcasting/podcast',
		array(
			'editor_script'   => 'podcasting-block-editor',
			'editor_style'    => 'podcasting-block-editor',
			'render_callback' => __NAMESPACE__ . '\render',
		)
	);
}
add_action( 'init', __NAMESPACE__ . '\init' );

/**
 * Render the block.
 *
 * @param array    $attributes Block attributes.
 * @param string   $content Block content.
 * @param WP_Block $block Block instance.
 * @return string HTML output
 */
function render( $attributes, $content, $block ) {
	ob_start();
	include PODCASTING_PATH . 'includes/blocks/podcast/markup.php';
	return ob_get_clean();
}

/**
 * Register block and its assets.
 */
function init_transcript() {
	$podcast_transcript_block_asset = require PODCASTING_PATH . 'dist/podcasting-transcript.asset.php';

	wp_register_script(
		'podcasting-transcript',
		PODCASTING_URL . 'dist/podcasting-transcript.js',
		$podcast_transcript_block_asset['dependencies'],
		$podcast_transcript_block_asset['version'],
		true
	);

	wp_register_style(
		'podcasting-transcript',
		PODCASTING_URL . 'dist/podcasting-transcript.css',
		array(),
		$podcast_transcript_block_asset['version'],
		'all'
	);

	$transcript_block_args = array(
		'editor_script' => 'podcasting-transcript',
		'style_handles' => array( 'podcasting-transcript' ),
		'title'         => __( 'Podcast Transcript', 'simple-podcasting' ),
		'description'   => '',
		'textdomain'    => 'simple-podcasting',
		'name'          => 'podcasting/podcast-transcript',
		'icon'          => 'format-quote',
		'api_version'   => 2,
		'category'      => 'common',
		'attributes'    => array(
			'transcript' => array(
				'type' => 'string',
			),
			'display'    => array(
				'type'    => 'string',
				'default' => 'post',
			),
			'linkText'   => array(
				'type'    => 'string',
				'default' => __( 'Transcript Link', 'simple-podcastin' ),
			),
		),
		'example'       => array(),
		'supports'      => array(
			'multiple' => false,
			'inserter' => false,
		),
	);

	$transcript_block_args['render_callback'] = function ( $attributes, $content, $block ) {
		ob_start();
		include PODCASTING_PATH . 'includes/blocks/podcast-transcript/markup.php';
		return ob_get_clean();
	};

	register_block_type(
		'podcasting/podcast-transcript',
		$transcript_block_args
	);

	/**
	 * Simple cite block.
	 */
	register_block_type(
		'podcasting/podcast-transcript-cite',
		array(
			'editor_script' => 'podcasting-transcript',
			'title'         => __( 'Cite', 'simple-podcasting' ),
			'description'   => '',
			'textdomain'    => 'simple-podcasting',
			'name'          => 'podcasting/podcast-transcript-cite',
			'icon'          => 'admin-users',
			'api_version'   => 2,
			'category'      => 'text',
			'attributes'    => array(
				'text' => array(
					'type' => 'string',
				),
			),
			'supports'      => array(
				'html'     => false,
				'reusable' => false,
			),
			'parent'        => [ 'podcasting/podcast-transcript' ],
		)
	);

	/**
	 * Simple time block.
	 */
	register_block_type(
		'podcasting/podcast-transcript-time',
		array(
			'editor_script' => 'podcasting-transcript',
			'title'         => __( 'Time', 'simple-podcasting' ),
			'description'   => '',
			'textdomain'    => 'simple-podcasting',
			'name'          => 'podcasting/podcast-transcript-time',
			'icon'          => 'clock',
			'api_version'   => 2,
			'category'      => 'text',
			'attributes'    => array(
				'text' => array(
					'type' => 'string',
				),
			),
			'supports'      => array(
				'html'     => false,
				'reusable' => false,
			),
			'parent'        => [ 'podcasting/podcast-transcript' ],
		)
	);
}
add_action( 'init', __NAMESPACE__ . '\init_transcript' );

/**
 * Registers block for Podcast Platforms.
 */
function register_podcast_platforms_block() {
	if ( ! file_exists( PODCASTING_PATH . 'dist/podcast-platforms-block.asset.php' ) ) {
		return;
	}

	$block_asset = require PODCASTING_PATH . 'dist/podcast-platforms-block.asset.php';

	wp_register_script(
		'podcast-platforms-block-editor',
		PODCASTING_URL . 'dist/podcast-platforms-block.js',
		$block_asset['dependencies'],
		$block_asset['version'],
		true
	);

	wp_localize_script(
		'podcast-platforms-block-editor',
		'podcastingPlatformVars',
		array(
			'podcastingUrl' => PODCASTING_URL,
		)
	);

	wp_register_style(
		'podcast-platforms-block-editor',
		PODCASTING_URL . 'dist/podcast-platforms-block.css',
		array(),
		$block_asset['version'],
		'all'
	);

	register_block_type(
		'podcasting/podcast-platforms',
		array(
			'editor_script'   => 'podcast-platforms-block-editor',
			'editor_style'    => 'podcast-platforms-block-editor',
			'style'           => 'podcast-platforms-block-editor',
			'render_callback' => __NAMESPACE__ . '\render_podcasting_platforms',
		)
	);
}
add_action( 'init', __NAMESPACE__ . '\register_podcast_platforms_block' );

/**
 * Renders the block `podcasting/podcast-platforms`.
 *
 * @param array $attrs Block attributes.
 * @return string
 */
function render_podcasting_platforms( $attrs ) {
	if ( ! ( is_array( $attrs ) && isset( $attrs['showId'] ) ) ) {
		return '';
	}

	$show_id   = isset( $attrs['showId'] ) ? $attrs['showId'] : 0;
	$icon_size = isset( $attrs['iconSize'] ) ? $attrs['iconSize'] : 48;
	$align     = isset( $attrs['align'] ) ? $attrs['align'] : 'center';

	if ( 0 === $show_id ) {
		return '';
	}

	$supported_platforms = \tenup_podcasting\get_supported_platforms();
	$platforms           = get_term_meta( $show_id, 'podcasting_platforms', true );
	$theme               = get_term_meta( $show_id, 'podcasting_icon_theme', true );
	$theme               = empty( $theme ) ? 'color' : $theme;

	if ( ! is_array( $platforms ) || empty( $platforms ) ) {
		return '';
	}

	ob_start();

	?>

	<div class="simple-podcasting__podcast-platforms">
		<div class='simple-podcasting__podcasting-platform-list <?php echo esc_attr( 'simple-podcasting__podcasting-platform-list--' . $align ); ?>'>
			<?php foreach ( $platforms as $slug => $url ) : ?>
				<?php
				if ( empty( $url ) ) {
					continue;
				}

				$podcast_title = $supported_platforms[ $slug ]['title'];
				?>

				<span class='simple-podcasting__podcasting-platform-list-item'>
					<a href="<?php echo esc_url( $url ); ?>" target="_blank" title="<?php echo esc_attr( $podcast_title ); ?>" aria-label="<?php echo esc_attr( $podcast_title ); ?>">
						<img
							class="simple-pocasting__icon-size--<?php echo esc_attr( $icon_size ); ?>"
							src="<?php printf( '%sdist/images/icons/%s/%s-100.png', esc_url( PODCASTING_URL ), esc_attr( $slug ), esc_attr( $theme ) ); ?>"
						/>
					</a>
				</span>
			<?php endforeach; ?>
		</div>
	</div>

	<?php

	return ob_get_clean();
}

/**
 * Register JS block-specific strings.
 *
 * These need to be available in PHP for .pot creation but don't need to do anything.
 *
 * @return void
 */
function register_js_strings() {
	__( 'Insert a podcast episode into a post. To add it to a podcast feed, select a podcast in document settings.', 'simple-podcasting' );
	__( 'Podcast Settings', 'simple-podcasting' );
	__( 'Length (MM:SS)', 'simple-podcasting' );
	__( 'a podcast episode', 'simple-podcasting' );
	__( 'Season Number', 'simple-podcasting' );
	__( 'Episode Number', 'simple-podcasting' );
	__( 'Episode Type', 'simple-podcasting' );
}
add_action( 'init', __NAMESPACE__ . '\register_js_strings' );

/**
 * Register and load block editor translations.
 *
 * In an ideal world, this would only load the translations absolutely necessary in a JS context.
 * Since this is a small plugin it's still okay for now.
 *
 * @return void
 */
function load_translations() {
	if ( function_exists( 'wp_set_script_translations' ) ) {
		wp_set_script_translations( 'podcasting-block-editor', 'simple-podcasting' );
	} elseif ( function_exists( 'gutenberg_get_jed_locale_data' ) ) {
		$data = wp_json_encode( gutenberg_get_jed_locale_data( 'simple-podcasting' ) );
		wp_add_inline_script(
			'wp-i18n',
			'wp.i18n.setLocaleData( ' . $data . ', "simple-podcasting" );'
		);
	}
}
add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\load_translations' );

/**
 * Delete left over post meta after deleting podcast block.
 *
 * @param WP_Post         $post     Inserted or updated post object.
 * @param WP_REST_Request $request  Request object.
 * @param bool            $creating True when creating a post, false when updating.
 * @return void
 */
function block_editor_meta_cleanup( $post, $request, $creating ) {
	if ( $creating ) {
		return;
	}

	if ( has_block( 'podcasting/podcast', $post->ID ) ) {
		return;
	}

	\tenup_podcasting\helpers\delete_all_podcast_meta( $post->ID );
}
add_action( 'rest_after_insert_post', __NAMESPACE__ . '\block_editor_meta_cleanup', 10, 3 );

/**
 * Returns podcast platforms meta.
 */
function ajax_get_podcast_platforms() {
	$term_id = filter_input( INPUT_GET, 'show_id', FILTER_VALIDATE_INT );

	if ( ! $term_id ) {
		wp_send_json_error( esc_html__( 'Term ID not valid', 'simple-podcasting' ) );
	}

	$platforms = get_term_meta( $term_id, 'podcasting_platforms', true );

	if ( ! is_array( $platforms ) ) {
		wp_send_json_error( esc_html__( 'No shows found', 'simple-podcasting' ) );
	}

	$platforms = array_filter(
		$platforms,
		function ( $platform ) {
			return ! empty( $platform );
		}
	);

	$theme = get_term_meta( $term_id, 'podcasting_icon_theme', true );

	if ( empty( $theme ) ) {
		$theme = 'color';
	}

	$result = array(
		'platforms' => $platforms,
		'theme'     => $theme,
	);

	wp_send_json_success( $result );
}
add_action( 'wp_ajax_get_podcast_platforms', __NAMESPACE__ . '\ajax_get_podcast_platforms' );

/**
 * Latest podcast query for front-end.
 *
 * @param Object $query query object.
 */
function latest_episode_query_loop( $query ) {

	// update query to only return posts that have a podcast selected
	return [
		'post_type'      => 'post',
		'posts_per_page' => 1,
		'orderby'        => 'date',
		'order'          => 'DESC',
		'tax_query'      => [
			[
				'taxonomy' => 'podcasting_podcasts',
				'field'    => 'term_id',
				'operator' => 'EXISTS',
			],
		],
	];
}

/**
 * Latest podcast check.
 *
 * @param String $pre_render   pre render object.
 * @param Array  $parsed_block parsed block object.
 */
function latest_episode_check( $pre_render, $parsed_block ) {

	if ( isset( $parsed_block['attrs']['namespace'] ) && 'podcasting/latest-episode' === $parsed_block['attrs']['namespace'] ) {
		add_action( 'query_loop_block_query_vars', __NAMESPACE__ . '\latest_episode_query_loop' );
	}
}
add_filter( 'pre_render_block', __NAMESPACE__ . '\latest_episode_check', 10, 2 );

/**
 * Latest podcast query in editor.
 *
 * @param Array           $args    query args.
 * @param WP_REST_Request $request request object.
 */
function latest_episode_query_api( $args, $request ) {

	$podcasting_podcasts = $request->get_param( 'podcastingQuery' );

	if ( 'not_empty' === $podcasting_podcasts ) {
		$args = [
			'post_type'      => 'post',
			'posts_per_page' => 1,
			'orderby'        => 'date',
			'order'          => 'DESC',
			'tax_query'      => [
				[
					'taxonomy' => 'podcasting_podcasts',
					'field'    => 'term_id',
					'operator' => 'EXISTS',
				],
			],
		];
	}

	return $args;
}
add_filter( 'rest_post_query', __NAMESPACE__ . '\latest_episode_query_api', 10, 2 );


================================================
FILE: includes/create-podcast.php
================================================
<?php
/**
 * Defines class to handle the validation, sanitization
 * and creation of a podcast.
 *
 * @package tenup_podcasting
 */

namespace tenup_podcasting;

/**
 * Provides methods to create a podcast.
 */
class Create_Podcast {
	/**
	 * Name of the podcast.
	 *
	 * @var string
	 */
	protected $podcast_name = '';

	/**
	 * Name of the podcast artist.
	 *
	 * @var string
	 */
	protected $podcast_talent_name = '';

	/**
	 * Summary of the podcast.
	 *
	 * @var string
	 */
	protected $podcast_description = '';

	/**
	 * Podcast's primary category.
	 *
	 * @var string
	 */
	protected $podcast_category = '';

	/**
	 * ID of the podcast cover.
	 *
	 * @var int
	 */
	protected $podcast_cover_id = 0;

	/**
	 * Verifies nonce needed for the creation of a podcast.
	 *
	 * @return boolean|WP_Error
	 */
	public function verify_nonce() {
		$is_nonce_set = isset( $_POST['simple-podcasting-create-show-nonce-field'] );

		if ( ! $is_nonce_set ) {
			return false;
		}

		$is_nonce_verified = wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['simple-podcasting-create-show-nonce-field'] ) ), 'simple-podcasting-create-show-action' );

		if ( ! $is_nonce_verified ) {
			return new \WP_Error(
				'simple_podcasting_nonce_verification_failed',
				esc_html__( 'Nonce verification failed.' )
			);
		}

		ret
Download .txt
gitextract_alta88pg/

├── .distignore
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github/
│   ├── CODEOWNERS
│   └── workflows/
│       ├── build-release-zip.yml
│       ├── close-stale-issues.yml
│       ├── cypress.yml
│       ├── dependency-review.yml
│       ├── php-compatibility.yml
│       ├── phpcs.yml
│       ├── phpunit.yml
│       ├── push-asset-readme-update.yml
│       ├── push-deploy.yml
│       ├── repo-automator.yml
│       └── wordpress-version-checker.yml
├── .gitignore
├── .husky/
│   ├── .gitignore
│   └── pre-commit
├── .nvmrc
├── .phpcs.xml.dist
├── .prettierrc
├── .wordpress-org/
│   └── blueprints/
│       └── blueprint.json
├── .wordpress-version-checker.json
├── .wp-env.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CREDITS.md
├── LICENSE.md
├── README.md
├── assets/
│   ├── css/
│   │   ├── podcasting-edit-term.css
│   │   ├── podcasting-editor-screen.css
│   │   ├── podcasting-onboarding.scss
│   │   └── podcasting-transcript.css
│   └── js/
│       ├── blocks/
│       │   ├── latest-episode/
│       │   │   ├── index.js
│       │   │   └── index.scss
│       │   ├── podcast/
│       │   │   ├── index.js
│       │   │   └── index.scss
│       │   └── podcast-platforms/
│       │       ├── edit.js
│       │       ├── index.js
│       │       └── index.scss
│       ├── blocks.js
│       ├── create-podcast-show.js
│       ├── deprecated.js
│       ├── edit.js
│       ├── onboarding.js
│       ├── podcasting-edit-post.js
│       ├── podcasting-edit-term.js
│       └── transforms.js
├── composer.json
├── includes/
│   ├── admin/
│   │   ├── create-podcast-component.php
│   │   ├── onboarding.php
│   │   └── views/
│   │       ├── onboarding-header.php
│   │       ├── onboarding-page-one.php
│   │       └── onboarding-page-two.php
│   ├── block-patterns.php
│   ├── blocks/
│   │   ├── podcast/
│   │   │   └── markup.php
│   │   └── podcast-transcript/
│   │       ├── cite.js
│   │       ├── edit.js
│   │       ├── formats.js
│   │       ├── index.js
│   │       ├── markup.php
│   │       ├── styles.css
│   │       └── time.js
│   ├── blocks.php
│   ├── create-podcast.php
│   ├── customize-feed.php
│   ├── datatypes.php
│   ├── helpers.php
│   ├── post-meta-box.php
│   ├── rest-external-url.php
│   ├── transcripts.php
│   └── upgrade.php
├── package.json
├── phpunit.xml.dist
├── readme.txt
├── simple-podcasting.php
├── templates/
│   └── transcript.php
├── tests/
│   ├── bin/
│   │   └── set-wp-config.js
│   ├── cypress/
│   │   ├── .eslintrc
│   │   ├── config.config.js
│   │   ├── fixtures/
│   │   │   └── example.json
│   │   ├── integration/
│   │   │   ├── admin.test.js
│   │   │   ├── block.test.js
│   │   │   ├── onboarding.test.js
│   │   │   ├── podcast-setting-panel.test.js
│   │   │   └── taxonomy.test.js
│   │   ├── plugins/
│   │   │   └── index.js
│   │   ├── support/
│   │   │   ├── functions.js
│   │   │   └── index.js
│   │   └── tsconfig.json
│   └── unit/
│       ├── bootstrap.php
│       ├── test-blocks.php
│       ├── test-customize-feed.php
│       ├── test-datatypes.php
│       ├── test-helpers.php
│       ├── test-rest-external-url.php
│       └── test-transcript.php
└── webpack.config.js
Download .txt
SYMBOL INDEX (130 symbols across 26 files)

FILE: assets/js/blocks.js
  constant VARIATION_NAME (line 147) | const VARIATION_NAME = 'podcasting/latest-episode';

FILE: assets/js/blocks/podcast-platforms/edit.js
  function Edit (line 21) | function Edit( props ) {

FILE: assets/js/create-podcast-show.js
  constant DEFAULT_QUERY (line 26) | const DEFAULT_QUERY = {
  function attachPodcastToPost (line 276) | function attachPodcastToPost( isChecked, podcastId ) {

FILE: assets/js/edit.js
  constant ALLOWED_MEDIA_TYPES (line 22) | const ALLOWED_MEDIA_TYPES = ['audio'];
  function useFeaturedImage (line 30) | function useFeaturedImage() {
  function Edit (line 51) | function Edit( props ) {
  function PodcastBlockWithHooks (line 545) | function PodcastBlockWithHooks(props) {

FILE: assets/js/podcasting-edit-term.js
  function clearImageField (line 7) | function clearImageField( el ) {

FILE: includes/admin/create-podcast-component.php
  class Create_Podcast_Component (line 14) | class Create_Podcast_Component {
    method __construct (line 18) | public function __construct() {
    method admin_enqueue_scripts (line 25) | public function admin_enqueue_scripts() {

FILE: includes/admin/onboarding.php
  class Onboarding (line 13) | class Onboarding {
    method __construct (line 38) | public function __construct() {
    method register_onoarding_page (line 48) | public function register_onoarding_page() {
    method render_page_contents (line 68) | public function render_page_contents() {
    method onboarding_action_handler (line 94) | public function onboarding_action_handler() {

FILE: includes/block-patterns.php
  function init (line 13) | function init() {

FILE: includes/blocks.php
  function init (line 13) | function init() {
  function render (line 50) | function render( $attributes, $content, $block ) {
  function init_transcript (line 59) | function init_transcript() {
  function register_podcast_platforms_block (line 178) | function register_podcast_platforms_block() {
  function render_podcasting_platforms (line 227) | function render_podcasting_platforms( $attrs ) {
  function register_js_strings (line 288) | function register_js_strings() {
  function load_translations (line 307) | function load_translations() {
  function block_editor_meta_cleanup (line 328) | function block_editor_meta_cleanup( $post, $request, $creating ) {
  function ajax_get_podcast_platforms (line 344) | function ajax_get_podcast_platforms() {
  function latest_episode_query_loop (line 384) | function latest_episode_query_loop( $query ) {
  function latest_episode_check (line 408) | function latest_episode_check( $pre_render, $parsed_block ) {
  function latest_episode_query_api (line 422) | function latest_episode_query_api( $args, $request ) {

FILE: includes/create-podcast.php
  class Create_Podcast (line 14) | class Create_Podcast {
    method verify_nonce (line 55) | public function verify_nonce() {
    method sanitize_podcast_fields (line 77) | public function sanitize_podcast_fields() {
    method save_podcast_fields (line 90) | public function save_podcast_fields() {

FILE: includes/customize-feed.php
  function xmlns (line 15) | function xmlns() {
  function get_the_term (line 25) | function get_the_term() {
  function bloginfo_rss_name (line 40) | function bloginfo_rss_name( $output ) {
  function bloginfo_rss (line 61) | function bloginfo_rss( $output, $requested ) {
  function feed_head (line 92) | function feed_head() {
  function feed_item (line 169) | function feed_item() {
  function display_rss_enclosure (line 291) | function display_rss_enclosure( $post ) {
  function generate_categories (line 321) | function generate_categories() {
  function empty_rss_excerpt (line 379) | function empty_rss_excerpt( $output ) {
  function pre_get_posts (line 398) | function pre_get_posts( $query ) {

FILE: includes/datatypes.php
  function register_meta (line 13) | function register_meta() {
  function podcasting_term_auth_callback (line 206) | function podcasting_term_auth_callback() {
  function create_podcasts_taxonomy (line 217) | function create_podcasts_taxonomy() {
  function filter_parent_file (line 262) | function filter_parent_file( $file ) {
  function add_top_level_menu (line 278) | function add_top_level_menu() {
  function add_podcasting_taxonomy_help_text (line 295) | function add_podcasting_taxonomy_help_text() {
  function get_supported_platforms (line 307) | function get_supported_platforms() {
  function render_platform_fields (line 361) | function render_platform_fields( $field, $value, $term_id ) {
  function add_podcasting_term_add_meta_fields (line 421) | function add_podcasting_term_add_meta_fields( $term ) {
  function the_field (line 440) | function the_field( $field, $value = '', $term_id = false ) {
  function save_podcasting_term_meta (line 580) | function save_podcasting_term_meta( $term_id ) {
  function add_podcasting_term_edit_meta_fields (line 634) | function add_podcasting_term_edit_meta_fields( $term ) {
  function add_podcasting_term_meta_nonce (line 669) | function add_podcasting_term_meta_nonce( $term, $taxonomy = false ) {
  function add_podcasting_term_feed_link_column (line 699) | function add_podcasting_term_feed_link_column( $content, $column_name, $...
  function add_podcasting_term_podcasting_image_column (line 718) | function add_podcasting_term_podcasting_image_column( $content, $column_...
  function add_custom_term_columns (line 736) | function add_custom_term_columns( $columns ) {
  function get_meta_fields (line 755) | function get_meta_fields() {
  function get_podcasting_categories (line 872) | function get_podcasting_categories() {
  function get_podcasting_categories_options (line 1070) | function get_podcasting_categories_options() {
  function get_podcasting_language_options (line 1094) | function get_podcasting_language_options() {

FILE: includes/helpers.php
  function get_podcast_meta_from_url (line 17) | function get_podcast_meta_from_url( $url ) {
  function delete_all_podcast_meta (line 71) | function delete_all_podcast_meta( $post_id ) {

FILE: includes/post-meta-box.php
  function add_podcasting_meta_box (line 13) | function add_podcasting_meta_box() {
  function meta_box_html (line 33) | function meta_box_html( $post ) {
  function save_meta_box (line 110) | function save_meta_box( $post_id ) {
  function edit_post_enqueues (line 193) | function edit_post_enqueues( $hook_suffix ) {

FILE: includes/rest-external-url.php
  function setup (line 19) | function setup() {
  function define_endpoint_for_external_files_meta_check (line 30) | function define_endpoint_for_external_files_meta_check() {
  function handle_request (line 58) | function handle_request( \WP_REST_Request $request ) {

FILE: includes/transcripts.php
  function query_vars (line 21) | function query_vars( $vars ) {
  function template (line 35) | function template( $template ) {
  function rewrite_rules (line 50) | function rewrite_rules( $rules ) {
  function get_transcript_link_from_post (line 63) | function get_transcript_link_from_post( $post = null ) {
  function allow_time_element (line 82) | function allow_time_element( $html, $context ) {

FILE: includes/upgrade.php
  function maybe_flush_rewrite (line 15) | function maybe_flush_rewrite() {

FILE: simple-podcasting.php
  function minimum_php_requirement (line 27) | function minimum_php_requirement() {
  function site_meets_php_requirements (line 38) | function site_meets_php_requirements() {
  function activate_plugin (line 94) | function activate_plugin() {
  function podcasting_is_enabled (line 133) | function podcasting_is_enabled() {
  function podcasting_edit_term_enqueues (line 151) | function podcasting_edit_term_enqueues( $hook_suffix ) {
  function custom_feed (line 220) | function custom_feed( \WP_Query $query ) {
  function setup_edit_screen (line 248) | function setup_edit_screen() {
  function register_podcast_block_assets (line 258) | function register_podcast_block_assets() {
  function register_podcast_block_assets_admin (line 282) | function register_podcast_block_assets_admin() {
  function register_latest_episode_assets (line 306) | function register_latest_episode_assets() {
  function register_latest_episode_assets_admin (line 328) | function register_latest_episode_assets_admin() {

FILE: tests/cypress/config.config.js
  method setupNodeEvents (line 24) | setupNodeEvents(on, config) {

FILE: tests/cypress/integration/podcast-setting-panel.test.js
  function closeWelcomeGuide (line 29) | function closeWelcomeGuide() {
  function openEditorSidebar (line 43) | function openEditorSidebar() {
  function openComplementaryArea (line 56) | function openComplementaryArea() {

FILE: tests/unit/test-blocks.php
  class BlockTests (line 8) | class BlockTests extends TestCase {
    method setUp (line 14) | public function setUp() : void {
    method tearDown (line 22) | public function tearDown() : void {
    method test_init (line 26) | public function test_init() {
    method test_register_js_strings (line 77) | public function test_register_js_strings() {
    method test_block_editor_meta_cleanup (line 87) | public function test_block_editor_meta_cleanup( $creating, $has_block,...
    method data_provider_for_test_block_editor_meta_cleanup (line 108) | public function data_provider_for_test_block_editor_meta_cleanup() {

FILE: tests/unit/test-customize-feed.php
  class CustomizeFeedTests (line 8) | class CustomizeFeedTests extends TestCase {
    method setUp (line 14) | public function setUp() : void {
    method tearDown (line 22) | public function tearDown() : void {
    method test_get_the_term (line 26) | public function test_get_the_term() {
    method test_empty_rss_excerpt (line 58) | public function test_empty_rss_excerpt() {
    method test_pre_get_posts_no_feed (line 79) | public function test_pre_get_posts_no_feed() {
    method test_pre_get_posts_feed (line 90) | public function test_pre_get_posts_feed() {
    method test_feed_item (line 111) | public function test_feed_item( $talent_option, $post_data, $term, $po...
    method test_rss_title_can_be_filtered (line 179) | public function test_rss_title_can_be_filtered() {
    method data_provider_for_test_feed_item (line 204) | public function data_provider_for_test_feed_item() {

FILE: tests/unit/test-datatypes.php
  class DatatypesTests (line 8) | class DatatypesTests extends TestCase {
    method setUp (line 14) | public function setUp() : void {
    method tearDown (line 22) | public function tearDown() : void {
    method test_filter_parent_file (line 29) | public function test_filter_parent_file( $screen, $original_file, $exp...
    method data_provider_for_test_filter_parent_file (line 38) | public function data_provider_for_test_filter_parent_file()

FILE: tests/unit/test-helpers.php
  class HelpersTests (line 8) | class HelpersTests extends TestCase {
    method setUp (line 14) | public function setUp() : void {
    method tearDown (line 22) | public function tearDown() : void {
    method test_delete_all_podcast_meta (line 26) | public function test_delete_all_podcast_meta() {
    method test_get_podcast_meta_from_url (line 48) | public function test_get_podcast_meta_from_url( $url, $redirect, $head...
    method data_provider_for_test_get_podcast_meta_from_url (line 71) | public function data_provider_for_test_get_podcast_meta_from_url() {

FILE: tests/unit/test-rest-external-url.php
  class RestExternalUrlTests (line 8) | class RestExternalUrlTests extends TestCase {
    method setUp (line 14) | public function setUp() : void {
    method tearDown (line 22) | public function tearDown() : void {
    method test_handle_request_cached (line 26) | public function test_handle_request_cached() {

FILE: tests/unit/test-transcript.php
  class TranscriptTests (line 8) | class TranscriptTests extends TestCase {
    method setUp (line 14) | public function setUp() : void {
    method tearDown (line 22) | public function tearDown() : void {
    method test_get_transcript_from_post (line 26) | public function test_get_transcript_from_post() {
Condensed preview — 100 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (366K chars).
[
  {
    "path": ".distignore",
    "chars": 319,
    "preview": "# Directories\n/.git\n/.github\n/.husky\n/.wordpress-org\n/assets\n/gulp-tasks\n/node_modules\n/tests\n/vendor\n\n# Files\n.*\n/CHANG"
  },
  {
    "path": ".editorconfig",
    "chars": 262,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nindent_sty"
  },
  {
    "path": ".eslintignore",
    "chars": 31,
    "preview": "assets/js/frontend/vendor/*.js\n"
  },
  {
    "path": ".eslintrc",
    "chars": 103,
    "preview": "{\n\t\"extends\": [ \"plugin:@wordpress/eslint-plugin/recommended\" ],\n\t\"ignorePatterns\": [\"**/vendor/**\"]\n}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 429,
    "preview": "# These owners will be the default owners for everything in the repo. Unless a later match takes precedence, @10up/open-"
  },
  {
    "path": ".github/workflows/build-release-zip.yml",
    "chars": 1196,
    "preview": "name: Build release zip\n\npermissions:\n  contents: read\n\non:\n  workflow_dispatch:\n  workflow_call:\n  push:\n   branches:\n "
  },
  {
    "path": ".github/workflows/close-stale-issues.yml",
    "chars": 1708,
    "preview": "name: 'Close stale issues'\n\n# **What it does**: Closes issues where the original author doesn't respond to a request for"
  },
  {
    "path": ".github/workflows/cypress.yml",
    "chars": 2899,
    "preview": "name: E2E Test\n\npermissions:\n  contents: read\n  pull-requests: write\n\non:\n  push:\n    branches:\n      - trunk\n      - de"
  },
  {
    "path": ".github/workflows/dependency-review.yml",
    "chars": 1137,
    "preview": "# Dependency Review Action\n#\n# This Action will scan dependency manifest files that change as part of a Pull Reqest, sur"
  },
  {
    "path": ".github/workflows/php-compatibility.yml",
    "chars": 1355,
    "preview": "name: PHP Compatibility\n\npermissions:\n  contents: read\n\nenv:\n  COMPOSER_VERSION: \"2\"\n  COMPOSER_CACHE: \"${{ github.works"
  },
  {
    "path": ".github/workflows/phpcs.yml",
    "chars": 703,
    "preview": "name: PHPCS\n\npermissions:\n  contents: read\n\non:\n  push:\n    branches:\n      - develop\n      - trunk\n    paths:\n      - \""
  },
  {
    "path": ".github/workflows/phpunit.yml",
    "chars": 1568,
    "preview": "name: Unit Tests\n\npermissions:\n  contents: read\n\nenv:\n  COMPOSER_VERSION: \"2\"\n  COMPOSER_CACHE: \"${{ github.workspace }}"
  },
  {
    "path": ".github/workflows/push-asset-readme-update.yml",
    "chars": 778,
    "preview": "name: Plugin asset/readme update\n\non:\n  push:\n    branches:\n    - trunk\n\npermissions:\n  contents: read\n\njobs:\n  trunk:\n "
  },
  {
    "path": ".github/workflows/push-deploy.yml",
    "chars": 1139,
    "preview": "name: Deploy to WordPress.org\n\npermissions:\n  contents: write\n  packages: read\n  actions: write\n\non:\n  release:\n    type"
  },
  {
    "path": ".github/workflows/repo-automator.yml",
    "chars": 713,
    "preview": "name: 'Repo Automator'\n\npermissions:\n  contents: read\n  issues: write\n\non:\n  issues:\n    types:\n      - opened\n  push:\n "
  },
  {
    "path": ".github/workflows/wordpress-version-checker.yml",
    "chars": 477,
    "preview": "name: \"WordPress version checker\"\n\non:\n  push:\n    branches:\n      - develop\n      - trunk\n  pull_request:\n    branches:"
  },
  {
    "path": ".gitignore",
    "chars": 441,
    "preview": "node_modules\nbower_components\nlanguages\nrelease\nvendor\nphpunit.xml\n.idea\n.phpunit.result.cache\n\n# Project Files\ndist\nrul"
  },
  {
    "path": ".husky/.gitignore",
    "chars": 2,
    "preview": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 58,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".nvmrc",
    "chars": 4,
    "preview": "v20\n"
  },
  {
    "path": ".phpcs.xml.dist",
    "chars": 242,
    "preview": "<?xml version=\"1.0\"?>\n<ruleset name=\"Simple Podcasting\">\n\t<rule ref=\"10up-Default\" />\n\t<file>.</file>\n\t<exclude-pattern>"
  },
  {
    "path": ".prettierrc",
    "chars": 78,
    "preview": "{\n\t\"useTabs\": true,\n\t\"printWidth\": 90,\n\t\"tabWidth\": 4,\n\t\"singleQuote\": true\n}\n"
  },
  {
    "path": ".wordpress-org/blueprints/blueprint.json",
    "chars": 573,
    "preview": "{\n\t\"$schema\": \"https://playground.wordpress.net/blueprint-schema.json\",\n\t\"landingPage\": \"\\/wp-admin\\/admin.php?page=simp"
  },
  {
    "path": ".wordpress-version-checker.json",
    "chars": 52,
    "preview": "{\n    \"readme\": \"readme.txt\",\n    \"channel\": \"rc\"\n}\n"
  },
  {
    "path": ".wp-env.json",
    "chars": 23,
    "preview": "{\n  \"plugins\": [\".\"]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 38193,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file, per [the Keep a Changelog standard](ht"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3351,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 7487,
    "preview": "# Contributing and Maintaining\n\nFirst, thank you for taking the time to contribute!\n\nThe following is a set of guideline"
  },
  {
    "path": "CREDITS.md",
    "chars": 4259,
    "preview": "The following acknowledges the Maintainers for this repository, those who have Contributed to this repository (via bug r"
  },
  {
    "path": "LICENSE.md",
    "chars": 18092,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "README.md",
    "chars": 11015,
    "preview": "# Simple Podcasting for WordPress\n\n![Simple Podcasting](https://github.com/10up/simple-podcasting/blob/develop/.wordpres"
  },
  {
    "path": "assets/css/podcasting-edit-term.css",
    "chars": 765,
    "preview": ".taxonomy-podcasting_podcasts .term-parent-wrap {\n\tdisplay: none;\n}\n.podcast-image-thumbnail {\n\tmax-width: 300px;\n\tmax-h"
  },
  {
    "path": "assets/css/podcasting-editor-screen.css",
    "chars": 233,
    "preview": ".components-input-control,\n.components-base-control {\n\twidth: 100%;\n}\n.cover-art-container {\n\tdisplay: flex;\n\tflex-direc"
  },
  {
    "path": "assets/css/podcasting-onboarding.scss",
    "chars": 3503,
    "preview": "* {\n\tbox-sizing: border-box;\n}\n\n.admin_page_simple-podcasting-onboarding #wpcontent {\n\tpadding-left: 0;\n}\n\n#simple-podca"
  },
  {
    "path": "assets/css/podcasting-transcript.css",
    "chars": 112,
    "preview": ".wp-block-podcasting-podcast-transcript cite,\n.wp-block-podcasting-podcast-transcript time {\n\tdisplay: block;\n}\n"
  },
  {
    "path": "assets/js/blocks/latest-episode/index.js",
    "chars": 23,
    "preview": "import './index.scss';\n"
  },
  {
    "path": "assets/js/blocks/latest-episode/index.scss",
    "chars": 938,
    "preview": ".podcasting-latest-episode {\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: end;\n\tmin-height: 20rem;\n\toverfl"
  },
  {
    "path": "assets/js/blocks/podcast/index.js",
    "chars": 23,
    "preview": "import './index.scss';\n"
  },
  {
    "path": "assets/js/blocks/podcast/index.scss",
    "chars": 1449,
    "preview": ".wp-block-podcasting-podcast-outer {\n    border: 1px solid #707070;\n    border-radius: 4px;\n    padding: 20px;\n}\n\n.wp-bl"
  },
  {
    "path": "assets/js/blocks/podcast-platforms/edit.js",
    "chars": 7108,
    "preview": "import { useBlockProps, InspectorControls } from '@wordpress/block-editor';\nimport { useState, useEffect } from '@wordpr"
  },
  {
    "path": "assets/js/blocks/podcast-platforms/index.js",
    "chars": 786,
    "preview": "/**\n * Internal block libraries\n */\nimport { __ } from '@wordpress/i18n';\nimport { registerBlockType } from '@wordpress/"
  },
  {
    "path": "assets/js/blocks/podcast-platforms/index.scss",
    "chars": 1281,
    "preview": ".wp-block {\n\t.simple-podcasting__podcasting-platform-list {\n\t\tmargin-bottom: 3rem;\n\t}\n\n\t.simple-podcasting__podcast-plat"
  },
  {
    "path": "assets/js/blocks.js",
    "chars": 3687,
    "preview": "/**\n * Internal block libraries\n */\nimport { __ } from '@wordpress/i18n';\nimport { registerBlockType, registerBlockVaria"
  },
  {
    "path": "assets/js/create-podcast-show.js",
    "chars": 10384,
    "preview": "import { registerPlugin } from \"@wordpress/plugins\";\nimport { store as editPostStore } from '@wordpress/edit-post';\nimpo"
  },
  {
    "path": "assets/js/deprecated.js",
    "chars": 1578,
    "preview": "export default [\n\t{\n\t\tattributes: {\n\t\t\tid: {\n\t\t\t\ttype: 'number',\n\t\t\t},\n\t\t\tsrc: {\n\t\t\t\ttype: 'string',\n\t\t\t\tsource: 'attrib"
  },
  {
    "path": "assets/js/edit.js",
    "chars": 14315,
    "preview": "const { __ } = wp.i18n;\nconst {\n\tBlockControls,\n\tInspectorControls,\n\tMediaPlaceholder,\n\tMediaReplaceFlow,\n\tMediaUpload,\n"
  },
  {
    "path": "assets/js/onboarding.js",
    "chars": 756,
    "preview": "import '../css/podcasting-onboarding.scss';\n\n( function( $ ) {\n\t$( function() {\n\t\tconst selectImageBtn = $( '#simple-pod"
  },
  {
    "path": "assets/js/podcasting-edit-post.js",
    "chars": 863,
    "preview": "/*global jQuery */\njQuery( document ).ready( function( $ ) {\n\t$( '#podcasting-enclosure-button' ).click( function( e ) {"
  },
  {
    "path": "assets/js/podcasting-edit-term.js",
    "chars": 3328,
    "preview": "/*global jQuery, validateForm*/\nimport '../css/podcasting-edit-term.css';\n\njQuery( document ).ready( function( $ ) {\n\n\t/"
  },
  {
    "path": "assets/js/transforms.js",
    "chars": 807,
    "preview": "/**\n * WordPress dependencies\n */\nconst { select } = wp.data;\nconst { createBlock } = wp.blocks;\n\n/**\n * Transforms\n */\n"
  },
  {
    "path": "composer.json",
    "chars": 745,
    "preview": "{\n  \"name\": \"10up/simple-podcasting\",\n  \"description\": \"A simple podcasting solution for WordPress. \",\n  \"homepage\": \"ht"
  },
  {
    "path": "includes/admin/create-podcast-component.php",
    "chars": 1009,
    "preview": "<?php\n/**\n * Defines class to handle creation of a podcast\n * from with the Post editor.\n *\n * @package tenup_podcasting"
  },
  {
    "path": "includes/admin/onboarding.php",
    "chars": 2912,
    "preview": "<?php\n/**\n * Registers and renders the onboarding setup wizard.\n *\n * @package tenup_podcasting\n */\n\nnamespace tenup_pod"
  },
  {
    "path": "includes/admin/views/onboarding-header.php",
    "chars": 859,
    "preview": "<?php\n/**\n * Header template for onboarding: Step - 1.\n *\n * @package tenup_podcasting\n */\n\n?>\n<div id=\"simple-podcastin"
  },
  {
    "path": "includes/admin/views/onboarding-page-one.php",
    "chars": 4687,
    "preview": "<?php\n/**\n * Body template for onboarding: Step - 1.\n *\n * @package tenup_podcasting\n */\n\n?>\n<form method=\"POST\" action="
  },
  {
    "path": "includes/admin/views/onboarding-page-two.php",
    "chars": 2445,
    "preview": "<?php\n/**\n * Body template for onboarding: Step - 2.\n *\n * @package tenup_podcasting\n */\n\nuse tenup_podcasting\\admin\\Onb"
  },
  {
    "path": "includes/block-patterns.php",
    "chars": 2159,
    "preview": "<?php\n/**\n * Register and enqueue all things block patterns.\n *\n * @package tenup_podcasting\n */\n\nnamespace tenup_podcas"
  },
  {
    "path": "includes/blocks/podcast/markup.php",
    "chars": 4360,
    "preview": "<?php\n/**\n * Podcast markup\n *\n * @package tenup_podcasting\n *\n * @var array    $attributes Block attributes.\n * @var st"
  },
  {
    "path": "includes/blocks/podcast-transcript/cite.js",
    "chars": 983,
    "preview": "import { registerBlockType, createBlock } from '@wordpress/blocks';\nimport { useBlockProps, RichText } from '@wordpress/"
  },
  {
    "path": "includes/blocks/podcast-transcript/edit.js",
    "chars": 2867,
    "preview": "import { useBlockProps, RichText, InnerBlocks } from '@wordpress/block-editor';\nimport { __ } from '@wordpress/i18n';\nim"
  },
  {
    "path": "includes/blocks/podcast-transcript/formats.js",
    "chars": 1825,
    "preview": "import { registerFormatType, toggleFormat } from '@wordpress/rich-text';\nimport { BlockControls } from '@wordpress/block"
  },
  {
    "path": "includes/blocks/podcast-transcript/index.js",
    "chars": 305,
    "preview": "import { registerBlockType } from '@wordpress/blocks';\nimport { InnerBlocks } from '@wordpress/block-editor';\nimport './"
  },
  {
    "path": "includes/blocks/podcast-transcript/markup.php",
    "chars": 834,
    "preview": "<?php\n/**\n * Podcast Transcript markup\n *\n * @package tenup_podcasting\n *\n * @var array    $attributes Block attributes."
  },
  {
    "path": "includes/blocks/podcast-transcript/styles.css",
    "chars": 112,
    "preview": ".wp-block-podcasting-podcast-transcript cite,\n.wp-block-podcasting-podcast-transcript time {\n\tdisplay: block;\n}\n"
  },
  {
    "path": "includes/blocks/podcast-transcript/time.js",
    "chars": 983,
    "preview": "import { registerBlockType, createBlock } from '@wordpress/blocks';\nimport { useBlockProps, RichText } from '@wordpress/"
  },
  {
    "path": "includes/blocks.php",
    "chars": 11849,
    "preview": "<?php\n/**\n * Register and enqueue all things block-related.\n *\n * @package tenup_podcasting\n */\n\nnamespace tenup_podcast"
  },
  {
    "path": "includes/create-podcast.php",
    "chars": 4156,
    "preview": "<?php\n/**\n * Defines class to handle the validation, sanitization\n * and creation of a podcast.\n *\n * @package tenup_pod"
  },
  {
    "path": "includes/customize-feed.php",
    "chars": 12127,
    "preview": "<?php\n/**\n * Customize the feed for a specific podcast. Insert the podcast data stored in term meta.\n *\n * @package tenu"
  },
  {
    "path": "includes/datatypes.php",
    "chars": 31214,
    "preview": "<?php\n/**\n * Register the data types\n *\n * @package tenup_podcasting\n */\n\nnamespace tenup_podcasting;\n\n/**\n * Register t"
  },
  {
    "path": "includes/helpers.php",
    "chars": 2647,
    "preview": "<?php\n/**\n * Common helper functions\n *\n * @package tenup_podcasting\\helpers\n */\n\nnamespace tenup_podcasting\\helpers;\n\n/"
  },
  {
    "path": "includes/post-meta-box.php",
    "chars": 8929,
    "preview": "<?php\n/**\n * Add a meta box to the post edit screen, plus handlers for saving.\n *\n * @package tenup_podcasting;\n */\n\nnam"
  },
  {
    "path": "includes/rest-external-url.php",
    "chars": 2036,
    "preview": "<?php\n/**\n * Endpoint definitions.\n *\n * @package tenup_podcasting\\endpoints\n */\n\nnamespace tenup_podcasting\\endpoints\\e"
  },
  {
    "path": "includes/transcripts.php",
    "chars": 2165,
    "preview": "<?php\n/**\n * Adds an endpoint for viewing transcripts.\n *\n * @package tenup_podcasting\\transcripts;\n */\n\nnamespace tenup"
  },
  {
    "path": "includes/upgrade.php",
    "chars": 527,
    "preview": "<?php\n/**\n * Upgrade routines using options.\n *\n * @package tenup_podcasting\\upgrade;\n */\n\nnamespace tenup_podcasting\\up"
  },
  {
    "path": "package.json",
    "chars": 2088,
    "preview": "{\n  \"name\": \"@10up/simple-podcasting\",\n  \"version\": \"1.9.1\",\n  \"description\": \"A simple podcasting solution for WordPres"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 543,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n\t\tbootstrap=\"tests/unit/bootstrap.php\"\n\t\tbackupGlobals=\"false\"\n\t\tcolors="
  },
  {
    "path": "readme.txt",
    "chars": 20883,
    "preview": "=== Simple Podcasting ===\nContributors: 10up, helen, adamsilverstein, jakemgold, jeffpaul, cadic\nTags:         podcastin"
  },
  {
    "path": "simple-podcasting.php",
    "chars": 9058,
    "preview": "<?php\n/**\n * Plugin Name:       Simple Podcasting\n * Plugin URI:        https://github.com/10up/simple-podcasting\n * Des"
  },
  {
    "path": "templates/transcript.php",
    "chars": 1119,
    "preview": "<?php\n/**\n * Template for transcripts.\n * Intentionally barebones with the minimum html for use by tools.\n *\n * @package"
  },
  {
    "path": "tests/bin/set-wp-config.js",
    "chars": 817,
    "preview": "#!/usr/bin/env node\n\nconst fs = require('fs');\n\nconst path = `${process.cwd()}/.wp-env.override.json`;\n\nlet config = fs."
  },
  {
    "path": "tests/cypress/.eslintrc",
    "chars": 92,
    "preview": "{\n\t\"extends\": \"plugin:cypress/recommended\",\n\t\"rules\": {\n\t\t\"jest/expect-expect\": \"off\",\n\t}\n}\n"
  },
  {
    "path": "tests/cypress/config.config.js",
    "chars": 1053,
    "preview": "const { defineConfig } = require('cypress');\n\nmodule.exports = defineConfig({\n\tfixturesFolder: __dirname + '/fixtures',\n"
  },
  {
    "path": "tests/cypress/fixtures/example.json",
    "chars": 155,
    "preview": "{\n  \"name\": \"Using fixtures to represent data\",\n  \"email\": \"hello@cypress.io\",\n  \"body\": \"Fixtures are a great way to mo"
  },
  {
    "path": "tests/cypress/integration/admin.test.js",
    "chars": 261,
    "preview": "describe('Admin can login and make sure plugin is activated', () => {\n\tbeforeEach(() => {\n\t\tcy.login();\n\t});\n\n\tit('Can a"
  },
  {
    "path": "tests/cypress/integration/block.test.js",
    "chars": 2578,
    "preview": "import 'cypress-localstorage-commands';\nconst { populatePodcast } = require('../support/functions');\n\ndescribe('Admin ca"
  },
  {
    "path": "tests/cypress/integration/onboarding.test.js",
    "chars": 1887,
    "preview": "const {\n\trandomName,\n\tpopulatePodcast,\n\tdeleteAllTerms,\n} = require('../support/functions');\n\ndescribe('Onboarding tests"
  },
  {
    "path": "tests/cypress/integration/podcast-setting-panel.test.js",
    "chars": 5233,
    "preview": "describe('Create podcast setting panel', () => {\n\tbefore(() => {\n\t\tcy.login();\n\t\tcy.visit(\n\t\t\t'/wp-admin/edit-tags.php?t"
  },
  {
    "path": "tests/cypress/integration/taxonomy.test.js",
    "chars": 3171,
    "preview": "const {\n\trandomName,\n\tpopulatePodcast,\n\tdeleteAllTerms,\n} = require('../support/functions');\n\ndescribe('Admin can create"
  },
  {
    "path": "tests/cypress/plugins/index.js",
    "chars": 1388,
    "preview": "/// <reference types=\"cypress\" />\n// ***********************************************************\n// This example plugins"
  },
  {
    "path": "tests/cypress/support/functions.js",
    "chars": 1897,
    "preview": "export const randomName = () => Math.random().toString(16).substring(7);\n\nexport const getFirstImage = () => {\n\tcy.get('"
  },
  {
    "path": "tests/cypress/support/index.js",
    "chars": 598,
    "preview": "// ***********************************************************\n// This example support/index.js is processed and\n// load"
  },
  {
    "path": "tests/cypress/tsconfig.json",
    "chars": 102,
    "preview": "{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"types\": [\"cypress\"]\n  },\n  \"include\": [\"**/*.*\"]\n}\n"
  },
  {
    "path": "tests/unit/bootstrap.php",
    "chars": 1075,
    "preview": "<?php\n/**\n * The bootstrap file for PHPUnit tests for the Simple Podcasting plugin.\n * Starts up WP_Mock and requires th"
  },
  {
    "path": "tests/unit/test-blocks.php",
    "chars": 3514,
    "preview": "<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * The AddressTests class tests the functions associated with an address ass"
  },
  {
    "path": "tests/unit/test-customize-feed.php",
    "chars": 10789,
    "preview": "<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * The AddressTests class tests the functions associated with an address ass"
  },
  {
    "path": "tests/unit/test-datatypes.php",
    "chars": 2200,
    "preview": "<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * The AddressTests class tests the functions associated with an address ass"
  },
  {
    "path": "tests/unit/test-helpers.php",
    "chars": 3498,
    "preview": "<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * The AddressTests class tests the functions associated with an address ass"
  },
  {
    "path": "tests/unit/test-rest-external-url.php",
    "chars": 1315,
    "preview": "<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * The AddressTests class tests the functions associated with an address ass"
  },
  {
    "path": "tests/unit/test-transcript.php",
    "chars": 1271,
    "preview": "<?php\n\nuse PHPUnit\\Framework\\TestCase;\n\n/**\n * The TranscriptTests class tests the functions associated with a transcrip"
  },
  {
    "path": "webpack.config.js",
    "chars": 1421,
    "preview": "const defaultConfig = require('@wordpress/scripts/config/webpack.config');\nconst CopyPlugin = require('copy-webpack-plug"
  }
]

About this extraction

This page contains the full source code of the 10up/simple-podcasting GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 100 files (323.6 KB), approximately 97.0k tokens, and a symbol index with 130 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!